JavaDude.com

-- Scott Stanchfield

Effective Layout Management

(I wrote this article for the Sun Java Developer
Connection website around 1998 and is no longer available. I used the
Wayback Machine to retrieve it and post it here)

This module will explore in great detail how the standard
Abstract Window Toolkit (AWT) layout managers perform their jobs
and how they can be effectively nested to create useful
graphical user interfaces (GUIs).Course
Outline

To describe why layout managers are necessary, all you need to
do is examine a few of the problems they solve. Look at the
following screen shots. These describe severalGUI
sinsthat are
all too common:

The first layout sin is to ignore a user-resize. The most common
display size is probably still 640x480, so it's a good idea to
make sure your application fits on that size screen. However,
some users have much more screen real estate and want to take
advantage of it. Non-resizable GUIs can be extremely
frustrating. For example, in the initial display of a screen,
the name entered is rather long, so it doesn't quite fit in the
text field such that all is visible at once:

In the hopes of seeing the entire name at once, you could expand
the dialog horizontally. Unfortunately, the programmer who wrote
this application used absolute positioning and sizing, so the
components in the dialog do not expand:

After reporting this behavior, the developer realizes the error
that has been committed and changes the GUI to resize properly:

Another common problem is expecting all platforms orLookAndFeellibraries
(when using Java Foundation Classes (JFC) Project Swing
technology) to have the same sizing characteristics. The
following picture shows the above GUI under theMotifLookAndFeelof
the JFC Project Swing technology without using a layout manager:

Notice that theMotifLookAndFeeluses
a wider, empty border around the buttons, causing some
undesirable drawing effects. Switching font sizes will have a
similar effect.

A layout manager encapsulates an algorithm for positioning and
sizing of GUI components. Rather than building the layout
algorithms into your code and watching for window resizing and
other layout-modifying events, the algorithm is kept separate.
This allows a layout algorithm to be reused for several
applications, while simplifying your application code.

LayoutManageris
an interface in the Java class libraries that describes how aContainerand
a layout manager communicate. It describes several methods
which:

Ask for sizing information for the layout manager and the
components it manages.

Tell the layout manager when components are added and
removed from the container.

Size and position the components it manages.

An additional interface,LayoutManager2,
was added in JDK 1.1, which adds a few more positioning and
validation methods.

TheComponentclass
defines several size accessor methods to assist the layout
process. Each of these methods returns aDimensionobject
describing the requested size. These methods are as follows:

public Dimension getPreferredSize()
This returns the desired size for a component.

public Dimension getMinimumSize()
This returns the smallest desired size for a component.

public Dimension getMaximumSize()
This returns the largest desired size for a component.

Layout managers will use these sizing methods when they are
figuring out where to place components and what size they should
be.Layout
managers can respect or ignore as much or as little of this
information as they see fit.Each
layout manager has its own algorithm and may or may not use this
information when deciding component placement. Which of these
methods is respected or ignored is very important information
and should be documented carefully when creating your own layout
manager.

Controlling the sizes returned for your own components can be
accomplished in two ways, depending on whether you are using the
JFC Project Swing components.

If your component is a JFC Project Swing component, you inherit
three methods,setPreferredSize(Dimension),setMinimumSize(Dimension)andsetMaximumSize(Dimension).
You can call these methods directly to explicitly set the size
information for a component. For example:

A layout manager must be associated with aContainerobject
to perform its work. If a container does not have an associated
layout manager, the container simply places components wherever
specified by using thesetBounds(),setLocation()and/orsetSize()methods.

If a container has an associated layout manager, the container
asks that layout manager to position and size its components
before they are painted. The layout manager itselfdoes
notperform the painting; it simply decides what size and
position each component should occupy and callssetBounds(),setLocation()and/orsetSize()on
each of those components.

Each of these methods adds a component to the container and
passes information to the layout manager of the container. All
of the methods take aComponentparameter,
specifying which component to add. Some take an index. This is
used to specify an order in the container; some layout managers
(such asCardLayout)
respect the ordering of added components.

The other parameters,nameandconstraintsare
information that can be used by a layout manager to help direct
the layout. For example, when adding a component to a container
that is managed by aBorderLayout,
you specify a compass position as a constraint.

Each of the aboveadd()methods
delegates its work to a singleaddImpl()method:

This method is the one that does all the work. It adds theComponentto
theContainer,
and, if a layout manager is managing the container layout, calls
theaddLayoutComponent()method
of the layout manager. It is throughaddLayoutComponent()that
the layout manager receives the constraints (from theadd()call).

If you create a subclass ofContainerand
want to override anadd()method,
you only need to overrideaddImpl().
All otheradd()methods
route through it.

In addition to a layout manager, eachContainerhas
agetInsets()method
that returns anInsetsobject.
TheInsetsobject
has four public fields:top,bottom,leftandright.

