Swing layout

Introduction

So you know how to use a bunch of Swing widgets, and you'd like to arrange them nicely on the screen. How do you specify which widget goes where?

Swing (and the original AWT for that matter) has a concept of containers. A Container is just another widget that is able to hold more widgets inside itself. The container is responsible for laying out the positions of the components (widgets) inside it, and does so through something called a LayoutManager. The LayoutManager is actually a separate object that the Container knows about and uses to perform the actual layout. This is known as delegation, where one object delegates the responsibility for some behavior to another object.

Specifying a layout for a container

All layout managers are subclasses of LayoutManager. To specify one, you typically create a new layout manager, and pass it to the constructor of the Container you want it to manage. Which one you pick depends on how you want to lay out the containers sub-components. In many cases, you can use a simple layout manager like a BorderLayout or even a Box. Other times you'll need more control, and you can spend the time to make a GridBagLayout manager and its constraint objects to put things exactly where you want them.

Many times, you can create the effect of a complicated layout manager by using several simple layout managers embedded in other simple layout managers.

Examples, please!

// Make a JPanel that uses a BorderLayout
JPanel panel = new JPanel(new BorderLayout());

// a 2x2 grid of components
JPanel grid = new JPanel();
GridLayout layout = new GridLayout(2,2);
grid.setLayout(layout);

Adding a component to a container

Once you've got your container set up, you can add a component to it. Some layout managers, like GridLayout and Box don't need any other information other than the component to be added -- the component just goes in the next slot. Others, like BorderLayout need some information about where the component goes, or what the constraints on it are. The Container's add method lets you add the component:

public void add(Component comp);
public void add(Component comp, int index);
public void add(Component comp, Object constraints);

In the first case, the component is added to the container in the "next" spot, wherever that happens to be. In the second case, the component is inserted before the component currently at some position. Only a few layout managers pay attention to the order in which the components were added. The others require some constraint information. The constraints are different for each kind of layout manager, which is why the constraint type is left unspecified, as an Object.

More examples!

// make a new container with a 2x2 grid layout
JPanel grid = new JPanel(new GridLayout(2, 2));

