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.
//: 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.