These insets define the area a container is reserving for its
own use (such as drawing a decorative border). Layout managersmustrespect
this area when positioning and sizing the contained components.

To demonstrate, create a simplePanelsubclass
that provides a raised, 3D border around whatever is contained
within it. You'll define this border as being 5 pixels away from
each edge of the container border, and reserving some extra room
between it and the laid out components. The class will look
something like this:

To create the panel, you defined a staticInsetsobject
that represents the space to reserve. Because that space won't
change, you used a single static final instance of it. You'll
return this instance anytime a layout manager (or anyone else)
callsgetInsets().

You then define apaint()method
that gets the size of the container into which it is painting,
then draws a raised border within that space. If you use the
above class as follows:

The AWT library includes five layout managers. These can be used
in various combinations to create just about any GUI you may
possibly want to write. The standard AWT layout managers are:

FlowLayout

BorderLayout

GridLayout

CardLayout

GridBagLayout

Each of these will now be discussed in detail, including
strategies and pitfalls when using them. By themselves, these
layout managers may not seem terribly useful. However, when
combined they become incredibly flexible.FlowLayout

This is the simplest of the AWT layout managers. Its layout
strategy is:

respect the preferred size of all contained components

lay out as many components as will fit horizontally within a
container

start a new row of components if more components exist

if all components can't fit, too bad!

To add components to a container managed byFlowLayout,
you could use one of the followingadd()methods:

This class creates aFrameand
adds five buttons to it. It lays the buttons out as many can fit
per row, then moves to the next row to display more of them. The
following pictures show the frame being expanded horizontally:

Initial Size

Some horizontal expansion

More horizontal expansion

Notice how the layout is fitting as many components per line as
possible. If there isn't room for all the components, they are
simply not shown:

FlowLayoutcan
be customized at construction time by passing the constructor an
alignment setting:

FlowLayout.CENTER(the
default)

FlowLayout.RIGHT

FlowLayout.LEFT

These settings adjust how the components in a given row are
positioned. By default, all components in a row will be
separated by an horizontal gap, then the whole chunk will be
centered in the available width.LEFTandRIGHTdefine
padding such that the row is left or right aligned. The
alignment can also be changed by calling thesetAlignment()method
ofFlowLayoutwith
one of the same alignment constants.

FlowLayoutcan
also be customized with different horizontal and vertical gap
settings. These specify how much space is left between
components, horizontally (hgap) and vertically (vgap)

Recall that when aContaineris
asked for its preferred size, it bases that preferred size on
its layout manager. This raises the subject of whatFlowLayoutwould
prefer to do with its components.

Unfortunately, there is no way for a container to know anything
about the size of its parent or how its parent's layout manager
plans to layitout.
So, the only information available to determine the preferred
size of a container is the set of components contained within
it.

FlowLayout's preference would be to lay all its
components out into asingle
row. This means that its preferred height would be the
maximum height of any of its components plus some "slop" known
as itsvgap.
(vgapandhgapare
common properties of most of the standard layout managers, and
specify how far apart components are placed.) The preferred
width would be the sum of all widths of its contained
components, plus anhgapbetween
each and on either end of the row.

Think about the consequences of this. What would happen if aFlowLayout-managed
container was nested within anotherFlowLayout-managed
container? For example:

Think this through a bit and the answer is very disconcerting.
The following walks you through the layout process:

p1's parent container tells it to lay itself out.

p1 sees that it has a layout manager and delegates thelayout
task to it.

p1'sFlowLayoutchecks
the preferred sizes of all its components:

The only component in p1 is p2

p2 sees it has a layout manager and delegates the
preferred size request to the layout manager:

p2'sFlowLayoutstates
that it would prefer to lay out all of its
components in a single row.

p2'sFlowLayoutasks
its components (the buttons) for their preferred
sizes and calculates the size of that single,
preferred row.

p2'sFlowLayoutreturns
that preferred size.

p2 returns the preferred size.

p1'sFlowLayouttries
to respect the preferred size of p2 as much as possible.

If there's enough room for the single row, it sets the
bounds of p2 to its preferred size.

If there's not enough horizontal room, it sets p2's
bounds to the single-line height and as much width as is
available.

If there's not enough horizontal or vertical room, it
sets the bounds of p2 to whatever it has.

So what does this tell us? If aFlowLayout-managed
container is placed within another container whose layout
manager respects its preferred height,the
nestedFlowLayout-managed
container will always have a single row.

Caveat: If you only have a few components, this is not
as important to avoid.

TheFlowLayoutmanager
effectively hides components if they won't fit.

There is no visual indication of this to the user—as far
as the user is concerned, the unshown components never
even existed.

Because of the previous point,FlowLayoutis
really only useful when you have a small number of
components.

FlowLayoutishorizontallybiased.
If you want averticalflow
layout, you must write your own. (Or use theBoxLayoutmanager
which comes with the JFC Project Swing component set.TheBoxLayoutmanager
is discussed in theFundamentals
of Swing: Part Itutorial.)

