Recent top five:
Java.next -- Four languages that represent the future of Java
Blogger Stuart Halloway has begun a series of posts on trends that point to the future of the Java platform. In his first
post, he compares Clojure, Groovy, JRuby, and Scala -- four wildly different languages that nonetheless all play together
in the JRE. Find out what unites these languages and what they can tell us about the future of Java-based development ...
| Enterprise AJAX - Transcend the Hype |
| Memory Analysis in Eclipse |
| Oracle Compatibility Developer's Guide |
| Memory Analysis in Eclipse |
GridBagLayout class, whose numerous GridBagConstraints settings cause your code to grow quickly. However, the GridLayout layout manager seems more appropriate. It can help you write short and readable code while laying out components in equally
sized cells. In this tip, I extend GridLayout to create cells of unequal size.Note: If you use a JTable to display information in a matrix, then check out "Java Tip 116: Set Your Table Options -- at Runtime!" Sonal Goyal with John D. Mitchell.
The GridLayout2 extension is fairly simple. The GridLayout2 class inherits from java.awt.GridLayout and then redefines the public methods preferredLayoutSize(), minimumLayoutSize(), and layoutContainer() to account for multisized grid cells. I started from the source code of those methods in the java.awt.GridLayout class. (Download the complete source code in Resources.)
preferredLayoutSize() method: public Dimension preferredLayoutSize(Container parent) {
synchronized (parent.getTreeLock()) {
Insets insets = parent.getInsets();
int ncomponents = parent.getComponentCount();
int nrows = getRows();
int ncols = getColumns();
if (nrows > 0) {
ncols = (ncomponents + nrows - 1) / nrows;
}
else {
nrows = (ncomponents + ncols - 1) / ncols;
}
int[] w = new int[ncols];
int[] h = new int[nrows];
for (int i = 0; i < ncomponents; i ++) {
int r = i / ncols;
int c = i % ncols;
Component comp = parent.getComponent(i);
Dimension d = comp.getPreferredSize();
if (w[c] < d.width) {
w[c] = d.width;
}
if (h[r] < d.height) {
h[r] = d.height;
}
}
int nw = 0;
for (int j = 0; j < ncols; j ++) {
nw += w[j];
}
int nh = 0;
for (int i = 0; i < nrows; i ++) {
nh += h[i];
}
return new Dimension(insets.left + insets.right +
nw + (ncols-1) * getHgap(),
insets.top + insets.bottom +
nh + (nrows-1)*getVgap());
}
}
As you can see, the code is pretty straightforward. You first ensure that you have the right number of rows and columns to lay out the components. Then you find each component's preferred size. Finally, you compute each row's height as the row components' maximum height. You compute each column width as the maximum width of the row components. The preferred layout size also accounts for the horizontal and vertical gap sizes between components and the parent container insets.
minimumLayoutSize() is basically the same as preferredLayoutSize(), except you use the subcomponents' minimum size dimensions.layoutContainer() method that follows accounts for each subcomponent's preferred size. The code also scales the component according to the
parent container size, horizontal and vertical gap size, and insets: public void layoutContainer(Container parent) {
synchronized (parent.getTreeLock()) {
Insets insets = parent.getInsets();
int ncomponents = parent.getComponentCount();
int nrows = getRows();
int ncols = getColumns();
if (ncomponents == 0) {
return;
}
if (nrows > 0) {
ncols = (ncomponents + nrows - 1) / nrows;
}
else {
nrows = (ncomponents + ncols - 1) / ncols;
}
int hgap = getHgap();
int vgap = getVgap();
// scaling factors
Dimension pd = preferredLayoutSize(parent);
double sw = (1.0 * parent.getWidth()) / pd.width;
double sh = (1.0 * parent.getHeight()) / pd.height;
// scale
int[] w = new int[ncols];
int[] h = new int[nrows];
for (int i = 0; i < ncomponents; i ++) {
int r = i / ncols;
int c = i % ncols;
Component comp = parent.getComponent(i);
Dimension d = comp.getPreferredSize();
d.width = (int) (sw * d.width);
d.height = (int) (sh * d.height);
if (w[c] < d.width) {
w[c] = d.width;
}
if (h[r] < d.height) {
h[r] = d.height;
}
}
for (int c = 0, x = insets.left; c < ncols; c ++) {
for (int r = 0, y = insets.top; r < nrows; r ++) {
int i = r * ncols + c;
if (i < ncomponents) {
parent.getComponent(i).setBounds(x, y, w[c], h[r]);
}
y += h[r] + vgap;
}
x += w[c] + hgap;
}
}
}
Again, you first ensure you have the right number of rows and columns to lay out the components. Then you compute the scaling
factors on x and y coordinates as the ratios between the parent container's current height and width and the height and width of its preferred
layout size. You find each component's preferred size and scale it using the scaling factors. You also compute each row's
height as the maximum height of the scaled components in the row. You compute each column's width as the maximum width of
the scaled components in the row. Finally, you lay out the components, considering the horizontal and vertical gap sizes between
components and the parent container insets.
You could also scale the horizontal and vertical gap sizes. I will leave that as an exercise for the reader.
Figures 1, 2, and 3 show a random arrangement of 14 multisized JLabels using the three grid layouts: GridLayout, GridBagLayout, and GridLayout2. Figure 3 shows the desired view.

