Bruce Eckel's Thinking in Java Contents | Prev | Next

Controlling layout

The way that you place components on a form in Java is probably different from any other GUI system you’ve used. First, it’s all code; there are no “resources” that control placement of components. Second, the way components are placed on a form is controlled by a “layout manager” that decides how the components lie based on the order that you add( ) them. The size, shape, and placement of components will be remarkably different from one layout manager to another. In addition, the layout managers adapt to the dimensions of your applet or application window, so if that window dimension is changed (for example, in the HTML page’s applet specification) the size, shape, and placement of the components could change.

Both the Applet and Frame classes are derived from Container, whose job it is to contain and display Components. (The Container is a Component so it can also react to events.) In Container, there’s a method called setLayout( ) that allows you to choose a different layout manager.

In this section we’ll explore the various layout managers by placing buttons in them (since that’s the simplest thing to do). There won’t be any capturing of button events since this is just intended to show how the buttons are laid out.

FlowLayout

So far, all the applets that have been created seem to have laid out their components using some mysterious internal logic. That’s because the applet uses a default layout scheme: the FlowLayout. This simply “flows” the components onto the form, from left to right until the top space is full, then moves down a row and continues flowing the components.

Here’s an example that explicitly (redundantly) sets the layout manager in an applet to FlowLayout and then places buttons on the form. You’ll notice that with FlowLayout the components take on their “natural” size. A Button, for example, will be the size of its string.

//: FlowLayout1.java
// Demonstrating the FlowLayout
import java.awt.*;
import java.applet.*;

public class FlowLayout1 extends Applet {
  public void init() {
    setLayout(new FlowLayout());
    for(int i = 0; i < 20; i++)
      add(new Button("Button " + i));
  }
} ///:~ 

All components will be compacted to their smallest size in a FlowLayout, so you might get a little bit of surprising behavior. For example, a label will be the size of its string, so right-justifying it yields an unchanged display.

BorderLayout

This layout manager has the concept of four border regions and a center area. When you add something to a panel that’s using a BorderLayout you must use an add( ) method that takes a String object as its first argument, and that string must specify (with proper capitalization) “North” (top), “South” (bottom), “East” (right), “West” (left), or “Center.” If you misspell or mis-capitalize, you won’t get a compile-time error, but the applet simply won’t do what you expect. Fortunately, as you will see shortly, there’s a much-improved approach in Java 1.1.

Here’s a simple example:

//: BorderLayout1.java
// Demonstrating the BorderLayout
import java.awt.*;
import java.applet.*;

public class BorderLayout1 extends Applet {
  public void init() {
    int i = 0;
    setLayout(new BorderLayout());
    add("North", new Button("Button " + i++));
    add("South", new Button("Button " + i++));
    add("East", new Button("Button " + i++));
    add("West", new Button("Button " + i++));
    add("Center", new Button("Button " + i++));
  }
} ///:~ 

For every placement but “Center,” the element that you add is compressed to fit in the smallest amount of space along one dimension while it is stretched to the maximum along the other dimension. “Center,” however, spreads out along both dimensions to occupy the middle.

The BorderLayout is the default layout manager for applications and dialogs.

GridLayout

A GridLayout allows you to build a table of components, and as you add them they are placed left-to-right and top-to-bottom in the grid. In the constructor you specify the number of rows and columns that you need and these are laid out in equal proportions.

//: GridLayout1.java
// Demonstrating the FlowLayout
import java.awt.*;
import java.applet.*;

public class GridLayout1 extends Applet {
  public void init() {
    setLayout(new GridLayout(7,3));
    for(int i = 0; i < 20; i++)
      add(new Button("Button " + i));
  }
} ///:~ 

In this case there are 21 slots but only 20 buttons. The last slot is left empty; no “balancing” goes on with a GridLayout.

CardLayout

The CardLayout allows you to create the rough equivalent of a “tabbed dialog,” which in more sophisticated environments has actual file-folder tabs running across one edge, and all you have to do is press a tab to bring forward a different dialog. Not so in the AWT: The CardLayout is simply a blank space and you’re responsible for bringing forward new cards. (The JFC/Swing library contains tabbed panes that look much better and take care of all the details for you.)

Combining layouts