BorderLayoutis
probably the most useful of the standard layout managers. It
defines a layout scheme that maps its container into five
logical sections:

The first thing going through your mind should be "but I willneverhave
a GUI that looks like that!" Moreover, you are probably correct.
However, the secret is in mastering its nesting capabilities,
and using two or three of the logical sections. (It's very rare
that you'll actually use more than three of the positions in a
container at once.)

TheBorderLayoutmanager
requires a constraint when adding a component. The constraint
can be one of the following:

BorderLayout.NORTH

BorderLayout.SOUTH

BorderLayout.EAST

BorderLayout.WEST

BorderLayout.CENTER

These constraints are specified within the following twoadd()methods:

public void add(String constraint, Component component)
This is the "old" form of adding a constraint. JDK 1.0.2
only provided for constraints that were represented by aStringto
be added when adding aComponentto
aContainer

public void add(Component component, Object constraint)
This is the "new" form of adding a constraint, added in JDK
1.1.

You'll see a few more variations of theadd()method
when you examineCardLayoutlater.
Concentrate on just these forms for now.

ForBorderLayout,
the constraint argument describes which position the component
will occupy. Note that the earlierBorderLayoutexample
source code could have been written as follows:

The big difference is that the newer form (using theBorderLayout.NORTHtype
constraints) can be compile-time checked; if you typeBorderLayout.NORFHthe
compiler will catch it. If you just type "Norfh", it will not be
caught until runtime, causing anIllegalArgumentExceptionto
be thrown.

The Java 2 platform (previously known as the JDK 1.2) adds
additional constants ofBEFORE_FIRST_LINE,AFTER_LAST_LINE,BEFORE_LINE_BEGINS,
andAFTER_LINE_ENDS.
These are effectively equivalent toNORTH,SOUTH,WEST,
andEAST,
respectively. However, they could have other orientations where
text is not oriented left-to-right, top-to-bottom. Examine thejava.awt.ComponentOrientationclass
for additional information on language-sensitive orientation
issues.

BorderLayoutrespectssomeof
the preferred sizes of its contained components,but
not all. Its layout strategy is:

If there is aNORTHcomponent,
get its preferred size.
Respect its preferredheightif
possible, and set its width to the full available width of
the container.

If there is aSOUTHcomponent,
get its preferred size.
Respect its preferredheightif
possible, and set its width to the full available width of
the container.

If there is anEASTcomponent,
get its preferred size.
Respect its preferredwidthif
possible, and set its height to theremainingheight
of the container.

If there is anWESTcomponent,
get its preferred size.
Respect its preferredwidthif
possible, and set its height to theremainingheight
of the container.

If there is aCENTERcomponent,
give it whatever space remains, if any.

Now consider nesting aFlowLayout-managed
container inside aBorderLayout-managed
container. First, what would happen if you added theFlowLayout-managed
container as theNORTHorSOUTHcomponent
of theBorderLayout?

Remember what happens when aFlowLayout-managed
container is added to a layout that respects preferred height?TheFlowLayoutcontainer
will only ever have a single row! It will never flow its
components to more than that one row.

That was the easy one (now that you know the secret). Now, what
if you add theFlowLayout-managed
container as theWESTorEASTcomponent?
To demonstrate, place the container in theEASTsection:

This initial display looks pretty much as you would expect. Each
component is taking up its preferred size.

Next, reduce the width and increase the height. TheFlowLayoutcontainer
is still insisting on a single row for its preferredwidth(as
you should expect) and is eating up room that theCENTERcomponent
would have liked to use. This result can be very unexpected, as
you might think theFlowLayoutcontainer
should expand to fill theEASTarea
and give some room back to theCENTERcomponent:

Remember: aBorderLayoutcontainer
asks for the preferred size of each section and respects it as
much as possible.

To demonstrate this even further, reduce the width until only
theEASTcomponent
is still visible:

Finally, if you compress it more, theFlowLayout-managed
container is clipped to the available space:

So, the only place that you should really put aFlowLayoutmanaged
container within aBorderLayout-managed
container is theCENTERsection
(unless you only have very few components in theFlowLayout).

Now consider some nice generalizations aboutBorderLayout:

NORTH andSOUTHpositions
in aBorderLayoutcan
be useful if you want tobindthe
height of part of a GUI to that part's preferred height.

EAST andWESTpositions
in aBorderLayoutcan
be useful if you want tobindthe
width of part of a GUI to that part's preferred width.

Once part of the GUI is bound, theCENTERis
the expanding part.

These are very important properties ofBorderLayout,
and make it very powerful when used to create a more complex,
nested GUI.

Take a very simple example. Suppose you wanted to create a
simple labeled text field. You want this new component to
exhibit the following properties:

