The Java AWT Toolkit: GridBagLayout

The Java AWT: GridBagLayout

Contents

Introduction

A graphical user interface is built up in Java by adding objects
to Containers, and using LayoutManagers
to place and size them within the containers.
There are several managers supplied in AWT:

  • BorderLayout – NSEW layout

  • FlowLayout – left-to-right with overflow

  • GridLayout – regular rectangular grid

  • GridBagLayout – general gridded layout

  • CardLayout – allows “flipping” through a set of “cards”

In the last issue of the X Advisor I discussed all of these
except for GridBagLayout. The reason is simple:
GridBagLayout is a manager that
can layout a large number of configurations in a flexible way.
This ability comes through complexity, and there is a lot to learn
before you can use this manager effectively. This article is devoted
entirely to GridBagLayout.

GridBagLayout is used to place objects in a rectangular
grid. The cells of this grid need not be the same size, and the objects
can span several cells. There is control over placement of each object
within the space allowed for it, and how it fills this space.

Debugging

The more complex the layout manager, the harder it is to get
layouts correct. Under X, I use two techniques to help me with
layouts using this manager. The first is very specifically
X Oriented: set the resource borderWidth for all
objects to some non-zero value. Then you can see exactly
how each object is placed, including objects like labels
which don’t normally have visible edges. I set this in
.Xdefaults:

*borderWidth: 3

The second technique is more generally applicable, but harder
to interpret. GridBagLayout has two
protected methods, DumpConstraints()
and DumpLayoutInfo(), intended for debugging the
manager itself. However, if you want access to this level of
information then you can subclass the manager and call these
methods yourself.
Here is a suitable subclass. Note that it must be installed
in $CLASSES/java/awt since it has to belong to package
java.awt

package java.awt;

public class DebugGridBagLayout extends GridBagLayout {

    public void dumpLayoutInfo(Container parent) {
	GridBagLayoutInfo s = GetLayoutInfo(parent, 
				GridBagLayout.PREFERREDSIZE);
	DumpLayoutInfo(s);
    }

    public void dumpConstraints(Container parent) {
	Component comp;
	GridBagConstraints constraints;

	for (int n = 0; n < parent.ncomponents; n++) {
	    comp = parent.getComponent(n);
	    constraints = lookupConstraints(comp);
	    DumpConstraints(constraints);
	}
    }
}

Not all problems with using this manager are caused by your bugs :-).
Many of the applets that run within this article fail to show
components when they begin execution. Partly for this reason, I
use components such as Button that respond to inputs
and redraw themselves on changes. If some of the applets seem to be
missing components, click over them to show the missing ones.

On my system, at least one of the applets displays correctly using
appletviewer but doesn’t show under Netscape 2.0.

GridBagConstraints

In order to layout objects within a container,
the manager needs to know some information
about them. A very simple manager like FlowLayout just
needs to know the order in which objects were added to the container
and it can get this from the container itself. A manager like
BorderLayout needs to associate objects with special
positions such as “North”, and it gets this association from the
add(String, Component) method. When the container
executes this it also calls the layout object’s
addLayoutComponent(String, Component) which allows the
layout manager to store information.

The information needed by GridBagLayout for each object
is complicated: direction of layout, number of cells spanned,
placement within this space, etc. The above methods are too simple for
that. Instead, all of this information is stored in a
GridBagConstraints object, and this is passed through
to the layout manager by

setConstraints(Component, GridBagConstraints)

The layout manager makes a copy of the GridBagConstraints
and links it to the Component using a hash table.
(This means that you only need to have one of these objects which
you can reset values of without messing up earlier references.)

Typical code using this manager is

GridBagLayout gridbag = new GridBagLayout();
setLayout(gridbag);

GridBagConstraints constraints = new GridBagConstraints();
// set values in constraints ... 

Button btn = new Button("Hello");
add(btn);

// tell the layout manager of the constraints
gridbag.setConstraints(btn, constraints);

The fields of GridBagConstraints are

public int gridx, gridy, gridwidth, gridheight;
public double weightx, weighty;
public int anchor, fill;
public Insets insets;
public int ipadx, ipady;

These may be set by an application. They are discussed in the article
in the appropriate places.

Absolute positioning

