The Wayback Machine - https://web.archive.org/web/20111209132212/http://www.ibm.com/developerworks/java/library/j-dynui/
Skip to main content

If you don't have an IBM ID and password, register here.

By clicking Submit, you agree to the developerWorks terms of use.

The first time you sign into developerWorks, a profile is created for you. Select information in your developerWorks profile is displayed to the public, but you may edit the information at any time. Your first name, last name (unless you choose to hide them), and display name will accompany the content that you post.

All information submitted is secure.

The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerworks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

By clicking Submit, you agree to the developerWorks terms of use.

All information submitted is secure.

Dynamic interface design with Swing

A trip to the outer regions of the Swing API

Peter Seebach (dw-java@seebs.net), Writer, Freelance
Peter Seebach is sure there was a File menu around here somewhere. He's been using computers a little longer than he's been programming them, and he still thinks GUIs are a little newfangled.

Summary:  The Swing UI toolkit makes it possible, though not always easy, to update user interfaces dynamically in response to events or user actions. This article reviews some of the common ways you can build UIs that update dynamically, a few pitfalls you might encounter along the way, and some principles to help you decide when this is the right approach for the job.

Date:  25 Apr 2006
Level:  Introductory
Also available in:   Chinese  Japanese  Vietnamese

Activity:  21242 views
Comments:  

The Swing toolkit offers a variety of tools for creating user interfaces and an almost bewildering array of options for modifying those interfaces during a program's lifetime. Careful use of these features can result in interfaces that dynamically adapt to the user's needs and simplify interaction. Careless use of the same features can lead to very confusing or even completely unusable programs. This article introduces the technology and philosophy of dynamic UIs and gives you a leg up on building effective ones. You'll modify source code that's based on the SwingSet2 demo application provided with the Sun JDK (see Resources); this application's UI uses a number of dynamic features and serves as an excellent starting point for understanding them.

Disabling a widget

The simplest form of dynamic UI is one that grays out menu items or buttons that are unavailable. Disabling UI widgets works the same way for all widgets; the setEnabled() function is a feature of the Component class. Listing 1 shows the code for disabling a button:


Listing 1. Disabling a button

button.setEnabled(false);

Even the simple action of graying out an unusable menu option or dialog box button involves trade-offs for the user. Although a grayed-out button immediately informs users that a particular action can't be performed, it doesn't tell them why. This can be a problem for users who might not understand the reason (see General principles).

Easy enough, as you can see. The question is when you should enable or disable a button. One common design decision is to disable a button when it's not applicable. For instance, many programs disable the Save button (and any corresponding menu item) when a file hasn't been modified since the last time it was saved.

The major caveat with disabling buttons is to remember to re-enable them at an appropriate time. For example, if there's a confirmation step between clicking on a button and the completion of its action, the button should be re-enabled even if confirmation fails.


Adjusting ranges

Sometimes an application needs to adjust the range of a numeric widget, such as a Spinner or Slider, dynamically. This can be a lot more complicated than it looks. Sliders, in particular, have secondary features -- ticks, tick spacing, and labels -- that might need to be adjusted along with the range to avoid catastrophic ugliness.

The SwingSet2 demo doesn't directly do any of this, so you'll modify it by attaching a ChangeListener to one slider that can modify another. Enter the new SliderChangeListener class, shown in Listing 2:


Listing 2. Changing a slider's range

class SliderChangeListener implements ChangeListener {
       JSlider h;

       SliderChangeListener(JSlider h) {
              this.h = h;
       }

       public void stateChanged(ChangeEvent e) {
           JSlider js = (JSlider) e.getSource();
           int i = js.getValue();
           h.setMaximum(i);
           h.repaint();
       }
}

When the third horizontal slider is created (the one that in the original demo has tick marks every unit and labels at 5, 10, and 11), a new SliderChangeListener is also created, passing the slider as the constructor argument. When the third vertical slider (with a range of 0 to 100) is created, the new SliderChangeListener is added to it as a change listener. This works about as expected: Adjusting the vertical slider changes the range of the horizontal slider.

Unfortunately, the ticks and labels don't work well at all. The labels every five ticks work out okay as long as the range doesn't get too large, but the extra label at 11 quickly becomes a usability problem, as shown in Figure 1:


Figure 1. Labels running together

Updating ticks and labels

The obvious solution would be simply to set the tick spacing on the horizontal slider whenever its maximum value is updated, as shown in Listing 3:


Listing 3. Setting tick spacing

// DOES NOT WORK
int tickMajor, tickMinor;
tickMajor = (i > 5) ? (i / 5) : 1;
tickMinor = (tickMajor > 2) ?  (tickMajor / 2) : tickMajor;
h.setMajorTickSpacing(tickMajor);
h.setMinorTickSpacing(tickMinor);
h.repaint();

Listing 3 is correct as far as it goes, but it doesn't result in any change to the labels drawn on screen. You must set the labels separately, using setLabelTable(). Adding one more line fixes it:

