The effective user interface

Five ways to enhance the appearance and effectiveness of your user interface


In order to be effective, a user interface must do much more than throw a panel full of buttons, labels, and scrollbars onto the screen. Instead, it must "look right" and it must "act right." Unfortunately, there is often a lack of consensus as to what constitutes "right." It is almost certain that someone else's definition of "right" will sooner or later diverge from yours.

The designers of the Abstract Windowing Toolkit (or AWT) attempted to make the classes provided by the AWT "do the right thing." Much of the time, the result is pleasing and effective. However, they were wise enough to provide mechanisms by which the AWT's idea of the "right" behavior and appearance could be changed, so that programmers could alter the behavior and appearance of components.

This column, my last on the AWT, introduces five techniques for enhancing a user interface. The techniques draw upon and extend the concepts learned in my previous two columns. I will describe how to select the font, the background color, and the foreground color of components; how to specify the preferred size of a component, and the insets of a container; and how to enable and disable components on the fly.

Setting the Font

The font with which component text is displayed contributes to the impact of a user interface. An effective user interface shouldn't be littered with fonts of all styles and sizes, but it may use two or three fonts to enhance its attractiveness and expressiveness. The applet in Figure 1 illustrates this point. It uses three fonts (in three sizes and styles) to draw the user's attention to successive portions of the user interface. The source is available



Figure 1: Fonts in use

The default font for components is the "Dialog" font. Java provides a number of other fonts for specialized display purposes. The exact number depends on the platform. Figure 2 contains a listing of the fonts available in your implementation of Java. The source is available here.

Figure 2: Fonts available on your system

When a programmer needs to display a component, such as a TextArea object, in a font other than the default font, the new font may be selected with the setFont() method:

public void setFont(Font f)

The setFont() method expects a Font object as a parameter. The code in Listing 1 illustrates the use of the setFont() method.

TextArea ta = new TextArea();
Font f = new Font("Helvetica", Font.ITALIC, 12);

Listing 1: The setFont() method

The code in Listing 1, with only slight modifications, will work for any component.

Figure 3 illustrates the result of changing the font for several components provided by the AWT. The font in this example has been set to 12 point, italic Helvetica.

Figure 3: The result of changing the font

If a container's font is changed, all of the components placed within the container will automatically use the new font. The code in Listing 2 illustrates this point. The font is set only for the Frame object. The Button, Checkbox, and TextArea objects, in turn, also use that font.

Frame f = new Frame();
Font font = new Font("Helvetica", Font.ITALIC, 12);
f.setLayout(new FlowLayout());
Button b = new Button("Hello");
Checkbox cb = new Checkbox("Press Me");
TextArea ta = new TextArea();

Listing 2: Inheritance of font information

Setting the Background and Foreground Colors

The visual impact of color should not be underestimated when designing a graphical user interface. Color tends to attract the viewer's eye, and can be used to draw attention to an important piece of data or to an important position on the display. In the example in Figure 4, the red highlight around the button immediately draws your attention to the button. The source is available



Figure 4: Colors in Use

The Component class provides two methods for modifying the color information about a component. They are the setBackground() method and the setForeground() method, which set the background and foreground colors respectively:

public void setBackground(Color c)
public void setForeground(Color c)

Both methods require a single parameter, a Color object. The code in Listing 3 illustrates how to set the background and foreground colors.

TextArea ta = new TextArea();

Listing 3: The setBackground() and setForeground() methods

The code in Listing 3 will work with any component with only slight modifications. Figure 5 illustrates the result of setting the background and foreground colors for several of the components provided by the AWT.

Figure 5: The result of changing the colors

If a container's background color or foreground color is changed, all of the components placed within that container will automatically use the new colors. The code in Listing 4 illustrates this point. The color is set only for the Frame object. The Button, Checkbox, and TextArea objects, in turn, also use that color.

Frame f = new Frame();
f.setLayout(new FlowLayout());
Button b = new Button("Hello");
Checkbox cb = new Checkbox("Press Me");
TextArea ta = new TextArea();

Listing 4: Inheritance of color information

The quality of color support varies considerably from platform to platform. Under Windows 95, the Button class totally ignores color commands and stubbornly insists on remaining a drab gray rectangle. On the other hand, the Label class's background seems to be transparent. Some components don't display the same color for the same Color object. Under Windows 95, a background of orange appears orange for most components (except buttons), but presents itself as yellow when it appears in TextArea and TextField objects. Color support under Solaris appears to be more consistent.

Setting the Preferred Size