This example will combine more than one layout type, which seems rather difficult at first since only one layout manager can be operating for an applet or application. This is true, but if you create more Panel objects, each one of those Panels can have its own layout manager and then be integrated into the applet or application as simply another component, using the applet or application’s layout manager. This gives you much greater flexibility as seen in the following example:

//: CardLayout1.java
// Demonstrating the CardLayout
import java.awt.*;
import java.applet.Applet;

class ButtonPanel extends Panel {
  ButtonPanel(String id) {
    setLayout(new BorderLayout());
    add("Center", new Button(id));
  }
}

public class CardLayout1 extends Applet {
  Button
    first = new Button("First"),
    second = new Button("Second"),
    third = new Button("Third");
  Panel cards = new Panel();
  CardLayout cl = new CardLayout();
  public void init() {
    setLayout(new BorderLayout());
    Panel p = new Panel();
    p.setLayout(new FlowLayout());
    p.add(first);
    p.add(second);
    p.add(third);
    add("North", p);
    cards.setLayout(cl);
    cards.add("First card", 
      new ButtonPanel("The first one"));
    cards.add("Second card", 
      new ButtonPanel("The second one"));
    cards.add("Third card", 
      new ButtonPanel("The third one"));
    add("Center", cards);
  }
  public boolean action(Event evt, Object arg) {
    if (evt.target.equals(first)) {
      cl.first(cards);
    } 
    else if (evt.target.equals(second)) {
      cl.first(cards);
      cl.next(cards);
    } 
    else if (evt.target.equals(third)) {
      cl.last(cards);
    } 
    else 
      return super.action(evt, arg);
    return true;
  }
} ///:~ 

This example begins by creating a new kind of Panel: a ButtonPanel. This contains a single button, placed at the center of a BorderLayout, which means that it will expand to fill the entire panel. The label on the button will let you know which panel you’re on in the CardLayout.

In the applet, both the Panel cards where the cards will live and the layout manager cl for the CardLayout must be members of the class because you need to have access to those handles when you want to manipulate the cards.

The applet is changed to use a BorderLayout instead of its default FlowLayout, a Panel is created to hold three buttons (using a FlowLayout), and this panel is placed at the “North” end of the applet. The cards panel is added to the “Center” of the applet, effectively occupying the rest of the real estate.

When you add the ButtonPanels (or whatever other components you want) to the panel of cards, the add( ) method’s first argument is not “North,” “South,” etc. Instead, it’s a string that describes the card. Although this string doesn’t show up anywhere on the card, you can use it if you want to flip that card using the string. This approach is not used in action( ); instead the first( ), next( ), and last( ) methods are used. Check your documentation for the other approach.

In Java, the use of some sort of “tabbed panel” mechanism is quite important because (as you’ll see later) in applet programming the use of pop-up dialogs is heavily discouraged. For Java 1.0 applets, the CardLayout is the only viable way for the applet to have a number of different forms that “pop up” on command.

GridBagLayout

Some time ago, it was believed that all the stars, planets, the sun, and the moon revolved around the earth. It seemed intuitive from observation. But then astronomers became more sophisticated and started tracking the motion of individual objects, some of which seemed at times to go backward in their paths. Since it was known that everything revolved around the earth, those astronomers spent large amounts of time coming up with equations and theories to explain the motion of the stellar objects.

When trying to work with GridBagLayout, you can consider yourself the analog of one of those early astronomers. The basic precept (decreed, interestingly enough, by the designers at “Sun”) is that everything should be done in code. The Copernican revolution (again dripping with irony, the discovery that the planets in the solar system revolve around the sun) is the use of resources to determine the layout and make the programmer’s job easy. Until these are added to Java, you’re stuck (to continue the metaphor) in the Spanish Inquisition of GridBagLayout and GridBagConstraints.

My recommendation is to avoid GridBagLayout. Instead, use the other layout managers and especially the technique of combining several panels using different layout managers within a single program. Your applets won’t look that different; at least not enough to justify the trouble that GridBagLayout entails. For my part, it’s just too painful to come up with an example for this (and I wouldn’t want to encourage this kind of library design). Instead, I’ll refer you to Core Java by Cornell & Horstmann (2 nd ed., Prentice-Hall, 1997) to get started.

There’s another light on the horizon: in the JFC/Swing library there is a new layout manager that uses Smalltalk’s popular “Springs and Struts,” and this could significantly reduce the need for GridBagLayout.

Contents | Prev | Next