The two fields gridx and gridy may be used
to set the positions of objects, where the topleft cell is at (0, 0).
For example, to arrange four buttons at the corners of a fifth,

import java.awt.*;

public class Absolute extends Frame {

    public static void main(String argv[]) {
	new Absolute().show();
    }

    public Absolute() {
	GridBagLayout gridbag = new GridBagLayout();
	setLayout(gridbag);

	GridBagConstraints constraints = new GridBagConstraints();

	Button  btn1 = new Button("Button 1");
	add(btn1);
	// set at (0,0)
	constraints.gridx = 0;
	constraints.gridy = 0;
	gridbag.setConstraints(btn1, constraints);

	Button btn2 = new Button("Button 2");
	add(btn2);
	// set at (2,0)
	constraints.gridx = 2;
	constraints.gridy = 0;
	gridbag.setConstraints(btn2, constraints);


	Button btn3 = new Button("Button 3");
	add(btn3);
	// set at (0,2)
	constraints.gridx = 0;
	constraints.gridy = 2;
	gridbag.setConstraints(btn3, constraints);

	Button btn4 = new Button("Button 4");
	add(btn4);
	// set at (2,2)
	constraints.gridx = 2;
	constraints.gridy = 2;
	gridbag.setConstraints(btn4, constraints);

	Button btn5 = new Button("Button 5");
	add(btn5);
	// set at (1,1)
	constraints.gridx = 1;
	constraints.gridy = 1;
	gridbag.setConstraints(btn5, constraints);

	resize(300, 300);
    }
}

The program looks like

Note that problems in the AWT may result in buttons not showing until
pressed. It should look like


Cell sizes

The manager displays objects in one or more cells.
The cells may be of different sizes.
In each row the cells all have the same height, though,
and in each column the cells all have the same width.

For each row, the height is calculated by looking at the
preferredSize().height of the components in
that row. The maximum of these is found, and then the value of
ipady is added twice (for top and bottom).
The height is usually this, unless made larger by some other
constraint. Whatever,
the height of the row cannot be smaller than this.

Similarly, the width of each column is not smaller than the
maximum preferred width of each component,
plus twice ipadx.

Different rows can thus have different heights, and different columns
can have different widths. To see this, replace one of the
buttons by, say, a TextArea:

The sizing policy means that if there is nothing
in a row and ipady
is also zero, then the row has no width and does not show.
Similarly with a column with no components. In the example program
if we remove the central button, “Button 5”, then that row and
column have zero height and width respectively, and all four
buttons show up against each other.

if you want to get four buttons occupying corners with a middle space,
then you need to adjust ipadx and ipady
to leave space around each button. This would leave a border around
the outside though. This particular problem is revisited later.
REVISIT THIS.

In the JDK version 1.0, there is a limitation on the number of
components that can appear in any row or column. Each is set to
a maximum of 128. If you exceed this, then an
ArrayIndexOutOfBoundsException is raised.

Relative positioning

If you want to layout a lot of objects in a row, then having to
specify gridx for each of them may be a little tedious.
An alternative way is to use relative positioning, in which
you say how to layout a component with respect to the last one
(roughly). The fields gridx and gridy are
used for this relative placement as well as the absolute placement.

If gridx == RELATIVE then add in row order. That is,
place the next component to the right of the previous component
unless the previous component was the last in the row,
in which case this one starts a new row.

Similarly, if gridy == RELATIVE then add in column
order. That is, add below the previous component unless it was the
last in the column, in which case it is placed in a new column to the right.

If both gridx and gridy are RELATIVE
then add in row order. This is the default value for a
GridBagConstraints object.
These values can be reset for each component.
It is also possible to mix up the absolute and relative
styles of positioning together.

A simple example of relative positioning is to set a group of buttons
vertically without having to reset the GridBagConstraints
each time (this is, of course, easier with GridLayout)

import java.awt.*;

public class Vertical extends Frame {

    public static void main(String argv[]) {
	new Vertical().show();
    }

    public Vertical() {
	Button btn;
	GridBagLayout gridbag = new GridBagLayout();
	setLayout(gridbag);

	GridBagConstraints constraints = new GridBagConstraints();
	constraints.gridx = 0; // note: gridy == RELATIVE 

	for (int n = 1; n <= 8; n++) {
	    btn = new Button("Button " + n);
	    add(btn);
	    gridbag.setConstraints(btn, constraints);
	}
    }
}

