GridBagLayout Example: A Simple Form Layout

The java.awt.GridBagLayout layout manager is remarkably flexible but intermittently infuriating. This note illustrates a simple utility class intended to hide the details of using GridBagLayout for a common GUI development task, laying out a simple data entry form.

Most of the standard layout managers included in the JDK (e.g., BorderLayout, FlowLayout, and GridLayout) allow you to implicitly or explicitly specify the relative position of each added component, where each position has well-defined sizing semantics. In a GridBagLayout, positions are broadly determined by the order in which components are added (a bit like FlowLayout or GridLayout), but the details of each added component’s size are controlled by a GridBagConstraints object associated with each component.

GridBagConstraints has no fewer than 11 separate fields that describe how the layout manager is permitted to position, stretch, and align each component. Learning these details is undoubtedly a worthwhile endeavor. For some tasks, however, it may be simpler to just wrap up all of the details for manipulating and setting GridBagConstraints in a simple utility class.

One such task is the layout of basic forms for data entry. By “basic form”, I mean components that:

Address form window screenshot

Figure 1. A simple address form GUI.

Figure 1 illustrates a form that matches these criteria. The labels for “Name”, “Address”, “City”, and “Phone” are aligned. When the window is stretched, the input fields for name, address, and city stretch. The labels do not stretch, nor do the input fields for state, zip, or phone. This layout can be thought of as consisting of three kinds of components:


  1. Labels are fixed width components that occur in columns, typically the first column. All labels in a given column will have the same width, determined by the preferred width of the widest label in the column. These will typically be text (e.g., in a JLabel), but need not be.
  2. Last fields take up the remainder of the space on a row. Their width is determined by the width of the form and the width of any other elements that precede them on the row. If the form can be resized, the last field in each row will absorb the size change if every other component on the row has a fixed width. Every row must have a last field.
  3. Middle fields are variable-width fields that are not at the end of the row. Like labels, variable width fields occur in columns, with all fields in the same column having the same width. These are used in cases where the last component on a given row needs to have a fixed width.

These are my terms — GridBagLayout and GridBagConstraints know nothing of the semantics of a form layout, but each of the three kinds of components can be represented by a particular configuration of a GridBagConstraints object. In Figure 2, the FormUtility class shows how we can hide these configuration details in a utility class that provides methods for adding each kind of component to a Container that is using a GridBagLayout.



/**
 * Simple utility class for creating forms that have a column
 * of labels and a column of fields. All of the labels have the
 * same width, determined by the width of the widest label
 * component.
 * <P>
 * Philip Isenhour - 060628 - http://javatechniques.com/
 */
public class FormUtility {
    /**
     * Grid bag constraints for fields and labels
     */
    private GridBagConstraints lastConstraints = null;
    private GridBagConstraints middleConstraints = null;
    private GridBagConstraints labelConstraints = null;

    public FormUtility() {
        // Set up the constraints for the "last" field in each 
        // row first, then copy and modify those constraints.

        // weightx is 1.0 for fields, 0.0 for labels
        // gridwidth is REMAINDER for fields, 1 for labels
        lastConstraints = new GridBagConstraints();

        // Stretch components horizontally (but not vertically)
        lastConstraints.fill = GridBagConstraints.HORIZONTAL;

        // Components that are too short or narrow for their space
        // Should be pinned to the northwest (upper left) corner
        lastConstraints.anchor = GridBagConstraints.NORTHWEST;

        // Give the "last" component as much space as possible
        lastConstraints.weightx = 1.0;

        // Give the "last" component the remainder of the row
        lastConstraints.gridwidth = GridBagConstraints.REMAINDER;

        // Add a little padding
        lastConstraints.insets = new Insets(1, 1, 1, 1);

        // Now for the "middle" field components
        middleConstraints = 
             (GridBagConstraints) lastConstraints.clone();

        // These still get as much space as possible, but do
        // not close out a row
        middleConstraints.gridwidth = GridBagConstraints.RELATIVE;

        // And finally the "label" constrains, typically to be
        // used for the first component on each row
        labelConstraints = 
            (GridBagConstraints) lastConstraints.clone();

        // Give these as little space as necessary
        labelConstraints.weightx = 0.0;
        labelConstraints.gridwidth = 1;
    }

    /**
     * Adds a field component. Any component may be used. The 
     * component will be stretched to take the remainder of 
     * the current row.
     */
    public void addLastField(Component c, Container parent) {
        GridBagLayout gbl = (GridBagLayout) parent.getLayout();
        gbl.setConstraints(c, lastConstraints);
        parent.add(c);
    }
    
    /**
     * Adds an arbitrary label component, starting a new row
     * if appropriate. The width of the component will be set
     * to the minimum width of the widest component on the
     * form.
     */
    public void addLabel(Component c, Container parent) {
        GridBagLayout gbl = (GridBagLayout) parent.getLayout();
        gbl.setConstraints(c, labelConstraints);
        parent.add(c);
    }

    /**
     * Adds a JLabel with the given string to the label column
     */
    public JLabel addLabel(String s, Container parent) {
        JLabel c = new JLabel(s);
        addLabel(c, parent);
        return c;
    }