// add four labels, one in each cell
grid.add(new JLabel("top left"));
grid.add(new JLabel("top right));
grid.add(new JLabel("bottom left"));
grid.add(new JLabel("bottom right"));

Sizes of things

In Swing, components describe how big they'd like to be, and then fight each other for the space. Some components, like JLabels, know exactly how big they'd like to be because of the size of their contents. Other components, like the game screen a few of you will be building, don't have innate sizes, so you'll have to specify them.

You specify the preferred size of something by saying something like this on your component (here, called comp):

comp.setPreferredSize(new Dimension(600,400));

Of course, your preferred size is likely to be different.

The preferred size of a container has to do with the kind of layout manager being used and the preferred sizes of the components. When a layout manager lays out a container, it looks at the preferred sizes of the container's components, then attempts to position the components appropriately. The container's new size is then used to layout any containers that contain it.

It is also possible to set a minimumSize and maximumSize, although many layout managers don't pay attention to them.

BorderLayout

The simplest useful layout manager is a BorderLayout. This manager can handle up to five components, as shown below.

If any of the components is missing, its space shrinks to zero width or height. So, for example, if you only put a single component in the CENTER, it will take up all of the space of the container. If you specify only CENTER and WEST, you'll get two components next to each other.

The CENTER component grows to fill any space; the WEST and EAST components keep their preferred width and expand vertically; the NORTH and SOUTH components keep their preferred height and expand horizontally.

Usage

JPanel panel = new JPanel(new BorderLayout());

panel.add(new JLabel("A topic"), BorderLayout.NORTH);
panel.add(new JButton("Quit"), BorderLayout.SOUTH);
panel.add(myBigComponent, BorderLayout.CENTER);

You can also specify the amount of space between the components in the constructor for the BorderLayout:

public BorderLayout(int hgap, int vgap);

NOTE: If you forget, and leave off the constraint, the BorderLayout assumes you meant BorderLayout.CENTER. If you attempt to add a component to a region that already has a component, the original component is dropped; only the new component will be drawn.

Box

A Box layout is unlike the other layouts because a Box is itself a Container. There is such a thing as a BoxLayout, but you typically don't use it yourself. It's a really handy way of getting a bunch of components lined up.

A Box forces all its components into a single line, either horizontally or vertically. As you add components, they are placed in the line.

A Box introduces two kinds of fake component. Neither renders anything on the screen -- they both just take up space.

The first is called a strut. You can create a strut by saying something like:

Box.createHorizontalStrut(10);

A strut is an invisible component with some forced exact dimension, in this case, 10 pixels wide. You use struts to separate other components by a certain distance.

The second special kind of Box component is called glue. Glue expands to fill whatever space remains after the other components have been placed at their preferred size. If there are two or more instances of glue in a single Box, then the extra space is divided evenly between them.

Creating the pictured layout

Box box = Box.createHorizontalBox();

box.add(new JLabel("   A   ");
box.add(Box.createHorizontalGlue());
box.add(new JLabel("     B     ");
box.add(Box.createHorizontalGlue());
box.add(new JLabel("   C   ");
box.add(Box.createHorizontalStrut(20));

GridLayout

A GridLayout specifies a grid of cells. Each cell is exactly the same size througout the grid, and is just large enough to hold any component in the grid.

In the example above, each cell is wide enough to hold the last piece on the first row (the widest piece) and is tall enough to hold the middle piece on the top row (the tallest piece). The remaining components are each centered within their cell. The faint dotted lines are there just to illustrate the cell size and positions, and do not appear when the GridLayout is actually drawn.

The number of rows and columns are specified in the constructor for a GridLayout, with the number of rows first. If you know that you'd like three columns, but don't know how many components will be in the container (and thus don't know how many rows to make), you can specify 0 as the number of rows in the constructor, and the grid will grow by rows as needed. You can do the same thing for columns as well.

Using a GridLayout

// make a 3 column, n row, grid, with 5 pixels
// between columns and 6 pixels between rows
GridLayout layout = new GridLayout(0, 3, 5, 6);
JPanel grid = new JPanel(layout);

// fill in 4 rows of the grid.
for (int i=0; i<12; i++) {
    grid.add(new JLabel("#"+i));
}

GridBagLayout

Conceptually, a GridBagLayout is just like a GridLayout except that the rows and columns are each only as tall or wide as they need to be, and components can span multiple rows or columns in the grid.

A GridBagLayout gives you immense control over how each piece looks, through a helper class called GridBagConstraints. The GridBagConstraints contains information about:

When you add a component to a container that uses a GridBagLayout, you must specify a GridBagConstraints object that describes how that component should look and where it should go in the grid. Fortunately, the GridBagLayout makes a copy of the GridBagConstraints, so you can reuse the constraints object and changes some of its values between calls to add.

This sounds complicated. Got an example?

// make a contaner that uses a GridBagLayout
JPanel grid = new JPanel(new GridBagLayout());

// make a GridBagConstraints
GridBagConstraints gbc = new GridBagConstraints();

// add components (assuming we've already made c1, etc)
grid.add(c1, gbc);
grid.add(c2, gbc);

// the third component is at the end of the row.
gbc.gridwidth = GridBagConstraints.REMAINDER;
grid.add(c3, gbc);

// first item on second row spans two columns
gbc.gridwidth = 2.0;
grid.add(c4, gbc);

// last item
gbc.gridwidth = GridBagConstraints.REMAINDER;
// make the last row and last column get bigger if
// the container grows
gbc.weightx = gbc.weighty = 1.0;
grid.add(c5, gbc);

Scrolling

It's so easy to make something scroll that many people find it confusing to use, believing that there must be more to it.

Just how easy is it?

// create a list of items.
String[] data = {"one", "two", "three", "four",
                 "five", "six", "seven", "eight",
                 "nine", "ten"};
JList list = new JList(data);

// give the list some scrollbars.
// the horizontal (bottom) scrollbar will only appear
// when the screen is too wide.  The vertical
// scrollbar is always present, but disabled if the
// list is small.
JScrollPane jsp = new JScrollPane(list,
        JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
        JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);

// add the JScrollPane (not the list) to the window.
jframe.getContentPane().add(jsp, BorderLayout.CENTER);

That's it! A JScrollPane can hold exactly one component. If you really want to put more than one thing into a JScrollPane, then put them instead into a JPanel using some layout manager, and then put the JPanel into the scroll pane.

Tabs

A JTabbedPane takes care of drawing the tabs and switching pages for you. All you need to do is to create the pages themselves. Like a JScrollPane, each page must be a single Component but that component can be a JPanel or a Box that contains other components.

To add a component to a JTabbedPane, you call one of the addTab methods:

public void addTab(String title, Component component);
public void addTab(String title, Icon icon, Component component);
public void addTab(String title, Icon icon, Component component,
                   String tooltip);

Each method requires a title for the tab and the Component that will become the page for the tab. The extra methods also allow you to specify an icon for the tab and/or a tooltip for the tab.

Once you've added the tabs, you can specify them by index, starting with 0. There are getSelectedTab and setSelectedTab methods among many others. See the JTabbedPane Javadoc page for all of the available methods.

If you want to find out when someone has changed tabs, you'll have to add a listener to the JTabbedPane's model:

Listening for tab changes

JTabbedPane tabs = new JTabbedPane();

// add the tabs
tabs.addTab("First", firstcomponent);
tabs.addTab("Second", secondcomponent);
tabs.addTab("Third", thirdcomponent);

tabs.getModel().addChangeListener(new ChangeListener() {
   public void stateChanged(ChangeEvent evt) {
      // the source of the event is the
      // SingleSelectionModel used by the JTabbedPane.
      SingleSelectionModel model =
         (SingleSelectionModel)evt.getSource();
         
      int selectedIndex = model.getSelectedIndex();
      // now, go do something with that index
      myHandleTabChange(selectedIndex);
   }
});