Laying Out Realistic GUIs the GridBagLayout Way
- GridBagLayout Demonstration Application 2: Address Book
- Whats Next?
In part 1 of this series, I introduced you to GridBagLayout’s classes. I also demonstrated a number of simple constraints combinations with a GBLDemo application (available in the source.zip for this article). In this article, we’ll get down to the nitty-gritty of building sensible layouts with GridBagLayout.
I developed and tested the Java applications for this series using J2SE 5.0 (via Sun’s SDK command-line tools) and version 1.0.7 of the JGoodies Forms library (which contains the FormLayout layout manager, its panel builder, and more). Windows 98 SE was the underlying platform.
GridBagLayout Demonstration Application 2: Address Book
Although the GBLDemo application’s GridBagLayout-based layout code is helpful in understanding how a GUI’s layout is affected when you apply different combinations of constraints, it’s also important to observe this layout manager’s effectiveness when it comes to laying out a realistic GUI. To that end, I’ve created an address book application. This application uses GridBagLayout to lay out a more realistic GUI, shown in Figure 1.
Figure 1. Design-by-GridBagLayout address book GUI.
The address book’s GUI consists of the following:
- Name, Address, City, Region, Country, and Phone labels
- Text fields for entering the name, address, city, and region
- Combo box for selecting the country
- Text fields for entering the area code, prefix, and telephone number
- OK and Cancel buttons
An invisible "glue" component is also present. As shown in Figure 2, this GUI stretches horizontally across and remains at the top of its window when enlarged.
Figure 2. The GUI stretches horizontally and appears near the window’s top because of nonzero weights attached to the final column and final row.
A glue component has no minimum or preferred size, so it takes up no space unless excess space is available. Because GridBagLayout puts any extra space between its grid of cells and the edges of the container (if all weights are zero), and because the glue component occupies a cell, the glue component must be weighted vertically if the GUI is to appear near the window’s top when the GUI is made larger. This is described in the AB1.java source code shown in Listing 1—the first version of the address book application.
Listing 1 AB1.java.
// AB1.java import java.awt.*; import javax.swing.*; public class AB1 extends JFrame { GridBagLayout gbl; public AB1 () { super ("Address Book"); setDefaultCloseOperation (EXIT_ON_CLOSE); getContentPane ().add (buildGUI ()); pack (); dumpLayoutInfo (); setVisible (true); } public JPanel buildGUI () { JPanel panel = new JPanel (gbl = new GridBagLayout ()); panel.setBorder (BorderFactory.createEmptyBorder (5, 5, 10, 10)); GridBagConstraints gbc = new GridBagConstraints (); // Set shared constraints for the name, address, city, region, country, // and phone area code/prefix code/number label, text, and combo box // fields. // The insets constraint provides a five-pixel insets separator between // each label and its text or combo box field, and between a label plus // its text/combo box field and the previous label plus its text/combo // box field. gbc.insets = new Insets (5, 5, 0, 0); // The anchor constraint left-aligns all labels in their leftmost // column. Furthermore, this constraint ensures that the phone’s number // field is positioned at the start of its display area. If this wasn’t // done, the number field would be centered within its lengthy display // area, putting it far to the right of the area code and prefix code // fields. gbc.anchor = GridBagConstraints.LINE_START; // For each of the name, address, city, region, country, and phone area // code/prefix code/number, install appropriate label, text, and combo // box fields. JLabel lblName = new JLabel ("Name:"); lblName.setDisplayedMnemonic (’N’); JTextField txtName = new JTextField (25); lblName.setLabelFor (txtName); gbc.gridy = 0; panel.add (lblName, gbc); gbc.gridwidth = GridBagConstraints.REMAINDER; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1.0; panel.add (txtName, gbc); gbc.gridwidth = 1; gbc.fill = GridBagConstraints.NONE; gbc.weightx = 0.0; JLabel lblAddress = new JLabel ("Address:"); lblAddress.setDisplayedMnemonic (’A’); JTextField txtAddress = new JTextField (25); lblAddress.setLabelFor (txtAddress); gbc.gridy = 1; panel.add (lblAddress, gbc); gbc.gridwidth = GridBagConstraints.REMAINDER; gbc.fill = GridBagConstraints.HORIZONTAL; panel.add (txtAddress, gbc); gbc.gridwidth = 1; gbc.fill = GridBagConstraints.NONE; JLabel lblCity = new JLabel ("City:"); lblCity.setDisplayedMnemonic (’C’); JTextField txtCity = new JTextField (25); lblCity.setLabelFor (txtCity); gbc.gridy = 2; panel.add (lblCity, gbc); gbc.gridwidth = GridBagConstraints.REMAINDER; gbc.fill = GridBagConstraints.HORIZONTAL; panel.add (txtCity, gbc); gbc.gridwidth = 1; gbc.fill = GridBagConstraints.NONE; JLabel lblRegion = new JLabel ("Region:"); lblRegion.setDisplayedMnemonic (’R’); JTextField txtRegion = new JTextField (25); lblRegion.setLabelFor (txtRegion); gbc.gridy = 3; panel.add (lblRegion, gbc); gbc.gridwidth = GridBagConstraints.REMAINDER; gbc.fill = GridBagConstraints.HORIZONTAL; panel.add (txtRegion, gbc); gbc.gridwidth = 1; gbc.fill = GridBagConstraints.NONE; JLabel lblCountry = new JLabel ("Country:"); lblCountry.setDisplayedMnemonic (’o’); String [] countries = { "Brazil", "Ireland", "Sweden" }; JComboBox cbCountry = new JComboBox (countries); lblCountry.setLabelFor (cbCountry); gbc.gridy = 4; panel.add (lblCountry, gbc); gbc.gridwidth = GridBagConstraints.REMAINDER; gbc.fill = GridBagConstraints.HORIZONTAL; panel.add (cbCountry, gbc); gbc.gridwidth = 1; gbc.fill = GridBagConstraints.NONE; JLabel lblPhone = new JLabel ("Phone:"); lblPhone.setDisplayedMnemonic (’P’); JTextField txtAreaCode = new JTextField (3); JTextField txtPrefixCode = new JTextField (3); JTextField txtNumber = new JTextField (4); lblPhone.setLabelFor (txtAreaCode); gbc.gridy = 5; panel.add (lblPhone, gbc); panel.add (txtAreaCode, gbc); panel.add (txtPrefixCode, gbc); panel.add (txtNumber, gbc); // Install the OK and Cancel buttons panel -- it’s easier to treat the // two buttons as a single component. JButton btnOK = new JButton ("OK"); Dimension size = btnOK.getPreferredSize (); size.width = 100; btnOK.setPreferredSize (size); JButton btnCancel = new JButton ("Cancel"); size = btnCancel.getPreferredSize (); size.width = 100; btnCancel.setPreferredSize (size); JPanel panel2 = new JPanel (new GridBagLayout ()); GridBagConstraints gbc2 = new GridBagConstraints (); gbc2.insets = new Insets (0, 5, 0, 0); panel2.add (btnOK, gbc2); panel2.add (btnCancel, gbc2); gbc.gridy = 6; gbc.gridwidth = GridBagConstraints.REMAINDER; gbc.anchor = GridBagConstraints.LINE_END; panel.add (panel2, gbc); // Install a weighted glue row to take up all vertical extra space, so // that this program’s GUI appears near the top of its main window when // the window is enlarged vertically. panel.add (Box.createGlue (), new GridBagConstraints (0, 7, 1, 1, 0.0, 1.0, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets (0, 0, 0, 0), 0, 0)); return panel; } public void dumpLayoutInfo () { System.out.println ("Layout Dimensions"); System.out.println (); int [][] x = gbl.getLayoutDimensions (); if (x == null || x.length < 2) { System.err.println ("Unable to obtain layout dimensions"); return; } System.out.println (" Column Widths"); System.out.println (); System.out.print (" "); for (int i = 0; i < x [0].length; i++) System.out.print (x [0][i] + " "); System.out.println (); System.out.println (); System.out.println (" Row Heights"); System.out.println (); System.out.print (" "); for (int i = 0; i < x [1].length; i++) System.out.print (x [1][i] + " "); System.out.println (); System.out.println (); System.out.println ("Layout Weights"); System.out.println (); double [][] w = gbl.getLayoutWeights (); if (w == null || w.length < 2) { System.err.println ("Unable to obtain layout weights"); return; } System.out.println (" Horizontal Weights"); System.out.println (); System.out.print (" "); for (int i = 0; i < w [0].length; i++) System.out.print (w [0][i] + " "); System.out.println (); System.out.println (); System.out.println (" Vertical Weights"); System.out.println (); System.out.print (" "); for (int i = 0; i < w [1].length; i++) System.out.print (w [1][i] + " "); } public static void main (String [] args) { new AB1 (); } }
To simplify interacting with the GUI, and to make the GUI more accessible to assistive technologies, Listing 1 invokes the following javax.swing.JLabel methods for each of the Name:, Address:, City:, Region:, Country:, and Phone: labels:
public void setDisplayedMnemonic(char aChar) public void setLabelFor(Component c)
When the user activates the mnemonic, the associated component receives the input focus.
GridBagLayout provides two methods for returning arrays of layout dimensions (column widths and row heights) and layout weights (horizontally and vertically), respectively:
public int[][] getLayoutDimensions() public double[][] getLayoutWeights()
These methods are invoked by the public void dumpLayoutInfo() method in Listing 1, which dumps the arrays’ contents to the standard output device. This output is helpful in understanding the layout of the address book’s GUI:
Layout Dimensions Column Widths 56 42 42 200 Row Heights 25 25 25 25 30 25 31 0 Layout Weights Horizontal Weights 0.0 0.0 0.0 1.0 Vertical Weights 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0
The output reveals four interesting layout facts:
- The number of column widths or horizontal weights indicates four layout columns.
- Eight rows are revealed by counting the row heights or vertical weights.
- The final column width (200) indicates a lengthy display area for the phone’s number field—this field would appear far to the right of the prefix code field (but centered in its display area) if I hadn’t set its anchor constraint to LINE_START.
- Only the final column and row are weighted—they get all of the extra space when the GUI is made larger.