which shows as

More complex arrangements can be obtained by mixing relative and
absolute positioning:

import java.awt.*;

public class Direction extends Frame { 
    Button btn1, btn2, btn3, btn4, btn5, btn6;
    GridBagLayout gridbag;   
    GridBagConstraints c;

    public static void main(String argv[])
    {
	new Direction().show();
    }

    void makeButtons() {
	btn1 = new Button("Button 1"); add(btn1);
	btn2 = new Button("Button 2"); add(btn2);
	btn3 = new Button("Button 3"); add(btn3);
	btn4 = new Button("Button 4"); add(btn4);
	btn5 = new Button("Button 5"); add(btn5);
	btn6 = new Button("Button 6"); add(btn6);
    }

    public Direction()
    {
	gridbag = new GridBagLayout();
	setLayout(gridbag);
	c = new GridBagConstraints();

	makeButtons();

	// use defaults for btn1
	gridbag.setConstraints(btn1, c);

	// btn2 below btn1
	c.gridx = 0; // gridy is still RELATIVE
	gridbag.setConstraints(btn2, c);

	// btn3 below of btn2 - reuse constraint
	gridbag.setConstraints(btn3, c);

	// btn4 right of btn2
	c.gridx = GridBagConstraints.RELATIVE;
	c.gridy = 1;
	gridbag.setConstraints(btn4, c);

	// btn5 right of btn4
	c.gridx = GridBagConstraints.RELATIVE;
	c.gridy = 2;
	gridbag.setConstraints(btn5, c);

	// btn6 down and to right of btn5
	c.gridx = 2;
	c.gridy = 3;
	gridbag.setConstraints(btn6, c);

	resize(400, 200);
    }
}

which shows as

Filling

When a component is placed in a cell, the cell is guaranteed to be
at least as large as the component plus twice the values of
ipadx and ipady.
If there are components of different sizes then some of them will
be smaller than the cell size.
A component may also be set to occupy more than one cell,
which it may not be large enough to fill.
The amount of space an object occupies within its allocated area
is controlled by the fill field of the constraints
object. The possible values are

  • HORIZONTAL – set the component’s width to the full size
    available.

  • VERTICAL – set the component’s height to the full size
    available.

  • BOTH – set the width and height to the size of the
    available space.

Anchor

There is an additional method of control over placement of an object
when its preferred size is smaller than that of the space it has to
occupy. The anchor field controls location within this.
The possible values of this are
NORTH, NORTHEAST, EAST,
SOUTHEAST, SOUTH, SOUTHWEST,
WEST, NORTHWEST and (the default)
CENTER.

The following example is quite artificial: it forces a space larger
than a normal Button by setting a long TextField
horizontally and a high TextArea vertically.
A Button is set in this because it has natural edges so you
can see what happens to its boundaries. The applet allows selection
of fill and anchor parameters and how
they affect the Button. The source contains several uses
of layout managers and is available as

FillAnchorApplet.java

(Note: setting the anchor has no effect until a change is
made to fill – another minor bug in AWT 🙁 ).

The program looks like

Padding and Insets

There is a third level of control over sizing and placement of objects
(just for variety 🙁 ). Each GridBagConstraint has fields
of ipadx, ipady and insets
an Insets object.

The insets object acts like it does in other managers.
Given a space in which to locate an object, insets specifies
a top, bottom, left and
right restriction of this space. So with a top
value of, say, 10, the top of the object must appear 10 pixels down
from the top of the space.

The ipadx and ipady fields specify internal
padding i.e. space that is added to the object’s size to find
its “real” size. With an ipadx of 20 pixels, the object
will be 40 pixels (20 for each side) wider than otherwise.

The difference can be seen by the following applet. It sets up five
buttons vertically. Buttons 1, 3 and 5 all have constraints with
ipadx = ipday = 0, and insets with all
fields zero. Button 2 sets its ipady to 20, and so is
40 pixels taller than normal. Button 4 sets insets.top = 20
and insets.bottom = 50, meaning that the
height it requires is 70 pixels taller than the space it displays in.