h.setLabelTable(h.createStandardLabels(tickMajor)); 

This still leaves you with the odd label at 11 that was set up originally. The intent, of course, is to have a label always at the right end of the slider. You can do this by removing the old label (before setting the new maximum value) and then adding a new one. This code almost works:


Listing 4. Replacing labels

public void stateChanged(ChangeEvent e) {
       JSlider js = (JSlider) e.getSource();
       int i = js.getValue();

       // clear old label for top value
       h.getLabelTable().remove(h.getMaximum());

       h.setMaximum(i);

       int tickMajor, tickMinor;
       tickMajor = (i > 5) ? (i / 5) : 1;
       tickMinor = (tickMajor > 2) ? (tickMajor / 2) : tickMajor;
       h.setMajorTickSpacing(tickMajor);
       h.setMinorTickSpacing(tickMinor);
       h.setLabelTable(h.createStandardLabels(tickMajor));
       h.getLabelTable().put(new Integer(i),
       new JLabel(new Integer(i).toString(), JLabel.CENTER));
       h.repaint();
}

If I've told you once, I've told you twice

By almost I mean that, although the code in Listing 4 removes the label at 11, it doesn't add the new label at i; instead, you see only the labels at tickMajor intervals. The solution is rather shocking at first:


Listing 5. Forcing a display update

h.setLabelTable(h.getLabelTable());

This seemingly pointless operation actually has a significant effect. The labels for a slider are generated whenever the label table is set. There's no special callback for modifications to the table, so new values added to the table don't necessarily have an effect; the apparent no-op in Listing 5 has the side effect of letting Swing know it must update the display. (Lest you think I invented this myself, notice that the original SwingSet code includes such a call.)

This leaves only one problem. The very reasonable desire to ensure that a label appears at the end of the slider sometimes puts two labels immediately adjacent to each other, or even overlapping, as shown in Figure 2:


Figure 2. Overlapping labels at the end of the slider

A number of solutions to this problem are possible. One is to write your own code to populate the label table with values and stop the sequence earlier so that the last label in the sequence is somewhat separated from the end of the slider. I'll leave this one as an exercise for you.


Updating menus

In many cases, it's quite practical to restrict menu modifications to enabling and disabling menu items. This approach is subject to the general caveat that applies to disabling items: Avoid leaving your program in an unusable state accidentally by disabling crucial items.

It's also possible to add or remove menu items or submenus. It's not as easy to modify a JMenuBar; there's no interface for removing or replacing individual menus from the bar. If you want to modify a bar (other than by adding new menus to its right end), you need to make a new bar and replace the old one with it.

Modifications to individual menus take effect immediately; you don't need to build a menu before attaching it to a bar or another menu. When you need to modify your selection of menu options, the easiest way is to modify a given menu. Still, you might want to add and remove entire menus, and it's not particularly hard to do so. Listing 6 shows a simple example of a method to insert a menu into a menu bar before a given index. This example assumes that the JMenuBar to be replaced is attached to a JFrame object, but anything that lets you get and set menu bars will work the same way:


Listing 6. Inserting a menu into a menu bar

public void insertMenu(JFrame frame, JMenu menu, int index) {
       JMenuBar newBar = new JMenuBar();
       JMenuBar oldBar = frame.getJMenuBar();
       MenuElement[] oldMenus = oldBar.getSubElements();
       int count = oldBar.getMenuCount();
       int i;

       for (i = 0; i < count; ++i) {
              if (i == index)
                     newBar.add(menu);
              newBar.add((JMenu) oldMenus[i]);
       }
       frame.setJMenuBar(newBar);
}

This code is not what I first attempted; this final version, nicely fixed up so that it works, reflects a handful of interesting quirks. It might seem at first that the obvious way to implement this would be to use getComponentAtIndex(), but that's been deprecated. Luckily, the getSubElements() interface is good enough. The cast to JMenu for newBar.add() is probably safe, but I don't like it. The getSubElements() interface works on menus, not just menu bars; menus can have subelements of several types, but JMenus are the only elements you can add to a JMenuBar. So you must cast the element to a JMenu to pass it to the JMenuBar.add() method. Unfortunately, if a future API revision lets you add elements of types other than JMenu to a JMenuBar, it will no longer be necessary, or even safe, to cast the returned elements to JMenu.

The code in Listing 6 reflects one other rather subtle interface quirk; the menu count must be cached in advance. When the menus are added to the new bar, they're removed from the old one. The code in Listing 7, although it looks similar, doesn't work; the loop terminates early:


Listing 7. Loop terminating too soon

// DOES NOT WORK
for (i = 0; i < oldBar.getMenuCount(); ++i) {
       if (i == index)
              newBar.add(menu);
       newBar.add((JMenu) oldMenus[i]);
}

Users benefit from consistency in an interface; a given menu should always be in the same place. For user convenience, try to keep menus that might change to the right end of your list of menus, with menus that don't change placed in fixed positions on the left. Similarly, when at all possible, keep items in the same position within a menu. A grayed-out menu item is less disruptive to the user than one that comes and goes, because other items on the menu aren't being moved around.

