This question already has answers here:
How to add JTable in JPanel with null layout?
(11 answers)
Closed 8 years ago.
I know this is horrible coding but I desperately need to fix this problem. I've tried multiple ways of trying to position the button but the button still stays in the top center with all the other buttons lined up after it.
import javax.imageio.ImageIO;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.Graphics;
import java.io.IOException;
import java.net.URL;
public class Template extends JFrame {
/**
* #param args
*/
public static void main(String[] args) throws IOException{
// TODO Auto-generated method stub
JFrame frame = new JFrame("The Impossible Quiz");//Construct JFrame
frame.setLayout(null);//manual setting for button placement
frame.setContentPane(new JPanel() {//sets panel as background
BufferedImage image = ImageIO.read(new URL("https://pbs.twimg.com/media/BoyFVfXIUAA0Tik.png"));//uses url image
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image, 0, 0, 1360, 690, this);//sets image as jframe background
}
});
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//closes jframe when press exit
frame.setSize(1365, 730);//sets size of jframe
JPanel buttonpanel = new JPanel();//sets panel for all buttons
buttonpanel.setBounds(0, 0, 1460, 690);//sets placing and sizing of panel
buttonpanel.setOpaque(false);//makes panel transparent
JButton next = new JButton ("Start");//contructs correct start button
next.setBounds(10, 5, 40, 50);
buttonpanel.add(next);//adds button to panel
next.addActionListener(new ActionListener()//adds actionlistener to button
{
public void actionPerformed(ActionEvent e)
{
new Introduction();//continues to next question
}
});
JButton wrongstart = new JButton("Start!!");//constructs wrong start button
wrongstart.setSize(100, 400);//setes size of button
buttonpanel.add(wrongstart);//adds button to panel
wrongstart.addActionListener(new ActionListener()//adds actionlistener to button
{
public void actionPerformed(ActionEvent e)
{
new Wrong();//direct user to wrong panel
}
});
frame.add(buttonpanel);//adds panel to jframe
frame.setVisible(true);//sets jframe as visible
}
}
Your problem is that you're trying to use absolute positioning to position a component (your JButton) in a container (the containing JPanel) that uses FlowLayout as default, and FlowLayout completely ignores the components bounds. A quick solution is to set the JPanel's layout to null allowing for absolute positioning. A correct solution is to always avoid null layouts and absolute positioning and instead to nest JPanels, each using its own layouts, in order to create complex yet flexible and pleasing GUI's.
You're setting the JFrame contentPane's layout to null as well -- don't do that either.
And then adding a JPanel as the contentPane which uses a default FlowLayout -- don't do that. Let the contentPane's layout be BorderLayout.
Edit
For example, if we leave the contentPane alone with its BorderLayout, and add another image panel on top of it, one that uses GridBagLayout, we can easily position our JButton to the top left corner of the GUI if desired. ....
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.image.BufferedImage;
import javax.swing.*;
#SuppressWarnings("serial")
public class Template2 extends JPanel {
private static final int PREF_W = 1460;
private static final int PREF_H = 690;
private BufferedImage img;
private JButton startButton = new JButton("Start");
public Template2() {
setLayout(new GridBagLayout());
// TODO: .... read in your image here
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 1;
gbc.gridy = 1;
gbc.gridwidth = 1;
gbc.gridheight = 1;
gbc.insets = new Insets(5, 10, 0, 0);
gbc.anchor = GridBagConstraints.NORTHWEST;
gbc.fill = GridBagConstraints.NONE;
gbc.weightx = 1.0;
gbc.weighty = 1.0;
add(startButton, gbc);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
#Override
public void paintComponents(Graphics g) {
super.paintComponents(g);
if (img != null) {
g.drawImage(img, 0, 0, this);
}
}
private static void createAndShowGui() {
Template2 mainPanel = new Template2();
JFrame frame = new JFrame("Some Horrendous Program");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
Related
I want to show another jPanel from a button event action.
e.g.
private void jButtonMouseClicked(MouseEvent e)
{
getContentPane().removeAll();
update(getGraphics());
//code to show another jPanel containing different sub-panels
}
When I use CardLayout, I am able to use only one panel at a time, isn't there a way to add multiple panels in one frame and then after an event switch to another set of multiple panels within same frame?
Exactly, you can show only one JPanel everytime with CardLayout but that doesn't prevent you to show multiple JPanels when using it...
You need to make the card (the JPanel that is shown in the current view) to show multiple JPanels.
For example:
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.event.ActionListener;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class CardLayoutWithMultiplePanes {
private JFrame frame;
private JPanel pane;
private JPanel cardsPane;
private JPanel[] cards;
private CardLayout cl;
private JButton nextButton;
private JButton previousButton;
public static void main(String[] args) {
SwingUtilities.invokeLater(new CardLayoutWithMultiplePanes()::createAndShowGui);
}
private void createAndShowGui() {
frame = new JFrame(getClass().getSimpleName());
pane = new JPanel();
pane.setLayout(new BoxLayout(pane, BoxLayout.PAGE_AXIS));
previousButton = new JButton("Previous");
nextButton = new JButton("Next");
cl = new CardLayout();
cardsPane = new JPanel(cl);
cards = new JPanel[2];
for (int i = 0; i < cards.length; i++) {
cards[i] = new JPanel();
cards[i].setLayout(new GridLayout(2, 1));
cards[i].add(new CustomPane((i + 1) % 2 == 0 ? Color.BLUE : Color.RED));
cards[i].add(new CustomPane((i + 1) % 2 == 0 ? Color.GREEN : Color.MAGENTA));
cardsPane.add(cards[i]);
}
Box box = Box.createHorizontalBox();
box.add(previousButton);
box.add(Box.createHorizontalGlue());
box.add(nextButton);
previousButton.addActionListener(listener);
nextButton.addActionListener(listener);
pane.add(cardsPane);
pane.add(box);
frame.add(pane);
frame.pack();
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
private ActionListener listener = e -> {
if (e.getSource().equals(previousButton)) {
cl.previous(cardsPane);
} else if (e.getSource().equals(nextButton)) {
cl.next(cardsPane);
}
};
#SuppressWarnings("serial")
class CustomPane extends JPanel {
private Color color;
public CustomPane(Color color) {
this.color = color;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(color);
g.fillRect(0, 0, getWidth(), getHeight());
}
#Override
public Dimension getPreferredSize() {
return new Dimension(100, 100);
}
}
}
The above code shows a single JPanel that contains 2 more JPanels, in which each JPanel has its own background color (and might contain their own components such as JLabel or JButton, etc)
I hope this gives you an idea for what you're trying to do.
Note:
Imagine your JFrame as a notebook.
Imagine the JPanels as the sheets.
Imagine CardLayout as your finger passing pages (back and forward)
In every sheet (JPanel) you can have whatever you want (even more than 1 sheet (glued to it)), it's the same principle here
I am back again. I was wondering how I would go about placing a button on top of an image in a GUI. Here is my current code:
private static JPanel titlePanel = new JPanel();
private static JLabel titleScreen = new JLabel();
private static JLabel titleScreenBackground = new JLabel();
private static JButton startGameButton = new JButton("START GAME");
private static ImageIcon titleScreenPic = new ImageIcon("http://icdn6.digitaltrends.com/image/battleship-650x0.jpg");
private static JFrame frame=new JFrame(); //creates frame
public static void main(String[] args) throws MalformedURLException{
titleScreen();
}
public static void titleScreen() throws IOException{
titleScreen.setLayout(new GridBagLayout());
titlePanel.setLayout(new GridBagLayout());
GridBagConstraints c1 = new GridBagConstraints();
c1.gridx = 0;
c1.gridy = 0;
c1.anchor = GridBagConstraints.PAGE_END;
titleScreenBackground.setIcon(titleScreenPic);
titlePanel.add(startGameButton);
titlePanel.setAlignmentY(SwingConstants.BOTTOM);
frame.add(titleScreenBackground);
frame.add(titlePanel);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(630, 300); //sets appropriate size for frame
frame.setVisible(true); //makes frame visible
}
I tried to make the panel a gridbaglayout so I could place the components in the same cell, but it still places the image first and then the button directly next to it.
EDIT: I have redone the code, making it do somewhat what I wanted. As you can see, the line where I try to set the location of the button does not do anything to the button.
how I would go about placing a button on top of an image in a GUI.
If you want to place a Swing button on top of an image then you need to follow 2 steps.
set a layout manager for the label containing the image.
add the button to the label (not the panel).
See Background Panel for more information and examples.
Edit:
To center a component the easiest approach is:
label.setLayout( new GridBagLayout() );
label.add(button, new GridBagConstraints());
If you want button on the image you can just use image in paint method of JPanel.
Example (with resource im
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridBagLayout;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class TestFrame extends JFrame {
BackgroundPane bgPane;
private JButton startButton;
public TestFrame() {
super();
initComponents();
}
private void initComponents() {
try {
URL url = getClass().getResource("battleship-650x0.jpg");
BufferedImage image = ImageIO.read(url);
bgPane = new BackgroundPane(image);
bgPane.setLayout(new GridBagLayout());
startButton = new JButton("Start");
bgPane.add(startButton);
setContentPane(bgPane);
setDefaultCloseOperation(EXIT_ON_CLOSE);
pack();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* #param args
*/
public static void main(String[] args) {
TestFrame frame = new TestFrame();
frame.setVisible(true);
}
class BackgroundPane extends JPanel {
Image image;
public BackgroundPane(Image backGroundImage) {
super();
image = backGroundImage;
setPreferredSize(new Dimension(image.getWidth(this), image.getHeight(this)));
}
#Override
public void paint(Graphics g) {
super.paint(g);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image, 0, 0, null);
}
}
}
I want two images from two different classes that extend JPanel to be side by side.
The problem that I'm having is that the two JPanels should be inside the JFrame but when I do framename.add(panel) it replaces the other one instead of adding two of them side by side.
I have tried adding flowlayout and other layouts inside the main class, but none of the images showed up.
So my question is, if I have two classes that extend Jpanel, how do i add those two panels inside a Jframe so that they'll be side by side (next to each other) without replacing the other panel?
Any suggestions would be greatly appreciated.
Edit: If I extend JFrame to a class, does that class automatically become a JPanel itself? I know what extends means, but I'm not sure how it works in regards to a Jframe.
Main.java
import javax.swing.*;
import java.awt.*;
import java.net.URL;
public class Main
{
public static void main(String[] args)
{
JFrame frame = new JFrame();
Panel1 s = new Panel1(); //picture 1
Panel2 n = new Panel2(); //picture 2
frame.add(n);
frame.add(s); //here is the problem, it replaces the previous panel
f.setSize(200,100);
f.setLocation(0,400);
f.setVisible(true);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
Panel1.java
import javax.swing.*;
import java.awt.*;
import java.net.URL;
public class image2 extends JPanel
{
ImageIcon anotherIcon;
public image2() //constructor
{
URL imageURL = Panel1.class.getResource("images/puppy.png");
anotherIcon = new ImageIcon(imageURL);
}
public void paint(Graphics g)
{
super.paintComponent(g);
anotherIcon.paintIcon(this, g, 0, 0);
}
}
Panel2.java
import javax.swing.*;
import java.awt.*;
import java.net.URL;
public class Panel2 extends JPanel
{
ImageIcon anotherIcon2;
public Panel2() //constructor
{
URL imageURL = Panel2.class.getResource("images/puppy2.png");
anotherIcon = new ImageIcon(imageURL);
}
public void paint(Graphics g)
{
super.paintComponent(g);
anotherIcon2.paintIcon(this, g, 0, 0);
}
}
JFrame has implemented BorderLayout in the API
only one JComponent can be placed to the one of 5th. areas in BorderLayout
I want two images from two different classes that extend JPanel to be
side by side.
change built_in LayoutManager to the GridLayout
or
use JLabel with Icon in the case that there aren't any JComponent added to JPanels
The JFrame's default layout manager is BorderLayout. Try changing it to use something like GridLayout instead
Take a look at A visual guide to layout managers for more ideas
Try frame.getContentPane().add(...)?
And set the layout of the contentpane to FlowLayout (or other)
JFrame f = new JFrame("This is a test");
f.setSize(400, 150);
Container content = f.getContentPane();
content.setLayout(new FlowLayout());
content.add(new JButton("Button 1"));
content.add(new JButton("Button 2"));
f.setVisible(true);
You are adding the panels directly to the JFrame, you have to adds the panels in the contentPane.
It's also recomended create a panel to be the contentPane frame.setContentPane(myPanel);
and configure it with some layout (BorderLayout, GridBagLayout, whatever...)
In frame set FlowLayout()
frame.setLayout(new FlowLayout());
I corrected both Panel1.java and Panel2.java of minor mistakes and added an example of how you'd place them side by side using GridBagLayout/GridBagConstraints combo:
Main.java
import javax.swing.*;
import java.awt.*;
import java.net.URL;
public class Main {
public static void main(String[] args) {
GridBagConstraints gbc = new GridBagConstraints();
JFrame frame = new JFrame();
frame.setLayout(new GridBagLayout());
Panel1 s = new Panel1(); //picture 1
Panel2 n = new Panel2(); //picture 2
gbc.gridx = 0;
gbc.gridy = 0;
gbc.gridwidth = 1;
gbc.weightx = 1;
gbc.weighty = 1;
gbc.fill = GridBagConstraints.BOTH;
frame.add(n, gbc);
gbc.gridx = 1;
gbc.gridy = 0;
gbc.gridwidth = GridBagConstraints.REMAINDER;
frame.add(s, gbc); //here is the problem, it replaces the previous panel
frame.setSize(200,100);
frame.setLocation(0,400);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
Panel1.java
import javax.swing.*;
import java.awt.*;
import java.net.URL;
public class Panel1 extends JPanel {
ImageIcon anotherIcon;
public Panel1 ( ) {
URL imageURL = Panel1.class.getResource("images/puppy.png");
anotherIcon = new ImageIcon(imageURL);
}
public void paint(Graphics g) {
super.paintComponent(g);
anotherIcon.paintIcon(this, g, 0, 0);
}
}
Panel2.java
import javax.swing.*;
import java.awt.*;
import java.net.URL;
public class Panel2 extends JPanel {
ImageIcon anotherIcon;
public Panel2 ( ) {
URL imageURL = Panel2.class.getResource("images/puppy2.png");
anotherIcon = new ImageIcon(imageURL);
}
public void paint(Graphics g) {
super.paintComponent(g);
anotherIcon.paintIcon(this, g, 0, 0);
}
}
The general idea for your code was not wrong, just that the panels were placed one on top of the other, causing one to hide the other....
You can try this. Set Layout of your Frame as:
setLayout(null)
and then can add Bounds to your panel
ie. setBounds()
and then add these panels to frame
I want to have my screen split in two so I used a BorderLayout with East and West sections. I had problems resizing and here I eventually found out that width is not changed in the East and West panels and height is not changed in the North and South panels and both are changed in the Center panel.
However, I want both width and height to be changed upon resize, and have two panels side by side. I have tried various levels of nesting to try getting it to work but I do not think it will work with BorderLayout.
It seems like this should be easy for the default layout manager but maybe I should try a different layout (e.g. BoxLayout) to achieve what I want.
Also here is some code which replicates the problem I am talking about (try resizing the window):
import java.awt.BorderLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Main extends JFrame {
public static void main(String[] args) {
JFrame window = new Main();
window.setVisible(true);
}
public Main() {
JButton east = new JButton("East");
JButton west = new JButton("West");
JPanel content = new JPanel();
content.setLayout(new BorderLayout());
content.add(east, BorderLayout.EAST);
content.add(west, BorderLayout.WEST);
setContentPane(content);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
pack();
}
}
Edit: I do not want the two sides to be equal, roughly 2:1 is the ratio which I want.
What you can use in your case is GridLayout, here two JButtons will resize themselves as the JFrame resizes.
import java.awt.GridLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Main extends JFrame {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
JFrame window = new Main();
window.setVisible(true);
}
});
}
public Main() {
JButton east = new JButton("East");
JButton west = new JButton("West");
JPanel content = new JPanel();
content.setLayout(new GridLayout(1, 2));
content.add(east);
content.add(west);
setContentPane(content);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
pack();
}
}
Moreover, it's always best to run your GUI related code from the EDT - Event Dispatch Thread, and not from the Main Thread. Do read Concurrency in Swing, for more info on the topic.
LATEST EDIT : As per requested comment
Use GridBagLayout to specify the size that you want to give
import java.awt.Color;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Main extends JFrame {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
JFrame window = new Main();
window.setVisible(true);
}
});
}
public Main() {
JPanel east = new JPanel();
east.setOpaque(true);
east.setBackground(Color.WHITE);
JPanel west = new JPanel();
west.setOpaque(true);
west.setBackground(Color.BLUE);
JPanel content = new JPanel();
content.setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.anchor = GridBagConstraints.FIRST_LINE_START;
gbc.fill = GridBagConstraints.BOTH;
gbc.weightx = 0.3;
gbc.weighty = 1.0;
gbc.gridx = 0;
gbc.gridy = 0;
content.add(east, gbc);
gbc.weightx = 0.7;
gbc.gridx = 1;
content.add(west, gbc);
setContentPane(content);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
pack();
}
}
Why don't you try with JSplitPane:
import javax.swing.*;
import java.awt.*;
public class AppDemo {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame();
JButton eastButton = new JButton("East");
JButton westButton = new JButton("West");
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, eastButton, westButton);
JPanel content = new JPanel();
content.setLayout(new BorderLayout());
content.add(splitPane, BorderLayout.CENTER);
frame.setContentPane(content);
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setPreferredSize(new Dimension(500, 400));
frame.pack();
frame.setVisible(true);
});
}
}
You will get this:
If you want to keep your BorderLayout you can use something like the following object:
public class ResizablePanel extends JPanel {
public ResizablePanel(JComponent body) {
setLayout(new BorderLayout());
JButton resize = new JButton();
resize.setPreferredSize(new Dimension(Integer.MAX_VALUE, 4));
resize.addMouseMotionListener(new MouseAdapter() {
public void mouseDragged(MouseEvent e) {
Dimension preferredSize = ResizablePanel.this.getPreferredSize();
ResizablePanel.this.setPreferredSize(new Dimension(preferredSize.width, preferredSize.height-e.getY()));
ResizablePanel.this.revalidate();
}
});
add(resize, BorderLayout.PAGE_START);
add(body, BorderLayout.CENTER);
}
}
Now wrap the part you want to resize with an instance of ResizablePanel and you'll be able to resize it by dragging the thin button.
Note that this is code is for resizing the height of a panel that you put at the bottom (PAGE_END) part of a border layout, but it should be fairly straightforward to change it for resizing the width.
Sorry about replying to an old post.
My fix is to still use BorderLayout but to throw in the following line after the Component is resized
getLayout().layoutContainer(this);
I have a small problem when using JScrollPane in my Java application.
I have a JScrollPane containing a JPanel.
This JPanel is dynamically updated with buttons (vertically ordered) that can be of any width.
The JPanel automatically adjusts its width to the largest JButton component inside.
Now when the vertical scrollbar appears, it takes away some space on the right side of my JPanel, which causes the largest buttons not to appear completely. I don't want to use a horizontal scrollbar in addition to display the whole button.
Is there a way to resize my JPanel when a scrollbar appears, so it appears nicely next to my buttons? Or is there any other way to have the scrollbar appear next to my JPanel?
Thanks in advance!
EDIT: Here is a demo of my problem. When you resize the window to a smaller height, a little part of the buttons on the right side gets covered.
import java.awt.BorderLayout;
import java.awt.EventQueue;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import java.awt.GridLayout;
/**
* #author Dylan Kiss
*/
public class Demo {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
JFrame myFrame = new JFrame("Demo");
JPanel sideBar = new JPanel();
JPanel centerPanel = new JPanel();
centerPanel.add(new JLabel("This is the center panel."));
JPanel buttonContainer = new JPanel();
JButton myButton = null;
for (int i = 0; i < 20; i++) {
buttonContainer.setLayout(new GridLayout(20, 1, 0, 0));
myButton = new JButton("This is my button nr. " + i);
buttonContainer.add(myButton);
}
sideBar.setLayout(new BorderLayout(0, 0));
JScrollPane scrollPane = new JScrollPane(buttonContainer);
sideBar.add(scrollPane);
myFrame.getContentPane().add(sideBar, BorderLayout.WEST);
myFrame.getContentPane().add(centerPanel, BorderLayout.CENTER);
myFrame.setLocationByPlatform(true);
myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
myFrame.pack();
myFrame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
Here is a simple, not pretty, solution:
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
EDIT:
I thought that might not do the job in your case. Here is a better solution although it has quite a lot of boilerplate:
private class ButtonContainerHost extends JPanel implements Scrollable {
private static final long serialVersionUID = 1L;
private final JPanel buttonContainer;
public ButtonContainerHost(JPanel buttonContainer) {
super(new BorderLayout());
this.buttonContainer = buttonContainer;
add(buttonContainer);
}
#Override
public Dimension getPreferredScrollableViewportSize() {
Dimension preferredSize = buttonContainer.getPreferredSize();
if (getParent() instanceof JViewport) {
preferredSize.width += ((JScrollPane) getParent().getParent()).getVerticalScrollBar()
.getPreferredSize().width;
}
return preferredSize;
}
#Override
public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
return orientation == SwingConstants.HORIZONTAL ? Math.max(visibleRect.width * 9 / 10, 1)
: Math.max(visibleRect.height * 9 / 10, 1);
}
#Override
public boolean getScrollableTracksViewportHeight() {
if (getParent() instanceof JViewport) {
JViewport viewport = (JViewport) getParent();
return getPreferredSize().height < viewport.getHeight();
}
return false;
}
#Override
public boolean getScrollableTracksViewportWidth() {
return true;
}
#Override
public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
return orientation == SwingConstants.HORIZONTAL ? Math.max(visibleRect.width / 10, 1)
: Math.max(visibleRect.height / 10, 1);
}
}
It implements Scrollable to get full control of scrolling, does a fancy trick with tracking the viewport height to ensure the buttons expand when the space is available and adds on the width of the vertical scroll bar to the preferred width at all times. It could expand when the vertical scroll bar is visible but that looks bad anyway. Use it like this:
scrollPane = new JScrollPane(new ButtonContainerHost(buttonContainer));
It looks to me like this workaround is required because of a possible bug in javax.swing.ScrollPaneLayout:
if (canScroll && (viewSize.height > extentSize.height)) {
prefWidth += vsb.getPreferredSize().width;
}
Here extentSize is set to the preferred size of the viewport and viewSize is set to viewport.getViewSize(). This does not seem correct, AFAIK the size of the view inside the viewport should always equal the preferred size. It seems to me that the view size should be compared to the actual size of the viewport rather than its preferred size.
A simple workaround to meet your demands regarding
Is there a way to resize my JPanel when a scrollbar appears, so it
appears nicely next to my buttons?
is the use of EmptyBorder, this will let you achieve what you feel like, should happen, as shown in the image below :
I just added this line written below after this line JPanel buttonContainer = new JPanel();
ADDED LINE
buttonContainer.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
Here is your code with that added line :
import java.awt.BorderLayout;
import java.awt.EventQueue;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import java.awt.GridLayout;
/**
* #author Dylan Kiss
*/
public class Demo
{
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
#Override
public void run()
{
try
{
JFrame myFrame = new JFrame("Demo");
JPanel sideBar = new JPanel();
JPanel centerPanel = new JPanel();
centerPanel.add(new JLabel("This is the center panel."));
JPanel buttonContainer = new JPanel();
buttonContainer.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
JButton myButton = null;
for (int i = 0; i < 20; i++)
{
buttonContainer.setLayout(new GridLayout(20, 1, 0, 0));
myButton = new JButton("This is my button nr. " + i);
buttonContainer.add(myButton);
}
sideBar.setLayout(new BorderLayout(0, 0));
JScrollPane scrollPane = new JScrollPane(buttonContainer);
sideBar.add(scrollPane);
myFrame.getContentPane().add(sideBar, BorderLayout.WEST);
myFrame.getContentPane().add(centerPanel, BorderLayout.CENTER);
myFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
myFrame.pack();
myFrame.setLocationByPlatform(true);
myFrame.setVisible(true);
}
catch (Exception e)
{
e.printStackTrace();
}
}
});
}
}
You could resize the JPanel by calling setPreferredSize when the JPanel needs to be resized.
buttonContainer.setPreferredSize(Dimension d);