August, 1997
Just when this column was due, the UPS person dropped off a new O'Reilly
book. Or rather, the second edition of David Flanagan's Java in a
Nutshell. When I taught beginning Java classes, the Nutshell book
was my favorite reference and was given to all students. The second
edition covers the new features of the 1.1 JDK in the level of detail
one expects from a Nutshell book - terse, but useful.
I didn't have time to read the entire book, but I did read through
chapter 4, which explains the new features of 1.1. I discovered that I
was wrong in my February column - the number of classes more than
doubled in 1.1. I might not have counted, but David did. I also learned
about reflection, a technique that permits a Java application to examine
its own or other classes' methods, arguments, return types, interfaces,
and other parts of a class. I had considered writing a Java program that
would display this information by reading the class, because I thought
it would be useful. Now, a 1.1 package, java.lang.reflect ,
makes this trivial, and David provides a nice example application.
As in the first edition, Java in a Nutshell is not designed as a
standalone tutorial. I like it because it is a great memory aid to one
who is getting on in years and likely to forget anything. I also like
references that provide lots of examples.
Layout Managers
One thing I didn't find in David's book was any discussion of layout
managers. Layout managers handle the placement of components within a
Frame or Panel (or any Container). You can handle this yourself, of
course, by specifying the move() method (a deprecated 1.0
method), or the 1.1 setLocation() method. After creating an
instance of a Button, ScrollBar, or TextField, you can position it
absolutely within the container, remembering that the origin is in the
upper left corner.
The downsides to absolute positioning are plain. If you have a GUI
building tool, layout is not so difficult. Without the tool, graph paper
is in order for determining the upper left corner of each component, and
its size. The other problem concerns what should happen if the user
(can't forget those pesky users) decides to resize the window or chooses
a height and width in the applet tag that you didn't plan on?
One approach is simply to coerce the container to a single size and
shape. When the user drags the window frame, you capture the event and
call resize() to reset the Frame to what you, the
programmer, wants. Or call resize() within the applet's
init() method for panels within Web pages. These techniques
solve the problem of resizing simply by being inflexible.
The other solution is to use one of the five layout managers provided
with the JDK, or to write one of your own. Most people choose to use one
of the existing layout managers: Flow, Border, Grid, Card, and GridBag.
Using layout managers exchanges control over the absolute positioning of
components for ease in layout and flexibility.
FlowLayout
FlowLayout is the simplest to use and understand. You add a layout
manager to the container you are using. Then, as you add()
components, the layout manager handles positioning them. In the case of
the FlowLayout, each component is added until the components fill up one
row of the container, then the next row is started. You can fill from
the left, center, or right. Resizing the container can reduce or
increase the number of rows, and the number of components per row.
I wrote a simple application with several variations which allows you to
play around with layout managers. In most of these, I simply create five
Button objects, using the names of the numbers one through five as
labels, with makeButtons() . Let's look at Flow.
import java.awt.*;
public class Flow extends Frame {
public Flow(String name) {
super(name);
}
public static void main(String args[]) {
Flow h = new Flow("FlowLayout test.");
Button [] b = new Button[5];
h.makeButtons(b);
h.setLayout(new FlowLayout());
for (int i = 0; i < 5; i++) h.add(b[i]);
h.resize(200, 100);
h.show();
}
private void makeButtons(Button[] b) {
String [] name = { "One", "Two", "Three",
"Four", "Five"};
for (int i = 0; i < b.length; i++)
b[i] = new Button(name[i]);
}
}
The Flow class is quite simple. The main() method ignores
any arguments and creates a Frame with "FlowLayout test" at the top of
the window. The makeButtons() method returns a array of
Buttons, and the setLayout() method sets the layout manager
for the Frame (the container). Then each Button is added one at a time,
the Frame resized, then displayed.
When you run this application, a small window appears, with the first
four Buttons in one row, and Button number Five in the second row by
itself. Selecting the Button will produce the standard Button behavior,
making the borders of the Button invert, producing the appearance of
depressing the Button. What is more relevant is to try resizing the
window. If you stretch the window to the right, all the Buttons can fit
on the first row. If you make the window tall and narrow, each Button
has its own row. It is also possible to shrink the window so that only
several Buttons appear, still centered.
Several things are happening here. Resizing the window results in the
container querying the layout manager for the new position of each of
its components. Each component must also be consulted, so that it's
preferred size (getPreferredSize() in 1.1) can be
determined. Note that the size of the label determines the width of the
Buttons (One is narrower than Five), and the font used determines the
height. The layout manager, FlowLayout in this example, uses the
preferred sizes and the size of the container to position each of the
components - if all will still fit.
Border Layout
The BorderLayout manager uses a different scheme for arranging
components. There can only be five components, and they may take one of
five positions: North, South, East, West, and Center. In these cases,
the components are scaled to make the best use of the positions allotted
to them. The border positions are long and narrow, and the largest space
is given over to the Center. Here is a code fragment from a short
application (found on the Web server), for testing the BorderLayout.
public static void main(String args[]) {
Border h = new Border("BorderLayout test.");
Button [] b = new Button[5];
h.makeButtons(b);
h.setLayout(new BorderLayout());
String [] borders = { "North", "East",
"South", "West", "Center"};
for (int i = 0; i < 5; i++)
h.add(borders[i], b[i]);
h.resize(300, 200);
h.show();
h.show();
}
Components are added using the name of the border for positioning. You
might want to experiment by trying to add a sixth component. What
happens depends on the version of the JDK which you are using. Note that
you can add another container, such as a Panel, instead of a component,
then add a row of Buttons to the container. You can also use just two or
more positions, say South and Center. This way you could have a large
central area with a row of buttons (in the Panel) across the bottom.
Grid Layout
The GridLayout manager permits you to divide the container into equally
sized cells. You choose the number of rows and columns when you create
the layout manager. Components are added so that they fill up each row,
starting at the upper left corner. You can also add spacing, in pixels,
between each row and column. To add spacing around the border of any
layout manager, you override the insets() method.
public static void main(String args[]) {
Grid h = new Grid "GridLayout test.");
Button [] b = new Button[5];
h.makeButtons(b);
h.setLayout(new GridLayout(3, 2, 15, 15));
for (int i = 0;i < 5; i++) h.add(b[i]);
h.resize(300, 200);
h.show();
}
If you run this application, you will notice that each Button has been
scaled so that it fills the entire cell. Resizing the window affects the
size and shape of the Buttons, although the spacing between the Buttons
remains constant.
Suppose you want two rows of Buttons, with an equal amount of space at
the bottom of the container. If you allocate twice as many cells as you
need, the JDK will delete the extra cells. You cannot use an entire row
without components for spacing, as the layout manager simply uses all
the available space. You could fill the "unused" cells with Panels, and
reserve space by using the Panels.
Another trick you can use with the GridLayout manager is to use nested
grids. For example, you could use a top level Grid with just two cells
to split a container in half, then add a Panel to one of the cells and
use a Grid within that Panel to subdivide it further. Then you have one
half of the container as one large cell, and the other as a set of
smaller cells.
Other Layout Managers
There are two other layout managers. The CardLayout manager permits you
to have many containers, but only one is visible at a time. You use this
for the file folder paradigm popular in Windows applications, where each
container has a set of tabs on top, with one tab apparently connected to
the visible container, and the rest attached to hidden containers.
The GridBagLayout is the most complex layout manager. You have a lot of
flexibility here, with the usual price: complexity. You create a
GridBagConstraint object, and use it to suggest the position of each
component in the container. It's roughly a grid, but one where one cell
can steal space from other cells, or take up several cells. You also
have control over how components are scaled when the window is resized.
I'd like to expand on the folder paradigm, using a CardLayout manager,
but will save that for another time.
Terry Slattery of Chesapeake Computer Consulting wrote me about a useful
Java applet which you can find at https://www.ccci.com. You can use this
applet for calculating subnet numbers and directed broadcast addresses.
As always, I am looking for others who want to exhibit their own Java
skills in this space. I think I have one taker now, and am looking for
more.
|