Uh-oh! Your boss is an old-fashioned, diehard programmer. Having grown up
on FORTRAN, this traditionalist is accustomed to creating everything
through code, and thinks that new-fangled, GUI-based development
environments are so much fluff. Certainly not something for Real
Programmers! But somehow you managed to convince your boss that an
interface builder would save time and money, so you got approval to spend
the last dollars in the software budget on an Acme Systems Super Java App
Builder. You're supposed to use it to construct the user interface for
your company's first Java-language application, and the demo of the
prototype is due on Tuesday.
Now that you've started using the Super Java App Builder, you
realize that it has a fundamental limitation. The design spec for your
application requires one of the windows to be populated by different
widgets, depending on the current state of the application. In fact, the
number, type, and placement of the widgets has to change dynamically while
the window is open. But the App Builder assumes that a window is a static
beast containing fixed components. What now?
All is not lost! This article explains how to create
user-interface elements that change dynamically. Although hand-coding is
required, it turns out to be easy in the JavaTM
language, particularly when using the Abstract Window Toolkit (AWT).
A simple demo application is included, along
with source code.
When Do You Need to Hand-Code a User Interface?
Interface builders are great, but there are situations in which they might
not be able to do the trick. One classic situation of this sort is a
dynamic user interface. For purposes of this article, this term
refers to a UI that needs to change its appearance depending on the current
state of the application. More specifically, it refers to windows that
need to add or remove components in response to user actions. For example,
a window might serve as a control panel that has more than one mode. With
one option selected, the window displays a certain set of controls. When
the user switches to a different option, the appearance of the window
changes completely, displaying new controls that are appropriate to that
mode.
Another example of a dynamic user interface is a window that shows more or
fewer UI elements, depending on the user's selection. The Mac OS "Find
File" window, for instance, has a button called "More choices." When the
user clicks on this button, the window expands, retaining the existing
controls but adding new ones at the bottom.
Many interface builders can't help you create dynamic interfaces of this
sort--particularly interfaces that have customized appearances.
Fortunately, as Muscle Fish (the authors' company) discovered when writing
the SoundFisherTM application, AWT makes
it quite easy to implement dynamic UIs programmatically. With any luck,
your Acme Systems Super Java App Builder will let you add the dynamic portions of your UI
programmatically, even though you're constructing the majority of the UI
using the App Builder's graphical tools. Since your boss isn't going to
read your code (after all, it isn't FORTRAN), no one will ever know the
difference!
A Simple Demo with a Dynamic UI
As a simple example of a dynamic UI, take a look at
this demo application. Download this demo application -- a JAR
file -- to your desktop. (HINT: Most browsers provide a download function,
such as "Save link as...", when the right mouse button is pressed on a link.)
To run this demo application on, for example, a Solaris or Windows machine,
type the following at your command-line prompt:
java DynaGUI
(This demo was written using the Java Development KitTM, version
1.1.3. You will have to have JDK 1.1.3 or later installed to run the demo.
Also be certain to add the full pathname of the JAR file to your CLASSPATH
before executing the command-line above.)
This demo application is simplistic, because it's a basic programming example.
There's a pop-up menu (a Choice component) at the top. The two choices in
this pop-up menu are "Button Grid" and "Control Panel." You'll notice that
switching between the two changes the appearance of the rest of the window.
With "Button Grid" selected, there are three buttons, labelled "Button 1,"
"Add a button," and "Remove a button," respectively. Click "Add a button."
A new button, labelled "Button 2," appears. You can keep on adding new
buttons by clicking "Add a button," until you reach the maximum number of
buttons allowed: 9. Naturally, you can similarly delete buttons by
clicking "Remove a button." (None of these nine buttons does
anything; they simply show a dynamic UI in which user actions manipulate
the UI itself.)
If you switch to "Control Panel," different controls appear in the window:
a button, a check box, a textfield, and a slider. (Again, none of these
controls does anything meaningful; they're just there to illustrate that
the pop-up menu changes the UI dynamically.)
A Look at the Code
Well, that demo isn't terribly interesting in itself! Nevertheless, you
can use its source code as a basis for part of an
application that someone (such as your boss) might actually find useful.
This example code uses several layout managers from the Abstract Window
Toolkit (AWT). The FlowLayout
layout manager is implicitly used for the panel that an application displays in.
FlowLayout lays out the components in a panel from left to right, wrapping
them around as needed, as most word-processing programs do. Because
FlowLayout's behavior isn't appropriate for this application,
another top-level panel is created
that uses the BorderLayout
layout manager:
panelTopLevel = new Panel();
panelTopLevel.setLayout(new BorderLayout(5,5));
With BorderLayout, you specify components' positions as
North, West, East, South, and Center. This top-level panel is
placed in the application's main panel:
add(panelTopLevel);
A Choice component is placed in a new panel located at the "north"
(top) of this top-level panel:
panelSelect = new Panel();
panelTopLevel.add("North", panelSelect);
...
choices = new Choice();
...
panelSelect.add(choices);
In the init() routine, the Choice is initially set to "Button Grid," and
the makeButtonGrid() routine is called to construct the necessary
components, including the vector of Buttons. Inside makeButtonGrid(),
three Panels are constructed: panelGridMain, panelDynamic, and panelStatic.
The first of these Panels contains the other two; the second contains the
dynamically growing vector of Buttons; and the third contains the Add and
Remove Buttons.
Notice that panelDynamic uses the GridLayout
layout manager:
panelDynamic.setLayout(new GridLayout(3,2));
This layout places the Buttons in equally spaced rectangles of the same
size and shape.
The makeMiscControls() routine is analogous to makeButtonGrid(). It
creates and displays the components that appear when you select "Control
Panel."
Receiving Events
To receive events whenever the user makes a new choice, the
addItemListener() method is invoked:
choices.addItemListener(this);
The addItemListener method is part of the ItemSelectable interface, which
Choice implements. By invoking this method, this program's subclass of Applet
indicates to the Choices component that the applet (represented by "this")
wants to receive an event whenever the user makes a new selection. The
event is received through the itemStateChanged() callback.
Similarly, the code invokes addActionListener() to receive events whenever the user
clicks the Add and Remove buttons:
buttonAdd.addActionListener(this);
...
buttonRemove.addActionListener(this);
The actionPerformed() callback routine will be invoked when either of these
buttons is clicked. This routine checks the current size of the vector of
Buttons, and adds or removes a Button if appropriate.
Other Layout Managers
There's another layout manager, called CardLayout,
that can come in handy when creating a dynamic user interface. CardLayout
lets you define components that can be retrieved and displayed one at a
time in the same area, like cards from a deck. For example, the Choices
component in the DynaGUI application above could have caused different cards to
be retrieved. As an exercise, you might try rewriting the DynaGUI code to
use CardLayout.
If the AWT layout managers don't provide the geometry or behavior that your
application needs, you can write your own layout manager. To do so, you
create a new class that implements the LayoutManager
interface. This interface has methods that you can implement to specify
the placement of components within a panel.
How Dynamic Do You Need Your Code to Be?
For purposes of illustration, the DynaGUI programming example was made a
little more dynamic than it really needs to be. For example, every time
the user manipulates the Choices component to change between "Button Grid"
and "Control Panel," the code deletes all the components inside the current
panel, and then reconstructs the other panel's components. If you like,
you can probably find more efficient ways to solve the problem! A good
starting point would be Component's setVisible
method. (On the other hand, for this Tuesday's demo, you can probably get
by with unoptimized code, and maybe even impress your boss--as long
as your demo works!)
Thom Blum, Doug Keislar, Jim Wheaton, and Erling Wold are members of Muscle Fish, LLC, a
software consulting firm in Berkeley, California. Muscle Fish specializes
in audio and music technology, and produces software that searches for
sound based on its acoustical content. This is the second article by
Muscle Fish; also see their
article on web crawlers.
Reader Feedback
Tell us what you think of this article.