I often notice that people get really confused with the way java lays out components. Experience also shows that people struggle with what the sizing methods on JComponents really do. More than once I've seen code that does this kind of thing:
JComponent c = ...;
Dimension d = new Dimension(...);
c.setMinimumSize(d);
c.setMaximumSize(d);
c.setPreferredSize(d);
c.setSize(d);
Clearly that's insane, so I thought I might try to make people aware of a few little tricks to help size your components perfectly.
Here's a couple of points to remember:
- The preferred size of a JComponent always tells you how big it wants to be. Mostly that size is sensible and it should be used.
- If you set a preferred size of a component it is permanently altered. Stop and think before you do this. If you alter the preferred size of a combo box that has an item "Cat" (because you think it was too tall) like this:
Dimension d = combo.getPreferredSize();
d.height = 6;
combo.setPreferredSize(d);
Then when someone adds an element "Catamaran" you'll find your preferred width is now too small.
- Know your layout manager! A LayoutManager will size a component in whichever way it feels is correct. If a LayoutManager ignores minimum size then don't get angry with your custom component because you set a minimum size and it's being ignored.
- Don't be afraid to nest panels. Using layout gaps, borders and glue can get you quite far but for even the most basic setup of components nesting panels with individual layouts will take you to the finish line.
Here's a simple example illustrating a couple of those points.

JPanel p = new JPanel(new BorderLayout());
JScrollPane scroller = new JScrollPane(VERTICAL_SCROLLBAR_NEVER, HORIZONTAL_SCROLLBAR_ALWAYS);
scroller.setBorder(null);
scroller.setViewportView(new JTextArea("My little scrolling thing "));
p.add(scroller);
This is a panel containing a scrollpane (without its border cause it mucks up my example later on). If I want to add a widget of some sort next to the scrollbar then it'll adversely effect my size.

This was done by moving the scroll bar from out of its JScrollPane and into a custom panel next to another component.
JPanel scrollBarPanel = new JPanel(new BorderLayout());
JComboBox combo = new JComboBox(new String[] { "Some options" });
scrollBarPanel.add(combo, BorderLayout.WEST);
scrollBarPanel.add(scroller.getHorizontalScrollBar(), BorderLayout.CENTER);
p.add(scrollBarPanel, BorderLayout.SOUTH);
Notice in the code that I'm adding the combo to the west side of a border layout. This means that I know that the combo will always get its preferred size and therefore look correct. The scroll bar on the other hand ends up in the center of the BorderLayout which means it gets all of the rest of the space.
Problem here is that the combo's preferred size is taller than the scroll bars and that makes the scroll bar look ugly!

Since I want to retain the correct width, my solution is mostly correct, but I just want to adjust the height of the combo, but retain its relative calculation of its preferred size (which means I don't want to just set its preferred size).
The solution I'm getting at here is that you should know your layout manager and not bother tweaking your components. The BorderLayout will have its minimum height set to the largest preferred height. That means that all we need to do it ensure that the largest preferred height in the panel is the scroll bar and not the combo.

The result, pictured above, was produced by embedding the combo into a panel with its preferred size calculation always clamping the height to 0. By that I mean literally creating a class called ZeroHeightPanel that extends JPanel and override a single method as follows:
@Override
public Dimension getPreferredSize() {
final Dimension d = super.getPreferredSize();
d.height = 0;
return d;
}
This will now let your layout manager get preferred sizes of all the components it's laying out without having had to alter any settings on any of the components and without having to override any methods on the components. (Obviously the behaviour of the panel is altered, but my point is that if you get your JComponent from elsewhere in the API you won't be able to override it, but rather only embed it into something that you have control over.) Note that we are now laying out components following the
composition over inheritance described in Item 14 of
Josh Bloch's Effective Java. I believe that the rule can easily be applied to how UIs are laid out since we can easily compose a panel with altered sizing behaviour.
I'm not saying that this is how you should handle all of your sizing issues, but I just want to point out that there are other solutions that aren't necessarily obvious. If you know your layout managers and know what rules they apply to laying components out then you should be able to produce your desired behaviour without giving yourself a headache.
As a last point, if a layout manager doesn't do what you want it to do, don't be afraid of writing one. They are easy to write, but make sure that you document what rules it's using to size its component(s).
Questions and critique are welcome.
Labels: java, jcomponent, layoutmanager, swing