- Downloading and Installing JGoodies FormLayout
- FormLayout Basics
- Laying Out the Address Book GUI with FormLayout
- FormLayout Versus GridBagLayout
- Conclusion
Laying Out the Address Book GUI with FormLayout
Although you can directly create a JPanel that’s managed by FormLayout, the Forms library provides the com.jgoodies.forms.builder.PanelBuilder class for simplifying this task—it builds and returns a JPanel for you. The class methods conveniently let you specify a default border for, add labels (with mnemonics) to, and introduce a title for the panel (and so on). I took advantage of this class in a FormLayout version of the address book application—check out Listing 1.
Listing 1 AB2.java.
// AB2.java import java.awt.*; import javax.swing.*; import com.jgoodies.forms.builder.*; import com.jgoodies.forms.factories.*; import com.jgoodies.forms.layout.*; public class AB2 extends JFrame { public AB2 () { super ("Address Book"); setDefaultCloseOperation (EXIT_ON_CLOSE); getContentPane ().add (buildGUI ()); pack (); setVisible (true); } public JPanel buildGUI () { FormLayout layout = new FormLayout ("left:pref, 2dlu, pref, 2dlu, " + "pref, 2dlu, pref:grow", "pref, 3dlu, pref, 3dlu, pref, " + "3dlu, pref, 3dlu, pref, 3dlu, " + "pref, 3dlu, pref"); PanelBuilder builder = new PanelBuilder (layout); builder.setBorder (BorderFactory.createEmptyBorder (10, 10, 10, 10)); CellConstraints cc = new CellConstraints (); JLabel lblName = builder.addLabel ("&Name:", cc.xy (1, 1)); JTextField txtName = new JTextField (25); lblName.setLabelFor (txtName); builder.add (txtName, cc.xyw (3, 1, 5)); JLabel lblAddress = builder.addLabel ("&Address:", cc.xy (1, 3)); JTextField txtAddress = new JTextField (25); lblAddress.setLabelFor (txtAddress); builder.add (txtAddress, cc.xyw (3, 3, 5)); JLabel lblCity = builder.addLabel ("&City:", cc.xy (1, 5)); JTextField txtCity = new JTextField (25); lblCity.setLabelFor (txtCity); builder.add (txtCity, cc.xyw (3, 5, 5)); JLabel lblRegion = builder.addLabel ("&Region:", cc.xy (1, 7)); JTextField txtRegion = new JTextField (25); lblRegion.setLabelFor (txtRegion); builder.add (txtRegion, cc.xyw (3, 7, 5)); JLabel lblCountry = builder.addLabel ("C&ountry:", cc.xy (1, 9)); String [] countries = { "Brazil", "Ireland", "Sweden" }; JComboBox cbCountry = new JComboBox (countries); lblCountry.setLabelFor (cbCountry); builder.add (cbCountry, cc.xyw (3, 9, 5)); JLabel lblPhone = builder.addLabel ("&Phone:", cc.xy (1, 11)); JTextField txtAreaCode = new JTextField (3); JTextField txtPrefixCode = new JTextField (3); JTextField txtNumber = new JTextField (4); lblPhone.setLabelFor (txtAreaCode); builder.add (txtAreaCode, cc.xy (3, 11)); builder.add (txtPrefixCode, cc.xy (5, 11)); builder.add (txtNumber, cc.xy (7, 11, CellConstraints.LEFT, CellConstraints.DEFAULT)); JButton btnOK = new JButton ("OK"); JButton btnCancel = new JButton ("Cancel"); builder.add (ButtonBarFactory.buildOKCancelBar (btnOK, btnCancel), cc.xyw (1, 13, builder.getColumnCount ())); return builder.getPanel (); } public static void main (String [] args) { new AB2 (); } }
Listing 1 reveals my use of the following line to set the GUI border:
builder.setBorder (BorderFactory.createEmptyBorder (10, 10, 10, 10));
instead of PanelBuilder’s method:
public final void setDefaultDialogBorder()
By invoking setBorder(), the application yields a GUI that’s very similar to the GUI in Figure 1, which we created in part 2 of this series. Slightly different spacing (resulting from the use of dialog units) between components is the only real difference.
Figure 1 Design-by-GridBagLayout address book GUI.
Listing 1 also reveals the following convenient class:
com.jgoodies.forms.factories.ButtonBarFactory
This factory class consists only of static methods that are invoked to build frequently used button bars. Basically, a button bar is a panel that holds OK and Cancel buttons, and automatically sizes and lays out these buttons according to GUI standards for the operating system. (For example, placing the OK button to the left of the Cancel button is correct for Windows, but not for Linux.)
Finally, Listing 1 reveals the one disadvantage PanelBuilder has when compared to the GridBagLayout and GridBagConstraints classes: It’s a hassle to introduce new components (such as email label and text fields) into the grid because the placement of each component must be specified explicitly, which can require manual changes to many placement values. GridBagLayout avoids this hassle when GridBagConstraints.RELATIVE is used for gridx.
Fortunately, the Forms library provides this class, which is a more advanced panel builder:
com.jgoodies.forms.builder.DefaultFormBuilder
This builder combines frequently used panel-building steps:
- Add a new row.
- Add a label.
- Proceed to the next component column.
- Add a component.
This feature greatly simplifies the construction of a layout. To illustrate the convenience provided by DefaultFormBuilder, I’ve created a third version of the address book application. Listing 2 presents the source code.
Listing 2 AB3.java
// AB3.java import java.awt.*; import javax.swing.*; import com.jgoodies.forms.builder.*; import com.jgoodies.forms.debug.*; import com.jgoodies.forms.factories.*; import com.jgoodies.forms.layout.*; public class AB3 extends JFrame { public static boolean fDebugEnabled = false; public AB3 () { super ("Address Book"); setDefaultCloseOperation (EXIT_ON_CLOSE); JPanel panel = buildGUI (); getContentPane ().add (panel); pack (); if (fDebugEnabled) FormDebugUtils.dumpAll (panel); setVisible (true); } public JPanel buildGUI () { FormLayout layout = new FormLayout ("l:p, 2dlu, p, 2dlu, " + "p, 2dlu, p, 2dlu, p:g", ""); DefaultFormBuilder builder; if (fDebugEnabled) builder = new DefaultFormBuilder (layout, new FormDebugPanel ()); else builder = new DefaultFormBuilder (layout); builder.setBorder (BorderFactory.createEmptyBorder (10, 10, 10, 10)); JTextField txtName = new JTextField (25); builder.append ("&Name:", txtName, 7); JTextField txtAddress = new JTextField (25); builder.append ("&Address:", txtAddress, 7); JTextField txtCity = new JTextField (25); builder.append ("&City:", txtCity, 7); JTextField txtRegion = new JTextField (25); builder.append ("&Region:", txtRegion, 7); String [] countries = { "Brazil", "Ireland", "Sweden" }; JComboBox cbCountry = new JComboBox (countries); builder.append ("C&ountry:", cbCountry, 7); JTextField txtAreaCode = new JTextField (3); JTextField txtPrefixCode = new JTextField (3); JTextField txtNumber = new JTextField (4); builder.append ("&Phone:", txtAreaCode); builder.append (txtPrefixCode); builder.append (txtNumber); builder.nextLine (); JButton btnOK = new JButton ("OK"); JButton btnCancel = new JButton ("Cancel"); builder.append (ButtonBarFactory.buildOKCancelBar (btnOK, btnCancel), builder.getColumnCount ()); return builder.getPanel (); } public static void main (String [] args) { if (args.length != 0) fDebugEnabled = true; new AB3 (); } }
DefaultFormBuilder provides various append() methods that automatically create JLabel components and associate them with their data-entry components. There’s no longer a need to call setLabelFor(Component c) to make this association. For example, builder.append ("&Name:", txtName, 7); in Listing 2 performs a lblName.setLabelFor (txtName); and causes txtName to span seven columns, starting in column 3.
Listing 2 declaratively specifies columns via "l:p, 2dlu, p, 2dlu, p, 2dlu, p, 2dlu, p:g". In contrast, rows are added to the builder through append() method calls. When a component is added, the builder determines whether another row is necessary. If so, a new spacer and row with the appropriate preferred size are added automatically. If a row contains a yet-to-be-filled cell (such as the cell following the number text field on the phone line), the builder’s public void nextLine() method is called to move to the next row.
AB1’s dumpLayoutInfo() method provides limited debugging help by sending some useful data to standard output. Listing 2 uses more-sophisticated classes to paint grid bounds (if and only if the panel’s layout manager is FormLayout), as shown in Figure 2:
com.jgoodies.forms.debug.FormDebugPanel com.jgoodies.forms.debug.FormDebugUtils
Figure 2 FormDebugPanel highlights the layout’s row and column borders.
These classes also dump constraint values (and other data) to standard output:
COLUMN SPECS:l:p:n, f:2dluX:n, f:p:n, f:2dluX:n, f:p:n, f:2dluX:n, f:p:n, f:2dluX:n, f:p:g ROW SPECS: c:p:n, t:3dluY:n, c:p:n, t:3dluY:n, c:p:n, t:3dluY:n, c:p:n, t:3dluY:n, c:p:n, t:3dluY:n, c:p:n, t:3dluY:n, c:p:n COLUMN GROUPS: {} ROW GROUPS: {} COMPONENT CONSTRAINTS ( 1, 1, 1, 1, "d=l, d=c"); javax.swing.JLabel "Name:" ( 3, 1, 7, 1, "d=f, d=c"); javax.swing.JTextField ( 1, 3, 1, 1, "d=l, d=c"); javax.swing.JLabel "Address:" ( 3, 3, 7, 1, "d=f, d=c"); javax.swing.JTextField ( 1, 5, 1, 1, "d=l, d=c"); javax.swing.JLabel "City:" ( 3, 5, 7, 1, "d=f, d=c"); javax.swing.JTextField ( 1, 7, 1, 1, "d=l, d=c"); javax.swing.JLabel "Region:" ( 3, 7, 7, 1, "d=f, d=c"); javax.swing.JTextField ( 1, 9, 1, 1, "d=l, d=c"); javax.swing.JLabel "Country:" ( 3, 9, 7, 1, "d=f, d=c"); javax.swing.JComboBox ( 1, 11, 1, 1, "d=l, d=c"); javax.swing.JLabel "Phone:" ( 3, 11, 1, 1, "d=f, d=c"); javax.swing.JTextField ( 5, 11, 1, 1, "d=f, d=c"); javax.swing.JTextField ( 7, 11, 1, 1, "d=f, d=c"); javax.swing.JTextField ( 1, 13, 9, 1, "d=f, d=c"); javax.swing.JPanel GRID BOUNDS COLUMN ORIGINS: 10 61 65 102 106 143 147 195 199 344 ROW ORIGINS: 10 30 35 55 60 80 85 105 110 135 140 160 165 191