Figure 1. Arrange 14 multisized JLabels with GridLayout layout manager

Figure 2. Arrange 14 multisized JLabels with GridBagLayout layout manager

Figure 3. Arrange the same 14 labels with GridLayout2 layout manager
As you can see, GridLayout sizes all matrix cells equally; GridBagLayout allows for each column/row cell to have its own width/height; and GridLayout2 looks the same as GridBagLayout without the extra baggage and hassle.
Figures 4, 5, and 6 show a sample password panel using the same three grid layouts: GridLayout, GridBagLayout, and GridLayout2. As the above exercise, Figures 5 and 6 show the desired view: a compact change-password panel that doesn't consume as much
screen space as Figure 4.

Figure 4. Sample change-password panel using GridLayout layout manager

Figure 5. Sample change-password panel using GridBagLayout layout manager

Figure 6. Same panel using GridLayout2 layout manager
These screenshots run the simple Test class shown below. Since GridLayout2 is a GridLayout subclass, you can use the same code to construct the samples for either layout (the Test class for GridBagLayout will follow):
public Test(GridLayout layout, JComponent[] component) {
super(layout.getClass().getName());
JPanel panel = new JPanel(layout);
//--- code needed to add the components
// less than using a java.awt.GridBagLayout
for (int i = 0; i < component.length; i ++) {
panel.add(component[i]);
}
//---
panel.setBorder(new EtchedBorder());
setContentPane(panel);
pack();
show();
}
The Test class extends the JFrame class, so you don't need to do anything special to create the window frame -- just make sure the constructor invokes super() appropriately. You then set the frame's content pane to be a JPanel instance, and the given layout to be the panel's layout manager. Then you add a component array to the panel by invoking
the add() method for each component (see the loop iteration). Pretty simple. Finally, you pack() and show() the window.
Note that the test programs don't actually perform any actions. The test programs only show how the layout managers behave when the containers (whose layouts they manage) are resized.
On the other hand, GridBagLayout requires a separate routine to construct it. GridBagLayout requires more (convoluted) code to specify the constraints applied when laying out the components for the desired behavior:
public Test(int columns, Insets insets, JComponent[] component) {
super(GridBagLayout.class.getName());
GridBagLayout layout = new GridBagLayout();
JPanel panel = new JPanel(layout);
//--- code needed to add the components
GridBagConstraints constraints = new GridBagConstraints();
constraints.anchor = GridBagConstraints.WEST;
constraints.insets = insets;
constraints.fill = GridBagConstraints.BOTH;
for (int i = 0; i < component.length; i ++) {
constraints.gridx = i % columns;
constraints.gridy = i / columns;
layout.setConstraints(component[i], constraints);
panel.add(component[i]);
}
//---
panel.setBorder(new EtchedBorder());
setContentPane(panel);
pack();
show();
}
Here, you use a GridBagConstraints instance to create the desired layout. To simply lay out the components like in the previous example, use the constraints
instance's gridx and gridy fields to specify each component's location in the grid layout. You can easily observe that using a java.awt.GridBagLayout manager requires writing more code; for example, the loop iteration contains three more lines. Also, as you'll see below,
without more complex and convoluted constraint management, the GridBagLayout doesn't always behave as you would like when faced with functions like resizing windows.
Now, let's resize Figures 1, 2, and 3 from the first exercise. (You can try this yourself by running the Test program in Resources.) Figures 7, 8, and 9 show the screenshots from my system.

Figure 7. Resize 14 multisized JLabels with a GridLayout layout manager

Figure 8. Resize 14 multisized JLabels with a GridBagLayout layout manager

Figure 9. Resize 14 multisized labels using a GridLayout2 layout manager
In resizing the first test batch, you can see that the GridLayout2 code in Figure 9 remains the only one that displays the desired view. I will let you resize the figures in the compact panel
exercise. Note particularly the GridBagLayout example's poor behavior when you resize the window to something smaller than its desired size.
In this tip, I showed how you can extend the standard GridLayout class with a few lines of code to obtain a more flexible grid layout manager. This lets you easily write less code with more
predictable behavior for matrix-style layouts.