The loop in Listing 7 copies only half of the items. For instance, if four items are on the menu bar to begin with, it copies the first two. After it copies the first one, i is 1 and getMenuCount() returns 3; after it copies the second, i is 2 and getMenuCount() returns 2, so the loop ends. I couldn't find any documentation of the "feature" whereby adding a menu to one bar removes it from another, so it might not be intentional. Still, it's easy enough to work around.

Removing a menu from a menu bar is a little easier; just copy all of the other menus over from the old bar to the new one, and you're done. Easy!

If your interface uses a lot of dynamic menu updates, it might be better to create a set of menu bars and switch between them, rather than updating them on the fly all the time. However, if you're changing menus that much, you are probably also driving your users absolutely crazy.

Errata: During the drafting of this article, I overlooked the list of inherited methods of the JMenuBar class. In fact, it has both remove and add methods available that can remove or insert at a specified index. The additional lesson is: Check the inherited methods, not just the class-specific methods.


Window resizing

It's a great blessing that, for the most part, window resizing happens automatically. But you need to take a few implications of resizing into account. Button bars, menu bars, and similar features can become problematic in a very small window. Graphical panels that your program manages itself need to respond to resize events. Let Swing handle packing UI elements, but keep an eye on the size of the component; don't just get the dimensions once and keep using those values.

More subtly, some design decisions, such as frequency of ticks on sliders, might reasonably be updated in response to window resize events. A slider 100 pixels wide can't have as many readable labels as a slider 400 pixels wide. You might want to take some of your UIs even further by adding whole new convenience features on larger displays.

Nonetheless, for the most part, you can ignore window resizing. What you should not do is prevent or override it unnecessarily. Marginal convenience in your layout code is not a necessity. A minimum window size may be justifiable, but let people make windows as large as they want.


General principles

The Swing toolkit offers a great deal of flexibility in UI design. Used carefully, the option of updating an interface on the fly can simplify that interface dramatically; presenting a menu only when its options apply, for instance, can be easier for users.

Unfortunately, some of the API features that make this approach possible are a little quirky, and the side effects and interactions are not always as well documented as you might like. If you have an idea for a dynamic interface, be ready to spend a bit of extra time on debugging. You might well be operating out in the corners of the Swing library and find yourself needing to work around surprising behaviors and/or bugs.

Don't let the lack of an obvious implementation discourage you. As this article's JMenuBar example shows, even when there's no support for a task in the API, you might be able to implement it yourself, albeit a bit indirectly.

Try not to go overboard. Dynamic UIs are at their best when they make inherent restrictions clearer to the user. Ideally, a user might not even notice that an interface is changing. If the only time they can use a program's Object menu is when they have an object selected, they won't mind that the menu isn't there the rest of the time.

On the other hand, if a possibility exists that the user can't guess why an option isn't available, it might be better to let the user attempt an action and get an informative error message. This is particularly important for some actions. If the save option is disabled, that doesn't help much if I want to save my data. The program probably thinks it's already saved, but why not let me save it anyway? And if there's a specific reason why I can't save the file, I probably want to know what it is.

Interface design, despite years of study, is still in many ways a young field. Experiment a little. Dynamic changes to UIs can be a wonderful feature that makes them clearer, simpler, and more responsive. Adding dynamic UI features requires anything from a few minutes' work to a substantial time commitment.


Resources

Learn

Discuss

About the author

Peter Seebach

Peter Seebach is sure there was a File menu around here somewhere. He's been using computers a little longer than he's been programming them, and he still thinks GUIs are a little newfangled.

Report abuse help

Report abuse

Thank you. This entry has been flagged for moderator attention.


Report abuse help

Report abuse

Report abuse submission failed. Please try again later.


developerWorks: Sign in

If you don't have an IBM ID and password, register here.


Forgot your IBM ID?


Forgot your password?
Change your password


By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Select information in your developerWorks profile is displayed to the public, but you may edit the information at any time. Your first name, last name (unless you choose to hide them), and display name will accompany the content that you post.

Choose your display name

The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

(Must be between 3 – 31 characters.)


By clicking Submit, you agree to the developerWorks terms of use.

 


Rate this article

Comments

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology, Web development, Architecture
ArticleID=109561
ArticleTitle=Dynamic interface design with Swing
publish-date=04252006
author1-email=dw-java@seebs.net
author1-email-cc=jaloi@us.ibm.com

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

For articles in technology zones (such as Java technology, Linux, Open source, XML), Popular tags shows the top tags for all technology zones. For articles in product zones (such as Info Mgmt, Rational, WebSphere), Popular tags shows the top tags for just that product zone.

For articles in technology zones (such as Java technology, Linux, Open source, XML), My tags shows your tags for all technology zones. For articles in product zones (such as Info Mgmt, Rational, WebSphere), My tags shows your tags for just that product zone.

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Special offers

Morty Proxy This is a proxified and sanitized view of the page, visit original site.