    /**
     * Adds a "middle" field component. Any component may be 
     * used. The component will be stretched to take all of
     * the space between the label and the "last" field. All
     * "middle" fields in the layout will be the same width.
     */
    public void addMiddleField(Component c, Container parent) {
        GridBagLayout gbl = (GridBagLayout) parent.getLayout();
        gbl.setConstraints(c, middleConstraints);
        parent.add(c);
    }
    
}



Figure 2. The FormUtility class, a simple wrapper class that hides some of the details of configuring and applying GridBagConstraints objects.

It would certainly be possible to define other commonly found kinds of components in a form, and add GridBagConstaints and add methods for those additional kinds to FormUtility. These three (labels, last fields, and middle fields) are, however, sufficient to handle at least of skeleton of a wide range of form layouts. It would also be useful to extend FormUtility to allow for configuration of the padding around each component (by changing the arguments to the Insets constructor used to initialize the insets variable) or change the alignment (or font, etc…) of the constructed JLabels.

In Figure 3, FormUtilityDemo shows how the FormUtility class can be used to produce the form GUI shown in Figure 1.



/**
 * Simple application for demonstrating the use of FormUtility
 * to hide the details of creating a form layout with
 * GridBagLayout.
 * <P>
 * Philip Isenhour - 060628 - http://javatechniques.com/
 */
public class FormUtilityDemo {

    public static void main(String[] args) {
        JFrame f = new JFrame("FormUtility Demo");

        // Make a panel to hold the demo "form", then
        // add it to the top of the frame's content pane
        JPanel form = new JPanel();
        f.getContentPane().setLayout(new BorderLayout());
        f.getContentPane().add(form, BorderLayout.NORTH);

        // Set the form panel's layout to GridBagLayout
        // and create a FormUtility to add things to it.
        form.setLayout(new GridBagLayout());
        FormUtility formUtility = new FormUtility();

        // Add some sample fields
        formUtility.addLabel("Name: ", form);
        formUtility.addLastField(new JTextField(), form);

        formUtility.addLabel("Address: ", form);
        formUtility.addLastField(new JTextField(), form);

        // Using a blank label to indent an (unlabelled)
        // field. Without the blank label, the field would
        // take the entire width of the form
        formUtility.addLabel("", form);
        formUtility.addLastField(new JTextField(), form);

        // A more complex "field", with multiple
        // components. We want the city field to stretch
        // with the form, while the state and zip input
        // fields stay the same width. Hence we add
        // the city input field as a "middle" field.
        formUtility.addLabel("City: ", form);
        formUtility.addMiddleField(new JTextField(), form);

        // Put the state and zip labels and fields
        // in their own panel, each added as a "label"
        // in a FormUtility-managed GridBagLayout. This
        // has the effect of giving each piece only as
        // much as is needed. When placed in the "last"
        // field position in the main form, these will
        // have a fixed width and the city field can
        // stretch.
        JPanel stateZip = new JPanel();
        stateZip.setLayout(new GridBagLayout());
        formUtility.addLabel(" State: ", stateZip);
        JTextField state = new JTextField();
        Dimension stateSize = state.getPreferredSize();
        stateSize.width = 30;
        state.setPreferredSize(stateSize);
        formUtility.addLabel(state, stateZip);
        formUtility.addLabel(" Zip: ", stateZip);
        JTextField zip = new JTextField();
        Dimension zipSize = zip.getPreferredSize();
        zipSize.width = 80;
        zip.setPreferredSize(zipSize);
        formUtility.addLabel(zip, stateZip);

        // The panel containing the state and the zip
        // gets added as another (fixed width) label field.
        // This is less than ideal, but will suffice for
        // this example. A blank "last field" component
        // gets us to a new line.
        formUtility.addLabel(stateZip, form);
        formUtility.addLastField(new JPanel(), form);

        // And finally, an input field that shouldn't stretch to the end
        formUtility.addLabel("Phone: ", form);
        JTextField phone = new JTextField();
        Dimension phoneSize = phone.getPreferredSize();
        phoneSize.width = 200;
        phone.setPreferredSize(phoneSize);
        JPanel phonePanel = new JPanel();
        phonePanel.setLayout(new BorderLayout());
        phonePanel.add(phone, BorderLayout.WEST);
        formUtility.addLastField(phonePanel, form);

        // Add an little padding around the form
        form.setBorder(new EmptyBorder(2, 2, 2, 2));

        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // Note that we don't use pack() here, since that
        // may shrink the "last" column more than we want.
        f.setSize(400, 400);
        f.setVisible(true);
    }

}



Figure 3. The FormUtilityDemo class, illustrating the use of FormUtility to lay out a simple form GUI.

FormUtilityDemo still relies on nested panels with different layout managers, but moving the GridBagConstaints details out of the main layout logic yields more readable code. A few caveats:


  1. All “middle fields” in this simple example will be the same width. Hence if we added another field using the addMiddleField method, it would have the same width as the city input field.
  2. By default, GridBagLayout will cluster components in the center of the container. In FormUtilityDemo, I’ve added the form to the frame’s content panel in the BorderLayout‘s NORTH position. This will push the form to the top of the frame and give it only as much vertical space as it requires, but allow it to stretch to the full width of the window.
  3. The state and zip subpanel also uses GridBagLayout, with each of the four contained components being added as a “label”. This forces the layout manager to give each of them only as much horizontal space as the component’s preferred size specifies. A number of other layout managers could have been used to achieve a similar effect.


Categories



Pages

Meta


Copyright 2003-2007 - Philip Isenhour