Here you are binding the width of the label to its preferred
width. It will take up that much space and not expand. The entry
field is not bound and can expand. So, you achieve the desired
result:

Initial size

After horizontal stretch

At least itseemsyou
achieved the desired result. Look what happens when you expand
vertically:

TheTextFieldis
stretched. So, you need to bind the height of the two components
to their preferred height. This can be accomplished by placing
theLabel/TextFieldcombination
insideanotherBorderLayout-managed
container, as theNORTHorSOUTHcomponent.
Assuming that you want the fields to stay at the top of the GUI,
you can place it to theNORTH:

If aBorderLayout-controlled
Container is asked for its preferred size, what will it return?
The idea behind its preferred size is to make sure all contained
components are given their preferred sizes. First, look at the
preferred width:

Looking at the above picture, there are three rows of
components:

NORTH

WEST,CENTERandEAST(plus
hgaps as needed)

SOUTH

The preferred width of the layout needs to take into account the
widest of these rows. Using pw as the abbreviation for
"preferred width", you can write a simple equation for the
preferred width of aBorderLayout:

pw = max(north.pw, south.pw,
(west.pw + center.pw + east.pw + hgaps))

Thehgapsamount
to include depends on which components are present in the center
row.

The preferred height (phin
the following equation) depends on the sizes of theNORTHandSOUTHcomponents
plus thetallestof
the middle-row components:

ph = vgaps + north.ph + south.ph +
max(west.ph, center.ph, east.ph)

Thevgapsamount
depends on which rows are present in theBorderLayout.

A useful application of this preferred-size knowledge is
creating two or three rows of components that each keep their
preferred size. Suppose you had aLabel,
aTextArea,
and aTextFieldthat
you wanted to lay out like this:

And when you expand it vertically, youdo
notwant the
components to stretch vertically:

First, recall that you canbindthe
height of a component to its preferred height by placing it in
theNORTHorSOUTHpart
of aBorderLayout.
If that bound component happens to be anotherBorderLayout,
(withNORTH,CENTER,
andSOUTHcomponents)
each component within that layout would get its preferred
height. This results in the above figure, with code to produce
it:

When specifying aGridLayout,
there are two main parameters:rowsandcolumns.
You can specify both of these parameters,but
only one will ever be used.Take
a look at the following code snippet fromGridLayout.java:

Notice that ifrowsis
non-zero, itcalculatesthe
number of columns; ifrowsis
zero, it calculates the number of rows based on the specified
number of columns.

To the casual observer, a statement like

f.setLayout(new GridLayout(3,4));

looks like it willalwaysdivide
the screen into twelve sections, but that's not the case. In the
above statement, you could substituteanyvalue
for the number of columns, and the effect would be exactly the
same.

A better way to specify therowsandcolumnsof
aGridLayoutis
toalwaysset
one of them to zero. A zero value forrowsofcolumnsmeans
"any number of".

Note: A word of caution: you cannot specify zero forboth;
anIllegalArgumentExceptionwill
be thrown.

Specifying zero for one value makes thedesign
intentobvious.
If you alwayswantfour
rows, say so; if you alwayswantthree
columns, say so. The above exampleshouldbe
written as either

How do you determine the preferred size of aGridLayout?GridLayoutwants
to accommodate the preferred size ofallits
contained components if possible. To do this, it looks at all
the preferred sizes and determines the maximum preferred width
and the maximum preferred height.One
thing to keep in mind, the maximum preferred height and maximum
preferred widthdo
notnecessarily
come from the same component!

TheGridLayoutwould
like to set the size ofeachcomponent
to that maximum preferred width and maximum preferred height.
(Remember—all components in aGridLayoutwill
be the same size!) This makes the preferred size of aGridLayout

One of the goals of the earlier "Ok" and "Cancel" dialog was to
make the buttons the same size. This can be accomplished by
putting the buttons in a single-rowGridLayout.
You can then add thatGridLayoutcontainer
to theFlowLayout,
or use a nestedBorderLayoutin
place of theFlowLayout.
Here are both approaches:

Notice the difference in appearance. TheFlowLayoutpadsaroundthe
components with the hgap and vgap, whileBorderLayoutonly
padsbetweencomponents
(so the components butt right against the edges of the
container.)

In both cases, the buttons now appear the same size. The nesting
for this layout can be more easily seen in the following
picture:

And similarly for the second version of the layout, using the
nestedBorderLayout.

CardLayoutuses
a different strategy than the other layout managers. Instead of
assigning locations in the container for all nested components,
it only displays one component at a time. Components can be
added to aCardLayoutusing
the followingaddmethods:

The first two forms of theadd()method
will add the component at the end of the list of components for
the container. The last form of theadd()method
will add the component at the specified position in the
container. The position of the component within the container
determines the order in which the components will be displayed
via the manipulation methods ofCardLayout.

A uniqueStringkey
must be assigned for each component that is added to the
container. For example:

When components are added to container that is controlled by aCardLayout,
aStringkey
is associated with each component. Different components can be
displayed by using thenext(),previous,
andshowmethods
ofCardLayout.
The order in which components are added to the container
determines their display order when using thenext()andpreviousmethods.
For example:

Notice that theprevious(),next(),
andshow()methods
require a reference to the container be passed into them has an
argument. Layout managers do not keep a reference to the
container that uses them. When performing actions such as
callingprevious(),next(),
andshow(),
and, as you will see later, the laying out of the actual
components, the layout manager needs to be informed of the
container on which it is operating so it can have access to the
components it is laying out.

CardLayoutis
commonly used in GUIs that want to organize their data into
several smaller screens, rather than having all components on
one larger screen. This is typically used in combination with
several buttons for switching components within theCardLayout,
or a tabbed panel component.

So how do you determine the preferred size of aCardLayout?
Keeping in mind that the preferred size wants to take into
account the preferred size of all contained components in the
container, the preferred size of theCardLayoutwill
be the maximum preferred width of all contained components and
the maximum preferred height of all contained components

GridBagLayouttends
to be one of the most difficult layout managers to understand.
There are several reasons for this widely held opinion:

It is very complex and can be difficult to learn.

Ifyoulearn
it and use it in your GUI, the poor maintenance programmer
will have to learn it just as well as you did.

There are a few bugs inGridBagLayoutthat
evidence themselves after components are added to or removed
from theGridBagLayoutafter
theGridBagLayouthas
been displayedNote:GridBagLayoutmaintains
some internal state that sometimes gets confused when
components are added and removed.

Covering all of the details of usingGridBagLayoutcould
span an entire book. It will be covered briefly in the context
of an example in a latersection.

Without a GUI builder like VisualAge for Java or JBuilder, you
should avoidGridBagLayoutif
at all possible. For most GUIs, you can achieve the same results
by nesting the other, simpler layout managers.

TheFrameclass
provides apack()method
that helps set an "ideal" initial size. Thepack()method
calls thegetPreferredSize()method
ofFrameto
determine what size it would like to be laid out, and, if the
screen is large enough to allow it, sets the Frame to that size.
If there isn't enough screen space, the Frame will be limited to
the available screen space.

A great example of nesting the standard layout managers is
WS_FTP, a Windows program for graphical FTP support.

This GUI, while it may seem nice for "power users" is overly
complex. The buttons at the bottom should really be menu items
and the buttons to the right of each file list should be popup
menus on the file lists. Nevertheless, the GUI as it stands
makes a great example for nesting the basic layout managers.

Draw a picture!You
would be surprised how many people try to visualize the GUI
in their head. Drawing a picture makes the design
significantly easier to develop.

Describe the resize behavior.After
drawing the picture, add information about which parts of
the GUI will expand/collapse when the window is resized.

In the above example, ignore the border lines around components.
These can be added after the GUI is constructed, either by
applying the Decorator Pattern or, if using JFC Project Swing
containers, by callingsetBorder().

Start with a picture describing what you want the GUI to look
like. Simplify things a bit by removing the decorative borders
and assume the existence of a "File List" component (probably a
table with a header).

The arrows indicate which parts of the GUI will expand/collapse
when the GUI is resized.

The above diagram is marked with the resize behavior of the GUI.
The key explains which lines refer to fixed and varying sizing
behaviors. When part of a component has a fixed size
(horizontal, vertical, or both) it will not expand or contract
as the window is resized. The equal signs (=) refer to adjoining
components that have the same size.

A few notes on the GUI's behavior:

The two large sections that contain the File Lists should
occupy the same amount of space.

The two arrow buttons in the middle should be vertically
centered between the file list sections.

The ChgDir, MkDir, and other buttons all are the same size
and are fixed at the group's preferred width and do not
expand vertically. (The space below them expands when the
window is stretched vertically.)

The buttons at the bottom of the GUI are all the same size
and will expand/collapse horizontally with the GUI's width.

The ASCII, Binary and Auto checkboxes stay a fixed amount
apart, floating as a group horizontally centered.

Now, the fun begins. You need to examine the GUI and try to
visualize layout managers being used to represent sections of
it. When trying to design the GUI, work from the outside edges
inward. Always start by looking for "borders"; a component, or
group of components that border an edge with a fixed width or
height.

Looking at the above GUI, you can visualize it as aBorderLayoutwith
two components: aSOUTH,
and aCENTER:

TheSOUTHpart
of theBorderLayouthas
a fixed height, based on the preferred size of its components.
TheCENTERpart
will expand to fill the remaining room in the GUI. Examining theSOUTHsection
closer, you can view it as three parts:

Note that there are three components here, stacked vertically,
each with their preferred height. You might be tempted to stick
this in aGridLayout,
but that would force them all to take up the same amount of
space vertically. That's not what you want.