import java.awt.*;
import java.applet.*;

public class PaddingApplet extends Applet {

    public PaddingApplet() {
	GridBagLayout gridbag = new GridBagLayout();
	setLayout(gridbag);

	GridBagConstraints constraints = new GridBagConstraints();

	Button  btn1 = new Button("Button 1");
	add(btn1);
	constraints.gridx = 0;  // add in column order
	gridbag.setConstraints(btn1, constraints);

	Button btn2 = new Button("Button 2");
	add(btn2);
	constraints.ipady = 20;
	gridbag.setConstraints(btn2, constraints);

	Button btn3 = new Button("Button 3");
	add(btn3);
	constraints.ipady = 0; // reset to default
	gridbag.setConstraints(btn3, constraints);

	Button btn4 = new Button("Button 4");
	add(btn4);
	constraints.insets.top = 20;
	constraints.insets.bottom = 50;
	gridbag.setConstraints(btn4, constraints);

	Button btn5 = new Button("Button 5");
	add(btn5);
	constraints.insets.top = 0;  // reset to default
	constraints.insets.bottom = 0;  // reset to default
	gridbag.setConstraints(btn5, constraints);

	resize(300, 300);
    }
}

The program looks like

This mechanism allows us to solve the problem posed earlier in
“Cell sizes”: in the Absolute.java five buttons were
shown with corners touching. Remove the center one and the arrangement
collapses to the remaining four in a 2×2 grid. The middle row and
column have height and width set to zero, respectively, and don’t show.
To preserve the spacing of these four can be done by setting them in a
2×2 grid but setting insets to force them apart.

If we want to set the four objects with a fixed space
apart, then we can do that by just changing earlier programs to set
an Insets object. If we want to set the space to between
the four objects to the preferred size (or something related
to preferred or minimum sizes) then it gets a bit trickier.
This warrants a “sidebar” discussion, so here is an inline version:

Diversion: widget creation

Methods such as preferredSize() rely on the native
implementation. If the native code object has not yet been created
then such methods return “sensible” values such as
Dimension(0, 0).
The native code objects are created by peer methods
such as createButton(). These are called by a GUI
object’s method addNotify().

When a container calls layout() the native object has
already been created, so it can do meaningful geometry calculations.

If we want to find meaningful values for preferredSize()
before this, then we have to ensure that the native object has already
been created. So far, we have been relying on show()
to do this.

The Window method pack() is documented as
“packs the components of the Window”. It actually does something
far more important than that: it calls addNotify()
on itself and on all of its children. This creates the peer
objects and the native implementation. From then on, whenever a
container method add() is executed, it also calls
addNotify() on the component.

To be able to find the preferred/minimum size of an object before
show() is executed, call pack() on the
toplevel Window (or Frame), and then
add() each component. Then geometry works.
(For applets, packing has been done by the time the init()
method is called.)

End diversion

To creae a “hole” that is made from the preferred size of the
surrounding objects, first call pack() on the
Window and then add() each component.
After that it is valid to ask for preferred sizes and set this in the
Insets constraint field:

import java.awt.*;

public class AbsoluteHole extends Frame {

    public static void main(String argv[]) {
	new AbsoluteHole().show();
    }

    public AbsoluteHole() {
	// here is the pack()
	pack();
	// then add the rest
	setContents();
    }

    void setContents() {
	GridBagLayout gridbag = new GridBagLayout();
	setLayout(gridbag);

	GridBagConstraints constraints = new GridBagConstraints();

	// Button 1
	Button  btn1 = new Button("Button 1");
	add(btn1);
	constraints.gridx = 0;
	constraints.gridy = 0;

	Dimension size = btn1.preferredSize();
	constraints.insets = new Insets(0, 0, // top, left
					size.height/2,  // bottom
					size.width/2);  // right
	gridbag.setConstraints(btn1, constraints);

	// Button 2
	Button btn2 = new Button("Button 2");
	add(btn2);
	constraints.gridx = 1;
	constraints.gridy = 0;

	size = btn2.preferredSize();
	constraints.insets = new Insets(0, // top
				size.width/2, // left
				size.height/2,  //bottom
				0);
	gridbag.setConstraints(btn2, constraints);

	// Button 3
	Button btn3 = new Button("Button 3");
	add(btn3);
	constraints.gridx = 0;
	constraints.gridy = 1;

	size = btn3.preferredSize();
	constraints.insets = new Insets(size.height/2, // top
					0, 0, //left, bottom
					size.width/2); // right
					
	gridbag.setConstraints(btn3, constraints);

	// Button 4
	Button btn4 = new Button("Button 4");
	add(btn4);
	constraints.gridx = 1;
	constraints.gridy = 1;

	size = btn4.preferredSize();
	constraints.insets = new Insets(size.height/2, //top
					size.width/2, //left
					0, 0); // bottom, right
	gridbag.setConstraints(btn4, constraints);
	resize(300, 300);
    }
}

