Sun Java Solaris Communities My SDN Account Join SDN
 
Article

Programming a Dynamic User Interface

 
 

Articles Index


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 Java 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 SoundFisher 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 Kit, 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!)

coffeecup

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.