Suppose you put these three components asNORTH,CENTER,
andSOUTHof
aBorderLayout.
This newBorderLayoutpanel
is nested as theSOUTHcomponent
of the overallBorderLayout.
Think through what happens when this part of the GUI is laid
out:

The overallBorderLayoutasks
itsSOUTHcomponent
for its preferred size

TheSOUTHcomponent,
being aBorderLayoutitself,
asksitscomponents
for their preferred sizes

The nestedBorderLayoutreturns
a size that is

Width = max preferred width of those three
components.

Height = sum of the preferred heights of those three
components.

The overallBorderLayoutassignsthe
size of itsSOUTHcomponent
to the frame's width, and the preferred height of thatSOUTHcomponent.
TheCENTERcomponent
gets all remaining room.

TheSOUTHcomponent
now gets its chance to lay out its contained components.

TheSOUTHcomponent
asks for the preferred sizes of its children.

TheSOUTHcomponent
assigns the width of all components to the width it has
been given.

It grantsitsNORTHcomponent
its preferred height.

It grantsitsSOUTHcomponent
its preferred height.

It grantsitsCENTERcomponent
the remaining space.

Note that last bullet. TheCENTERgets
the remaining space. Because the overallBorderLayouthad
granted theSOUTHcomponent
its preferred height,andthe
preferred height was equal to the sum of the preferred heights
of the contained components, that nestedCENTERcomponent
(the messageTextArea)
just happens to get its preferred height.

First, define theNORTHpart
of this sub-GUI. It has three components, equally spaced, with
all of them centered across the width of thePanel.
Sounds like aFlowLayout,
eh? So, it is aPanelwith
aFlowLayoutcontaining
threeCheckBoxcomponents.
Notice that the first twoCheckBoxcomponents
must be associated with aCheckboxGroupso
they will become radio buttons.

Now you must create theSOUTHpart
of this sub-GUI. It has sevenButtoncomponents,
all equally sized. The phrase "equally-sized" should immediately
triggerGridLayoutin
your mind. So, theSOUTHpart
of the sub-GUI is aPanelwith
aGridLayoutcontaining
sevenButtoncomponent.
ThisGridLayoutconsists
of a single row, so use parameters of (1,0) to its constructor.

TheCENTERpart
is just a single component, aTextArea.
You do not need to nest this inside anotherPanel;
components can be directly added to containers... You'll set
thisTextArea's
number of rows to 3 and columns to 30. These numbers drive thepreferred
sizeof theTextArea;
they have no effect on theTextAreaonce
it has been sized by the layout manager. You must specify these
because its placement in the layout asks for its preferred size,
and it would return a larger number of rows than three if asked.
The 30 will only come in handy if youpack()theFrame,
where it will help contribute to the calculated width of the
frame. One final note on thisTextArea:
if you place it in aBorderPanelas
previously discussed in theInsetssection
above, you can give it a raised, lowered, or etched border.

To sum up what the layout looks like so far:

Ftpextends
Frame, layout=BorderLayout

SOUTH=Panel,
layout=BorderLayout

NORTH=Panel, layout=FlowLayout

Checkbox("ASCII") w/CheckboxGroup

Checkbox("Binary"), w/CheckboxGroup

Checkbox("Auto")

CENTER=TextArea(3,30)

SOUTH=Panel,
layout=GridLayout(1,0)

Button("Close")

Button("Cancel")

Button("LogWnd")

Button("Help")

Button("Options")

Button("About")

Button("Exit")

