I'm using GridBagLayout to create a panel consisting of 4 sub-panels. The 4 sub-panels are colored magenta, blue, red, and cyan, while the master panel is green. The goal is to arrange these 4 sub-panels in a certain fashion and theoretically, no green should show. Fortunately, I have a working example of exactly what I want to achieve:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Toolkit;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Main {
public static void main(String[] args) {
Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
int width = d.width;
int height = 1046;
JFrame frame = new JFrame("4.5 test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel masterPanel = new JPanel(new GridBagLayout());
masterPanel.setBackground(Color.GREEN);
GridBagConstraints c = new GridBagConstraints();
c.weightx = 1.0;
c.weighty = 1.0;
// 1
JPanel leftUpper = new JPanel();
leftUpper.setBackground(Color.MAGENTA);
leftUpper.setPreferredSize(new Dimension(width/3, height/7));
c.gridx = 0;
c.gridy = 0;
c.anchor = GridBagConstraints.NORTH;
masterPanel.add(leftUpper, c);
// 2
JPanel leftLower = new JPanel();
leftLower.setBackground(Color.BLUE);
leftLower.setPreferredSize(new Dimension(width/3, height - height/7));
c.gridheight = 2;
c.anchor = GridBagConstraints.SOUTH;
masterPanel.add(leftLower, c);
// 3
JPanel rightUpper = new JPanel();
rightUpper.setBackground(Color.RED);
rightUpper.setPreferredSize(new Dimension(width - width/3, height - height/5));
c.gridx = 1;
c.gridheight = 1;
masterPanel.add(rightUpper, c);
// 4
JPanel rightLower = new JPanel();
rightLower.setBackground(Color.CYAN);
rightLower.setPreferredSize(new Dimension(width - width/3, height/5));
c.gridy = 1;
masterPanel.add(rightLower, c);
frame.add(masterPanel);
frame.pack();
frame.setVisible(true);
}
}
When run, this gives this correct result. In fact, every height under 1046 works as intended.
However, the moment one changes Line 16 to this:
int height = 1047;
All goes to hell and we get this instead. This holds true for every height above 1047 as well. The panels are in the right positions, but are far from the right sizes. Note that unlike height, changing the width has no (unintended) effect.
I wouldn't be so surprised by this behavior if it wasn't caused by such a small (arbitrary?) limit in size. For context, I am using the 1.8 Java RunTime environment. My computer screen resolution is 1920x1080. Why is this happening, how can one remedy this, and if this is an atrocious practice, what detailed alternative can you provide?
When a GridBagLayout cannot accommodate all child components’ preferred sizes, it “gives up” and sets every single child component to its minimum size. So, one solution is to set each component’s minimum size to its preferred size.
However, rather than trying to mathematically account for every pixel on your screen, you have more robust options.
One solution is to only set the size of one panel, in any particular dimension, so the other one always takes up the remaining space. This way, you can never “overflow” the available space. BorderLayout is well suited to this, since its center component is stretched to take up all available space that the side components don’t use.
So, for your magenta and blue panels, you would set the height of the magenta panel only:
JPanel leftPanel = new JPanel(new BorderLayout());
// 1
JPanel leftUpper = new JPanel();
leftUpper.setBackground(Color.MAGENTA);
leftUpper.setPreferredSize(new Dimension(1, height/7));
leftPanel.add(leftUpper, BorderLayout.NORTH);
// 2
JPanel leftLower = new JPanel();
leftLower.setBackground(Color.BLUE);
leftPanel.add(leftLower, BorderLayout.CENTER);
And similarly for the red and cyan ones:
JPanel rightPanel = new JPanel(new BorderLayout());
// 3
JPanel rightUpper = new JPanel();
rightUpper.setBackground(Color.RED);
rightPanel.add(rightUpper, BorderLayout.CENTER);
// 4
JPanel rightLower = new JPanel();
rightLower.setBackground(Color.CYAN);
rightLower.setPreferredSize(new Dimension(1, height/5));
rightPanel.add(rightLower, BorderLayout.SOUTH);
And then you do something similar to those two panels themselves, to put them together:
JPanel masterPanel = new JPanel(new BorderLayout());
masterPanel.setBackground(Color.GREEN);
leftPanel.setPreferredSize(new Dimension(width / 3,
leftPanel.getPreferredSize().height));
masterPanel.add(leftPanel, BorderLayout.WEST);
masterPanel.add(rightPanel, BorderLayout.CENTER);
And since reporting of the borders given to windows by the system’s window manager is unreliable, you avoid doing any explicit math and just maximize the window:
frame.add(masterPanel);
frame.pack();
frame.setExtendedState(Frame.MAXIMIZED_BOTH);
frame.setVisible(true);
If you absolutely need to maintain the ratio among the panels (for instance, magenta_height : blue_height :: 1 : 6), you can do that with a SpringLayout, but SpringLayout is more complicated and usually isn’t worth the trouble. It allows you to specify size ranges which can be multiples of other size ranges:
SpringLayout layout = new SpringLayout();
JPanel masterPanel = new JPanel(layout);
masterPanel.setBackground(Color.GREEN);
// 1
JPanel leftUpper = new JPanel();
leftUpper.setBackground(Color.MAGENTA);
masterPanel.add(leftUpper);
// 2
JPanel leftLower = new JPanel();
leftLower.setBackground(Color.BLUE);
masterPanel.add(leftLower);
// 3
JPanel rightUpper = new JPanel();
rightUpper.setBackground(Color.RED);
masterPanel.add(rightUpper);
// 4
JPanel rightLower = new JPanel();
rightLower.setBackground(Color.CYAN);
masterPanel.add(rightLower);
Spring leftUpperHeight = Spring.height(leftUpper);
Spring leftLowerHeight = Spring.scale(leftUpperHeight, 6);
Spring rightLowerHeight = Spring.scale(leftUpperHeight, 7 / 5f);
Spring leftWidth = Spring.width(leftUpper);
Spring rightWidth = Spring.scale(leftWidth, 3);
layout.getConstraints(leftLower).setHeight(leftLowerHeight);
layout.getConstraints(rightLower).setHeight(rightLowerHeight);
layout.getConstraints(rightUpper).setWidth(rightWidth);
layout.getConstraints(rightLower).setWidth(rightWidth);
// Place leftLower beneath leftUpper.
layout.putConstraint(
SpringLayout.NORTH, leftLower, 0,
SpringLayout.SOUTH, leftUpper);
// Make leftLower's width match leftUpper's width.
layout.putConstraint(
SpringLayout.WEST, leftLower, 0,
SpringLayout.WEST, leftUpper);
layout.putConstraint(
SpringLayout.EAST, leftLower, 0,
SpringLayout.EAST, leftUpper);
// Make container high enough to hold both leftLower and leftUpper.
layout.putConstraint(
SpringLayout.SOUTH, masterPanel, 0,
SpringLayout.SOUTH, leftLower);
// Place rightUpper and rightLower to the right of leftUpper.
layout.putConstraint(
SpringLayout.WEST, rightLower, 0,
SpringLayout.EAST, leftUpper);
layout.putConstraint(
SpringLayout.WEST, rightUpper, 0,
SpringLayout.WEST, rightLower);
// Make container wide enough to accommodate rightUpper and rightLower.
layout.putConstraint(
SpringLayout.EAST, masterPanel, 0,
SpringLayout.EAST, rightLower);
// Align bottom of rightLower with bottom of leftLower.
layout.putConstraint(
SpringLayout.SOUTH, rightLower, 0,
SpringLayout.SOUTH, leftLower);
// Place rightUpper above rightLower.
layout.putConstraint(
SpringLayout.SOUTH, rightUpper, 0,
SpringLayout.NORTH, rightLower);
// Stretch rightUpper to reach to the top of the container.
layout.putConstraint(
SpringLayout.NORTH, rightUpper, 0,
SpringLayout.NORTH, masterPanel);