The program looks like

Spanning multiple cells

The GridBagConstraints fields gridwidth
and gridheight are used to specify how many cells
a component should span in that direction.
For example, the following applet places three buttons in a row along
the top, three down the left, with another button occupying the
remaining 2×2 space:

import java.awt.*;
import java.applet.*;

public class BigButtonApplet extends Applet { 
    Button btn1, btn2, btn3, btn4, btn5, btn6;
    GridBagLayout gridbag;   
    GridBagConstraints c;

    void makeButtons() {
	btn1 = new Button("Button 1"); add(btn1);
	btn2 = new Button("Button 2"); add(btn2);
	btn3 = new Button("Button 3"); add(btn3);
	btn4 = new Button("Button 4"); add(btn4);
	btn5 = new Button("Button 5"); add(btn5);
	btn6 = new Button("Button 6"); add(btn6);
    }

    public BigButtonApplet() {
	gridbag = new GridBagLayout();
	setLayout(gridbag);
	c = new GridBagConstraints();

	makeButtons();
	gridbag.setConstraints(btn1, c);

	c.gridx = 1;
	c.gridy = 0;
	gridbag.setConstraints(btn2, c);

	c.gridx = 2;
	c.gridy = 0;
	gridbag.setConstraints(btn3, c);

	c.gridx = 0;
	c.gridy = 1;
	gridbag.setConstraints(btn4, c);

	c.gridx = 0;
	c.gridy = 2;
	gridbag.setConstraints(btn5, c);

	c.gridx = 1;
	c.gridy = 1;
	c.gridwidth = 2;
	c.gridheight = 2;
	c.fill = GridBagConstraints.BOTH;
	gridbag.setConstraints(btn6, c);
    }
}

The program looks like

Note that Button 6 would normally be smaller than the space
it is allocated, so fill is set to BOTH to
force it to fill all of this space.

Relatively ending rows and colums

With relative placement of components, you can add components to the
right of or below the previous component. There is also a mechanism within
GridBagConstraints to allow the end of a row,
or the bottom of a column to be specified. Setting
gridWidth to REMAINDER makes this component
the last in a row, whereas setting gridHeight to
REMAINDER makes this the last in a column.

The more I use this manager, the less I use this method. It seems much
easier to use absolute positioning.

Setting gridWidth to RELATIVE makes this
component the last but one in this row. Similarly, setting
gridHeight to RELATIVE makes this component
the last but one in its column. I have never used this this.
The possibilities for specifying inconsistent geometry seem to
explode with this method!

Weight

The discussion so far has been in terms of placing components within
cells. The size of a cell is calculated as not smaller than the
biggest object that must fit inside it. When an object occupies a
space larger than its preferred size, then the fill
attribute specifies how it fills this space.

There may be external constraints that act on sizes. For example,
the container with a GridBagLayout may be managed
by another layout manager such as GridLayout that forces
the size of this container. How are these external size requests
passed to the components?

For example, in the last article we looked at aLabelledTextField,
where a Label was put to the left of a TextField.
The constraints on sizes were that the Label was kept
at a constant size (the width of the text) whereas the TextField
would stretch to fill the remaining space. This was done using a
BorderLayout manager, but should be (and can be) also
done with GridBagLayout.

The fields weightx and weighty control how
the manager resizes the components in response to external constraints.
A weight of 0.0 means no external resizing is done. This is the
default value.