CENTER=? (that's next...)

Now you need to address theCENTERpart
of that overallBorderLayout.
First, take a look at the following picture of the center
section:

Notice anything interesting? It has to do with those two boxes
drawn around the left and right side components. They are
structurally the same.Exactlythe
same. When two things are the same, and it's a chore to build it
each time, you should thinkreuse.

Looking first at theNORTHpart,
you need to have theLabelandChoicetake
up their preferred height and the full width. Since this is theNORTHcomponent,
whatever they are contained in will receive its preferred
height. By placing theLabelandChoiceasNORTHandSOUTH(orNORTHandCENTER,
orCENTERandSOUTH)
of aBorderLayout,
they will receive their preferred height. Again, aGridLayoutwould
be a bad choice because that assumes that both components want
the same height. You could change the font size on theLabelto
make it larger, and you wouldn't want that to affect the size of
theChoice.

TheCENTERpart
is simple: just aListcomponent.
Not contained inside anyPanel(unless
you had aPanelsubclass
that provided a decorative border, such as the
previously-discussedBorderPanel).

Now for theEASTpart,
which is a bit trickier. You want all theButtoncomponents
the same size (your mind should immediately thinkGridLayout)
but that size should be their preferred height and preferred
width. Any space below the buttons expands and collapses.

Because you've placed the entire strip ofButtons
as anEASTcomponent
of aBorderLayout,
their width is fixed on the preferred width of the container
that encloses those buttons. To fix their height on their
preferred height, you can place the stripinside
anotherBorderLayout,
as theNORTHcomponent!
SoEASTis
aPanelwith
aBorderLayoutcontaining
anotherPanelas
itsNORTHcomponent,
and thatNORTHPanelhas
aGridLayoutcontrolling
its buttons. TheGridLayoutis
a single column of buttons, so its constructor parameters are
(0,1).

Now for a look at the structure of theFileDisplay:

FileDisplayextendsPanel,
layout=BorderLayout

NORTH=Panel,
layout=BorderLayout

NORTH=Label("")

SOUTH=Choice

CENTER=List

EAST=Panel,
layout=BorderLayout

NORTH=Panel,layout=GridLayout(0,1)

Button("ChgDir")

Button("MkDir")

TextField("*.*")

Button("View")

Button("Exec")

Button("Rename")

Button("Delete")

Button("Refresh")

Button("DirInfo")

To make this work properly, you must haveFileDisplay"promote"
the text property of itsLabelas
a property of its own. Provide aget()andset()method
for that property that simply gets and sets the text property of
theLabel.
You'll see this when the code is reviewed later.

Examine what theCENTERsection
looks like without the details that you captured inFileDisplay:

The two ovals represent the two instance ofFileDisplay.
The behavior of this part of the GUI is really up to four
components: twoFileDisplaycomponents,
which should take up equal amounts of space, and twoButtoncomponents,
each with its preferred size, floating casually in between them.

For purposes of aesthetics, assume that the buttons should be
centered vertically.

This is a tricky layout situation. Among the standard layout
managers, there is really only one choice: theGridBagLayout.

Examine how difficult this can be for a simple case like this.
First, you need to determine where the grid cells are. You do
this as is always necessary to do withGridBagLayout—draw
lines between each pair of adjoining components all the way
across the GUI:

You can see that thisGridBagLayoutwill
have six cells and four (numbered) components. Each cell
requires the configuration of aGridBagConstraintsobject,
to be passed along as the constraints in theadd(Component
component, Object constraints)version
of theadd()method.
You now need to walk through theGridBagConstraintsoptions
and determine what they should be.

First, the easy ones, gridx, gridy, gridwidth, gridheight,
ipadx, and ipady. You'll use insets rather than padding, so it
makes the ipadx and ipady settings zero for all components:

1

2

3

4

gridx

0

2

1

1

gridy

0

0

0

1

gridwidth

1

1

1

1

gridheight

2

2

1

1

ipadx

0

0

0

0

ipady

0

0

0

0

Next you need to understand fill. Recall that this tells how the
components will expand within their allotted cells. For
components 1 and 2, this will be BOTH; you want components 3 and
4 to be their preferred size, so fill will be NONE.

Next, anchor. Because components 1 and 2 completely fill their
allotted space, their anchor could be any value. Usually you use
the default ofCENTER.
Component 3 sits at the bottom/center of its cell—that's theSOUTHanchor
position. Component 4 sits at the top/center of its cell—that's
theNORTH,
anchor position.

The insets define the space to leave around components. There's
no space around components 1 and 2, so their insets are all 0.
You want some space around components 3 and 4. How about four
pixels between them and any adjoining components. You can do
this in several ways to put 4 pixels between components 3 and
4—give them 2 as their bottom and top insets.

Now the tricky part of thisGridBagLayout:
the weights. Think about the constraints rules you want:

The cells of components 1 and 2 expand horizontally, equally

The cells of components 3 and 4 do not expand horizontally

The cells of components 1 and 2 expand vertically to fill
the entire space equally

The cells of components 3 and 4 expand vertically, each
filling half the space

By rule #1, the weightx settings of components 1 and 2 must be
equal. By rule #2, the weightx settings of components 3 and 4
must be 0. These are the only rules relating to horizontal
space, so you can pick any weights you want for components 1 and
2, as long as they are the same. The weights are floating point
numbers, and a nice convention to follow is to normalize the
total weights to 1.0, giving each component 1 and 2 a weightx of
0.5.

By rule #3, the weighty settings of components 1 and 2 must be
equal, and should be the total weighty of the container. By rule
#4, the weighty settings of components 3 and 4 must be equal and
be half of the total weighty. This gives the following results:

A note on that lastGridBagLayout:
sometimes the initial thought regarding components 3 and 4 is to
have weightx and weighty be zero for both components. This would
produce the following result:

Notice where the "<--" and "-->" buttons are. The weightx and
weighty values control the sizing of thegrid
cellsthat a
component will occupy. If the sum of the weights for a given row
or column in aGridBagLayoutdoes
not equal that of the other rows or columns,GridBagLayoutmakes
up the difference by setting the weight of thelastcomponent
in that row or column. In this case, you have three columns: two
are theFileDisplaycomponents,
and the third is the column with the two buttons in it. Because
the weighty values of the two buttons don't add to the same
weighty values of the other two columns,GridBagLayoutgives
the lower button's grid cell the entire remaining height.

This code was generated using IBM's VisualAge for Java, and
simplified by removing the exception handling and "ivj" prefixes
on all the variable names. The code follows a "lazy
instantiation" strategy: no GUI components or sub-GUIs are
created until needed. This has several advantages:

If a GUI in your application isn't used, it's never created,
making the program more efficient.

You never need to worry if you've already created part of a
GUI or not; you simply call the method in question to access
that part of the GUI.

The GUI structure is directly reflected in the code. Nested
GUI elements are calls made to other methods.

Of course, there are a few disadvantages as well:

More code to write

Slightly slower performance due to extra method calls
(although this is minimal compared to other performance
issues)

Another alternative is to use a GUI builder tool to create the
interfaces. This can be a very attractive option, especially if
your GUI is very complex.

Taking this same example and writing itblob-style
can result in the following code:

The advantage here is less code, although it can be difficult to
see the GUI structure in the code. It can also be very difficult
to properly order the creation/addition of GUI objects—you must
be very careful to create everything you need before you use it.

A common GUI need is to create some sort of input form. The
following is a simple example:

Note that in this GUI all of the labels line up horizontally, as
do the text fields. When this GUI is expanded horizontally, the
text fields should stretch but the labels should not.

One of the things to be mindful of when designing this GUI is
what happens when the window is expanded vertically. Text fields
tend not to look too good when expanded vertically. You probably
want everything to keep its preferred height.

Keeping the labels and text fields at their preferred height

Allowing the labels and text fields to stretch

So, how do you design this GUI?

First, you need to think about how to bind the labels and text
fields to their preferred height. This can be accomplished by
placing all of the components as theNORTHcomponent
of aBorderLayout.

Next, you need to divide the GUI into two evenly spaced parts:
the left half the right half. You should think "GridLayout"
whenever you hear the phrase "equally spaced". ThisGridLayoutconsists
of a single row; its parameters to its constructor would be
(1,0). (In the above example, you are also setting hgap to
five.)

Now what do you do about the pairs of text fields and labels?
Your initial answer might involve placing each pair of text
fields and labels into their ownBorderLayout.
For example:

Using this strategy for each pair of labels and text fields, and
adding each of those into their proper place in the GUI, you get
a slightly undesirable affect:

So what happened? Think about whatBorderLayoutdoes.
It says "look at myWESTcomponent.
Give it its preferred width. Then, give theCENTERcomponent
the remaining available space." Note that it doesn't say "look
at the component below me. Make sure myWESTcomponent
matches the same size ashisWESTcomponent."
You need to somehow associate the labels with one other, and the
text fields with one another.

The only way you can do this is to put all the labels that go
together in the same container. Similar for the text fields.
However, you also need to make sure the text fields and labels
line up.

If you can make sure that the container that holds the labels
takes up the same vertical space as the container that holds the
text fields, you can use aGridLayoutto
divide each of those spaces evenly. But wait; you have already
decided to put all of the labels and text fields in theNORTHcomponent
of the overallBorderLayout.
This fixes the height of that set of components to its preferred
height.

So, you start off with the overallBorderLayout.
You add a Panel as theNORTHcomponent
of theBorderLayout.
ThisNORTHPanel
contains all the other components, and uses a single-rowGridLayout.

Within thatGridLayout,
you add two Panels for the left and right halves of the GUI.
Each of these panels will contain two components: a Panel that
contains the labels, and a Panel that contains the text fields.
Because you want the text fields to expand horizontally, and the
labels to keep a fixed width, you want to use aBorderLayout.

The final GUI will be structured as follows:

Frame, layout=BorderLayout

NORTH=Panel, layout=GridLayout(1,0)

Panel, layout=BorderLayout

WEST=Panel, layout=GridLayout(3,0)

Label("First name:")

Label("Street:")

Label("Phone:")

CENTER=Panel, layout=GridLayout(3,0)

TextField

TextField

TextField

Panel, layout=BorderLayout

WEST=Panel, layout=GridLayout(3,0)

Label("Last name:")

Label("City:")

CENTER=Panel, layout=GridLayout(3,0)

TextField

TextField

One thing to notice here is the parameters passed to the most
deeply nestedGridLayoutcontainers.
Your first thought when setting up theseGridLayoutcontainers
should be "there is one column of components". The problem is,
the first set ofGridLayoutcontainers
have three components each; the second set has two components
each. If you set aGridLayoutconstructor
parameters to (0, 1), the GUI would look as follows:

Notice the second set of labels and text fields is evenly spaced
into two chunks. What you wanted was to have all the text fields
and all the labels take up the same amount of vertical space
(which means that there will be a blank spot at the bottom of
the rightmost set of labels and text fields).