I'm making a GUI for a a custom source server browser with improved filtering.
This is what I have so far.
However, when I resize...
When I resize the window I want the L4D2 'filter panel' to resize to the current maximum width of the container. I also want to be able to add more of these panels in a column (such as box layout provides).
Boxlayout get's the panels to appear in a column, but it doesn't do anything for their widths.
I'm thinking I may need to override the filter panels preferred size methods so that they can retrieve the size of the parent container, but I'm not sure how to do this.
How should I approach this problem?
EDIT: Here's an example program depicting the problem.
import javax.swing.*;
import java.awt.*;
public class guiExampleProblem {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
final MyWindows wnd = new MyWindows("guiExampleProblem");
wnd.setVisible(true);
}
});
}
}
class MyWindows extends JFrame {
public MyWindows(String text) {
super(text);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
JPanel mainPanel = new JPanel();
mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
JPanel containerPanel1 = new JPanel();
JPanel containerPanel2 = new JPanel();
JPanel containerPanel3 = new JPanel();
containerPanel1.setBackground(Color.BLACK);
containerPanel2.setBackground(Color.RED);
containerPanel3.setBackground(Color.GREEN);
mainPanel.add(containerPanel1);
mainPanel.add(containerPanel2);
mainPanel.add(containerPanel3);
this.add(mainPanel);
pack();
}
}
When the window is resized, I want the panels to expand only along the x-axis, and remain at a constant height on the y-axis, however in the example the panels expand on both the x y axis.
I managed to get the desired functionality by overriding the 'filter panels' getPrefferedSize methods so that they retrieve the parent containers width and use that. Here is the code in the form of an example:
import javax.swing.*;
import java.awt.*;
public class guiExampleProblem {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
final MyWindows wnd = new MyWindows("guiExampleProblem");
wnd.setVisible(true);
}
});
}
}
class MyWindows extends JFrame {
public MyWindows(String text) {
super(text);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
JPanel mainPanel = new JPanel();
mainPanel.setLayout(new FlowLayout());
JPanel containerPanel1 = new JPanel() {
#Override
public Dimension getPreferredSize() {
return new Dimension(this.getParent().getWidth(),60);
}
};
JPanel containerPanel2 = new JPanel() {
#Override
public Dimension getPreferredSize() {
return new Dimension(this.getParent().getWidth(),60);
}
};
JPanel containerPanel3 = new JPanel() {
#Override
public Dimension getPreferredSize() {
return new Dimension(this.getParent().getWidth(),60);
}
};
containerPanel1.setBackground(Color.BLACK);
containerPanel2.setBackground(Color.RED);
containerPanel3.setBackground(Color.GREEN);
mainPanel.add(containerPanel1);
mainPanel.add(containerPanel2);
mainPanel.add(containerPanel3);
this.add(mainPanel);
pack();
}
}
Put the panel (with BoxLayout) that is to stretch in the CENTER of a BorderLayout -- put the panel to the right in the EAST of that BorderLayout. You have given no detail of what else you want this to do, nor any code, but this might be what you want.
--
After your solution: it seems to me that using FlowLayout here is confusing -- it lays out its components one after the other horizontally, and your trick of getting preferred size from the width of the container makes it behave differently. I also avoid getting into layout logic in my application when I can, so I looked for another way to do this and came up with:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
public class guiExampleProblem2
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
final MyWindows2 wnd = new MyWindows2("guiExampleProblem2");
wnd.setVisible(true);
}
});
}
}
class MyWindows2 extends JFrame
{
public MyWindows2(String text)
{
super(text);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
JPanel mainPanel = new JPanel();
mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.PAGE_AXIS));
JPanel containerPanel1 = addContainedPanel(Color.BLACK, 60, 60, mainPanel);
JPanel containerPanel2 = addContainedPanel(Color.RED, 60, 60, mainPanel);
JPanel containerPanel3 = addContainedPanel(Color.GREEN, 60, 60, mainPanel);
this.add(mainPanel, BorderLayout.NORTH);
pack();
}
JPanel addContainedPanel(Color color, int width, int height, JPanel container)
{
JPanel panel = new JPanel();
panel.setPreferredSize(new Dimension(width, height));
panel.setBackground(color);
container.add(panel);
return panel;
}
}
This uses the NORTH portion of a BorderLayout (which is the default layout for a JFrame, by the way) to do the main thing you wanted -- stretch things horizontally. The BoxLayout with a page axis is intended to lay things out top-to-bottom, so I think that's less confusing for the reader. Anyway, it's another way to do it that I think uses the components - including the layout managers - more like they were intended and documented.
Related
I haven't really worked with Swing at all in Java. I'm experimenting with it. I want to make a set layout that the size can't be changed. I've seen alot of things suggesting to use Layout managers to add multiple JPanels into a JFrame.
However, all the tutorials I've seen involving layout managers say it allows for the user to resize the screen. The layout I want has a rectangle going along the left hand side, a thin rectangle going along the bottom, and a third rectangle taking up the rest of the space. I attempting it using an Absolute layout but it just doesn't want to work for me.
import java.awt.Color;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Test extends JFrame {
public Test() {
JPanel rect1 = new JPanel();
rect1.setBounds(101, 650, 900, 50);
rect1.setBackground(Color.RED);
getContentPane().add(rect1);
JPanel rect2 = new JPanel();
rect2.setBounds(0, 650, 100, 1000);
rect2.setBackground(Color.BLUE);
getContentPane().add(rect2);
JPanel rect3 = new JPanel();
rect3.setBounds(101, 700, 900, 950);
rect3.setBackground(Color.GREEN);
getContentPane().add(rect3);
setTitle("TEST");
setSize(1000, 700);
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
Test ex = new Test();
ex.setVisible(true);
}
});
}
}
Can someone help me properly make three Jpanels in a Jframe in this layout (all with different colors)?
You might be able to achieve the same thing using a BorderLayout as the bases or even a GridBagLayout.
The main piece you are missing is the fact that layout managers use (or can use depending on the layout manager) the component's preferred/minimum/maximum size
Basically, what you would do is define a custom component (extending from something like JPanel) and override it's getPreferredSize method and return the required value you need. Depending on the layout manager, you may also need to override the getMaximumSize and getMinimumSize methods as well.
For example...
Basically, this shows the "default" size and what happens when the screen is resized...
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class FixedSizeLayout {
public static void main(String[] args) {
new FixedSizeLayout();
}
public FixedSizeLayout() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
add(new ContentPane(), gbc);
gbc.gridx++;
add(new LeftPane(), gbc);
gbc.gridwidth = 2;
gbc.gridx = 0;
gbc.gridy = 1;
add(new BottomPane(), gbc);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
public class ContentPane extends JPanel {
public ContentPane() {
setBackground(Color.GREEN);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(150, 150);
}
#Override
public Dimension getMinimumSize() {
return getPreferredSize();
}
#Override
public Dimension getMaximumSize() {
return getPreferredSize();
}
}
public class BottomPane extends JPanel {
public BottomPane() {
setBackground(Color.RED);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 50);
}
#Override
public Dimension getMinimumSize() {
return getPreferredSize();
}
#Override
public Dimension getMaximumSize() {
return getPreferredSize();
}
}
public class LeftPane extends JPanel {
public LeftPane() {
setBackground(Color.BLUE);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(50, 150);
}
#Override
public Dimension getMinimumSize() {
return getPreferredSize();
}
#Override
public Dimension getMaximumSize() {
return getPreferredSize();
}
}
}
Now, if you prefer, you can make the screen non-resizable, but I for one won't like you. I prefer to use the power of the layout managers and allow users to make decisions about how they want to view the content ... where I can ... but that's just me (I don't like non-resizable windows except in the case of some dialogs)
You can use setResizable(). Please refer to below code fragment.
setResizable(false); // this will not allow resizing
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
AbsoluteLayout is no go. Don't do that. You are probably looking for BorderLayout. Check the tutorial: How to Use BorderLayout for details. If you don't want your JFrame to be able to re-size use frame.setResizable(false); on it.
Just use simple BorderLayout and it will resize automatically:
JPanel panel = new JPanel(new BorderLayout());
panel.add(greenPanel, BorderLayout.CENTER);
panel.add(redPanel, BorderLayout.SOUTH);
panel.add(bluePanel, BorderLayout.EAST);
You can use MigLayout and replace BorderLayout.CENTER by "dock center":
JPanel panel = new JPanel(new MigLayout(" insets 0"));
panel.add(greenPanel, "dock center");
panel.add(redPanel, "dock south");
panel.add(bluePanel, "dock east");
Read more about MigLayout here: http://www.miglayout.com/QuickStart.pdf
Could anyone point out where I am going wrong with this java swing gui code. I am trying to add two buttons to a JPanel and then add it into a frame after setting the size but it seems to not be responding to the setSize values passed to it
public Test() {
GridLayout layout = new GridLayout(1, 2);
//this.setLayout(layout);
this.setSize(700, 700);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
buttonPanel = new JPanel();
buttonPanel.setSize(new Dimension(30, 100));
JButton rectButton = new JButton("Rectangle");
JButton ovalButton = new JButton("Oval");
buttonPanel.add(rectButton);
buttonPanel.add(ovalButton);
this.add(buttonPanel);
this.add(new PaintSurface());
this.setVisible(true);
}
This may not answer your immediate question...but...
GridLayout layout = new GridLayout(1, 2);
this.setLayout(layout);
// You're original code...
// Why are you using `BorderLayout.CENTER` on a `GridLayout`
this.add(new PaintSurface(), BorderLayout.CENTER);
You set the layout as a GridLayout, but you are using BorderLayout constraints to apply one of the components??
Also, make sure that there are not calls to Test#pack else where in your code, as this will override the values of setSize
UPDATED (from changes to question)
Remember, the default layout manager for JFrame is BorderLayout, so even though you're calling buttonPanel.setSize, it's likely that it's begin overridden by the layout manager anyway.
I would take a read through A Visual Guide to Layout Managers and Using Layout Managers to find a layout manager that best meets your requirements.
If you can't find a single one, consider using compound components with different layout managers to bring the layout closer to what you want to achieve.
Ok, I'll just give you a solution:
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.GridLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Cobie extends JFrame{
JButton rectButton = new JButton("Rectangle");
JButton ovalButton = new JButton("Oval");
JPanel buttonPanel = new JPanel();
JPanel paintSurface = new JPanel();
public Cobie(){
setLayout(new GridLayout(2,1));
buttonPanel.setBackground(Color.RED);
paintSurface.setBackground(Color.BLUE);
buttonPanel.add(rectButton);
buttonPanel.add(ovalButton);
add(buttonPanel);
add(paintSurface);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable(){
public void run(){
Cobie c = new Cobie();
c.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
c.setSize(600,400); //Avoid using this method
c.setVisible(true);
}
});
}
}
According to your updated answer, you are not setting your layout on anything.
Anyway, if you use LayoutManager's (which you should), it is pointless to call setSize()/setBounds()/setLocation() since it will be overriden by the LayoutManager (that is actually its job).
And guessing that your Test class extends JFrame, by calling this.add(buttonPanel); this.add(new PaintSurface()); you are adding two components with the same constraint (BorderLayout.CENTER, since BorderLayout is the default LayoutManager of the content pane of the JFrame) to the content pane.
Consider reading the LayoutManager tutorial.
Just for information, although far from perfect, this shows something "working":
import java.awt.Dimension;
import java.awt.GridLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Test extends JFrame {
private JPanel buttonPanel;
public class PaintSurface extends JButton {
public PaintSurface() {
super("Paint surface dummy");
}
}
public Test() {
GridLayout layout = new GridLayout(1, 2);
this.setLayout(layout);
this.setSize(700, 700);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
buttonPanel = new JPanel();
buttonPanel.setSize(new Dimension(30, 100));
JButton rectButton = new JButton("Rectangle");
JButton ovalButton = new JButton("Oval");
buttonPanel.add(rectButton);
buttonPanel.add(ovalButton);
this.add(buttonPanel);
this.add(new PaintSurface());
this.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Test();
}
});
}
}
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);
The below code represents the problem. Since I have heights of the north and south panels set the rest of it goes to the center panel using GridLayout. I think that since it cannot share the leftover pixels equally among its rows it just leaves them. Therefore in the below code we have ugly white line over south panel.
My question here is: How to make sure that when the GridLayout is not taking the whole space it is at least centered?
Normally I would use TableLayout and situation is sorted, but since I was writing an answer I wanted to use only standard managers. Knowing this would be very useful for me thanks in advance.
Example:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridLayout;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class AligningButonsTest
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
JFrame f = new JFrame();
f.setSize(800, 600);
double CONSTANT_FACTOR = .1;
int noOfRows = 5;
JPanel centerP = new JPanel(new GridLayout(noOfRows,1));
for(int i = 0; i < noOfRows; i++)
{
BoxPanel bP = new BoxPanel();
centerP.add(bP);
}
JPanel contentPane = new JPanel(new BorderLayout());
f.setContentPane(contentPane);
contentPane.add(centerP, BorderLayout.CENTER);
JPanel southP = new JPanel();
southP.setBackground(Color.RED.darker());//southP.setOpaque(false);
southP.setPreferredSize(new Dimension(1, (int)(CONSTANT_FACTOR* f.getHeight())));
contentPane.add(southP, BorderLayout.SOUTH);
JPanel northP = new JPanel();
northP.setBackground(Color.RED.darker());//northP.setOpaque(false);
northP.setPreferredSize(new Dimension(1, (int)(CONSTANT_FACTOR* f.getHeight())));
contentPane.add(northP, BorderLayout.NORTH);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);
}
});
}
}
class BoxPanel extends JPanel
{
public BoxPanel()
{
setBorder(BorderFactory.createMatteBorder(1, 0, 1, 0, Color.RED));
setBackground(Color.DARK_GRAY);
}
}
How to make sure that when the GridLayout is not taking the whole space it is at least centered?
JPanel wrapper = new JPanel( new GridBagLayout() );
wrapper.add( centerP );
contentPane.add(wrapper, BorderLayout.CENTER);
//contentPane.add(centerP, BorderLayout.CENTER);
BoxLayout does a pretty good job of distributing the space between components using Box.createVerticalGlue(). This example uses Box.createVerticalStrut(), top and bottom. The spacers are described in How to Use BoxLayout: Using Invisible Components as Filler.
Addendum: BoxTest2 is a variation that uses BoxLayout to create fixed-size edge panels and vertical glue to distribute the space more evenly. Box.Filler may also be used to control the "leftover" vertical space.
/** #see http://stackoverflow.com/questions/6072956 */
public class BoxTest2 {
private static final int WIDE = 480;
private static final int HIGH = WIDE / 8;
private static final int ROWS = 5;
private static final Box center = new Box(BoxLayout.Y_AXIS);
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
center.setOpaque(true);
center.setBackground(Color.lightGray);
center.add(Box.createVerticalGlue());
center.add(new EdgePanel());
for (int i = 0; i < ROWS; i++) {
center.add(new BoxPanel());
}
center.add(new EdgePanel());
center.add(Box.createVerticalGlue());
f.add(center, BorderLayout.CENTER);
f.pack();
f.setVisible(true);
}
});
}
private static class EdgePanel extends JPanel {
public EdgePanel() {
Dimension d = new Dimension(WIDE, 2 * HIGH / 3);
setPreferredSize(d);
setBackground(Color.red.darker());
}
}
private static class BoxPanel extends JPanel {
public BoxPanel() {
setPreferredSize(new Dimension(WIDE, HIGH));
setBorder(BorderFactory.createMatteBorder(1, 0, 1, 0, Color.red));
setBackground(Color.darkGray);
}
}
}
Could you try perhaps nesting this center panel in either a BorderLayout.North or maybe even a FlowLayout.Center.
By this I mean:
JPanel holder = new JPanel(new BorderLayout());
holder.add(centerP,BorderLayout.NORTH);
contentPane.add(holder, BorderLayout.CENTER);
I cannot exactly visualize your problem so it is hard to write a solution.