The size at which a component or container is displayed is an important ingredient of its overall appearance. At the heart of component placement is the layout manager. The layout manager is able to set the size and position of the components added to the container the layout manager manages. It also indirectly influences the size of the container.

For the techniques above, the relevant characteristics are set via a method call. Setting the preferred size works differently. Rather than calling a method to tell the component what size it should be, a programmer instead derives a new component class and redefines the preferredSize() method so that it returns the preferred size. The layout manager calls the preferredSize() method in order to determine what the preferred size should be.

The following method must be overridden:

public Dimension preferredSize()

One might be tempted to use the resize() method or the reshape() method to specify a size, but do not do so. Both are used by the layout manager itself, and the size adjustments will be reset the next time the layout manager lays out the container.

The code in Listing 5 contains an example of a preferredSize() method, suitably overridden in a derived class. This method creates a new Dimension object with the specified width and height and returns it to the caller (typically the layout manager).

public Dimension preferredSize()
   return new Dimension(200, 100);

Listing 5: The preferredSize() method

Of course there is nothing to prevent the preferred size of a component from varying dynamically, as in the applet in Figure 6. This applet contains two buttons. Clicking on a button causes its size to increase at the expense of the other button. The source code for the applet in Figure 6 is available here.

Figure 6: Two competing buttons

The preferredSize() method for the applet in Figure 6 looks like the method in Listing 6. The variable dim is a private instance variable.

public Dimension preferredSize()
   return new Dimension(dim);

Listing 6: The preferredSize() method

The private instance variable, dim, can be set or changed via a member function like the one in Listing 7:

public void newPreferredSize(Dimension dim)
   this.dim = new Dimension(dim);

Listing 7: Setting the preferred size

The container must follow up a call to the method above by calling its layout() method to layout the components after the size of one change.

Using Insets

Insets, like the preferred size, can be used to give the user interface a more appealing spatial arrangement. A container's insets specify the amount of padding around the inside border of the container.

Like the preferred size, insets are specified for a container by redefining the insets() method:

public Insets insets()

The insets() method takes no parameters and returns an instance of the Insets object. The Insets class has four fields, that specify the number of pixels to inset the components from the top, left, bottom, and right, respectively.

The code in Listing 8 depicts a typically redefined insets() method from a new component class. The code in this example indicates that the components contained within should be set 5 units from the border of the container on each side.

public Insets insets()
   return new Insets(5, 5, 5, 5);

Listing 8: The insets() method

The applets in Figure 7 graphically illustrate the difference in appearance caused by specifying insets. The source is available here and here.

Figure 7: Two Buttons, with and without insets

Enable and Disable Components

Components in a user interface that are not currently available but may become available in response to the proper input from the user are frequently disabled. They are displayed, but they are "grayed out" and are unresponsive to user input. By disabling components, rather than hiding them, the user is able to see what operations are possible, even if they are not currently available.

The Component class provides three methods for enabling or disabling components:

public void enable()
public void disable()
public void enable(boolean boolCondition)

The first two methods enable or disable a component. The third method either enables or disables a component depending on the value of the boolean parameter. The code in Listing 9 illustrates how to enable and disable a component.

Button b = new Button("Press Me");
b.enable();      // or,
b.disable();     // or,

Listing 9: The enable() and disable() methods

The code in Listing 9 will work with any component with only slight modifications.

In order to effectively use these methods, a program must keep track of the state of the user interface. As the user interacts with the user interface, the internal state changes. This is reflected in components changing from disabled to enabled or vise versa.

Consider the applet in Figure 8. In this applet, the buttons "Add" and "Delete" are disabled unless the user has entered text into the text field. This prevents the inadvertent activation of these two buttons if the text field contains no data. As soon as even one character is typed into the text field, the text field enables the two buttons, which then assume their proper roles. If, at any time, all of the text in the field is deleted, the two buttons are once again disabled. The source for the applet in Figure 8 is available here.

Figure 8: Selective enabling

In addition to their modified visual appearance, disabled components no longer receive events from the Java run-time system. The events immediately pass to the enclosing container.


Next month's "How to Java" column will depart from the topic of the AWT and will examine the subject of Observers and Observables as they pertain to implementing the Model-View paradigm in Java.

Todd Sundsted has been writing programs since computers became available in desktop models. Though originally interested in building distributed object applications in C++, Todd moved to the Java programming language when Java became the obvious choice for that sort of thing. In addition to writing, Todd provides Internet and Web consulting services to companies in the southeastern United States.