In the earlier examples, the default value was used, so the externally
set size of the container was ignored. What the layout manager does
in this case is to use its own internal calculations, and then
place the group of objects in the center of its space.

If we specify an object to have a weight of more than zero in a
direction then the manager can resize the object to fill its available
space. To take the LabelledTextField example, here
is an implementation using GridBagLayout:

import java.awt.*;

public class LabelledTextField extends Panel {
    Label label;
    TextField text;

    public LabelledTextField(String l, int cols) {
	GridBagLayout gridbag = new GridBagLayout();
	setLayout(gridbag);

	GridBagConstraints constraints = new GridBagConstraints();

	label = new Label(l);
	text = new TextField(cols);
	add(label);
	add(text);

	// set resizing
	gridbag.setConstraints(label, constraints);
	constraints.weightx = 1.0;
	constraints.fill = GridBagConstraints.HORIZONTAL;
	gridbag.setConstraints(text, constraints);
   }

    public String getText() {
	return text.getText();
    }
}

Using this within a program looks like

Each component in a layout can have its own widthx
and widthy. However, when it comes to laying out the
components the cells in any row will all be the same size, and the
cells in any column will all be the same size. So GridBagLayout
needs to calculate row weights and column weights.
It finds a row weight by taking the maximum value of all the x-weights
in the row, and the column weight as the maximum of all the y-weights
for that column.

If there is more than one column and at least one of these has a non-zero
weight, then any extra space will need to be distributed. The following
example has three columns with weightx respectively of
1, 2 and 4.

import java.awt.*;

public class Weight extends Frame {

    public static void main(String argv[]) {
	new Weight().show();
    }

    public Weight() {
	GridBagLayout gridbag = new GridBagLayout();
	setLayout(gridbag);

	GridBagConstraints constraints = new GridBagConstraints();

	Button  btn1 = new Button("Button 1");
	add(btn1);
	constraints.weightx = 1;
	constraints.fill = GridBagConstraints.BOTH;
	gridbag.setConstraints(btn1, constraints);

	Button btn2 = new Button("Button 2");
	add(btn2);
	constraints.weightx = 2;
	gridbag.setConstraints(btn2, constraints);

	Button btn3 = new Button("Button 3");
	add(btn3);
	constraints.weightx = 4;
	gridbag.setConstraints(btn3, constraints);

	resize(300, 100);
    }
}

GridbagLayout distributes the extra space in
proportion to the weights. To make this more concrete,
suppose the preferred width of each button is 10 pixels, and the actual
width available is 65 pixels. Then there are 65-30 = 35 pixels spare.
The total weight of the row is 1+2+4 = 7. So Button 1 gets 1/7 of 35
i.e. 5 extra pixels to bring its width to 10+5 = 15 pixels.
Button 2 gets 2/7 of 35 to bring its width to 10+10 = 20 pixels,
and Button 3 gets 4/7 of 35 to bring its width to 10+20 = 30 pixels.

To observe this resizing behaviour, the following applet (similar to the
applets in the last article) may be used if you are running a
Java-aware browser:

Limitations and variations

We have already mentioned that if a row contains no elements then
its height is zero and nothing shows. There are other limitations
as well. One other that I have come across is in trying to set a
gridWidth or gridHeight that is too large.
For example, if only one row is specified but a request is made for
a height of two, then it will be shown with a height of only one.

Layouts may display very differently with only a minor change in code.
The following three layouts show versions that differ
in small ways.
In the first layout weightx and fill
use default values.
In the second layout weightx is set to 1.0.
In the third layout weightx is set to 1.0 and
fill to HORIZONTAL.

The variants look like

Conclusion

This article has discussed the GridBagLayout manager
in depth. The manager allows a tremendous amount of freedom to build
complex arrangements. However it is not easy to learn, and a large
amount of time will be needed to determine the best way to layout
any complex arrangement.

The next article in this series will probably be on dialogs.
Some possibilities for other articles include
designing layouts, menus, applets vs applications,
the new event model, adding native widgets,
interacting with the window manager or browser, and images.
In the previous articles
I have been choosing topics on what I found lacking in the
existing documentation,
so if you have any preferences,
or other topics that you wish
discussed let me know in the article evaluation comments. Thanks!