Skip to main content

Asserting Control Over the GUI: Commands, Defaults, and Resource Bundles

January 31, 2005

{cs.r.title}









Contents
Swing Event Handling Basics
Trampolines
A Trampoline Based on EventHandler
Swing Actions: Sharing Behavior and Visuals
Localizing Actions with Resource Bundles
Commands: International Actions
Disclaimers and Thanks
Resources

This article is about defining Swing application behavior. It's about
combining low-level J2SE primitives, like Actions,
ResourceBundles, and UIDefaults, in a way
that's appropriate for moderately large desktop Java applications.
The article begins with a review of event handling, Swing actions,
and conventional approaches for defining them. The second half
of the article demonstrates how one can separate an
Action's visual properties into a
ResourceBundle that's loaded through Swing's
UIDefaults API. In addition to enabling localization,
this approach shifts some of the GUI from code to a declarative
representation. That's an advantage for large applications, because
the declarative aspect of the application can be developed
independently from the code.

Swing Event Handling Basics

Swing components manage lists of observers, called event listeners,
for different types of events. There are listeners for event
types like raw mouse and keyboard input, as well as for component state
changes, such as changes to a component's properties. Swing components
notify their listeners by applying each one to an event that defines
what happened. The most common Swing event type is called
ActionEvent. All of the basic controls, like buttons and
menu items, notify their ActionListeners when they detect
a triggering input gesture such as a mouse button press. To complete
the review, here's an example that adds an ActionListener
to a JButton:

final JButton myButton = new JButton("Press Me");
ActionListener doMyAction = new ActionListener() {
    private int nActions = 0;
    public void actionPerformed(ActionEvent e) {
        nActions += 1;
        myButton.setText("Pressure: " + nActions);
    }
};
myButton.addActionListener(doMyAction);

There's much to like about this. We're using a class to define the
button's behavior, which means we can encapsulate any state associated
with handling the ActionEvent (the variable
nActions). All of the event handling linkage shown here
is statically type checked by the compiler, so there will be fewer
surprises at runtime.

Trampolines

It seemed as though it was only seconds after the debut of this
approach to event handling (in about 1996) that developers had created
applications with thousands of controls and a commensurate number of
EventListener subclasses. At the time, there was a
substantial fixed overhead cost (size) for each class, and this made
developers who were trying to deliver their apps to dialup modem users
pretty unhappy. Once the app arrived on a user's machine, the bloat
also adversely affected startup time and footprint. This led to some
appalling hacks, notably using a single listener class for many
components by switching on the component's label text or the component
itself:

ActionListener doMyActions = new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        Object src = e.getSource();
        if (src == myButton1) {
           doAction1(e);
        }
        // Don't do this. Ever.
        else if ((JButton)(src).getText() == "Button2") {
           doAction2(e);
        }
        // etc ...
    }
};

The effects of localization and changes to the component hierarchy
ensure a spot in the programmer's hall of shame for employing logic
like this. To be fair to those hall of shame inductees, some of the
code that followed these patterns was the legacy of AWT's original
event model. Examples and documentation advocating this approach can
be found in the Java 1.0 releases.

A common and sensible alternative to this kind of if/else
mess is to create listener "trampoline" classes that can call a method
like doActionN on a well-known object using
reflection. We call such a class an event trampoline because the
event just bounces from the trampoline's method to the real handler.
For example, if we create an object that contains all of
our action methods, a singe ActionListener
trampoline will suffice:

private final Object actions = new Object() {
    public void doAction1(ActionEvent e) { ... }
    public void doAction2(ActionEvent e) { ... }
    ...
};
private class ActionTrampoline extends ActionListener {
    private final String name;
    ActionTrampoline(String name) { this.name = name; }
    public void actionPerformed(ActionEvent e) {
        // exception handling elided
Class cls = actions.getClass();
        Method m = cls.getMethod(name, ActionEvent.class);
        m.invoke(actions, e);
    }
}
button1.addActionListener(new ActionTrampoline("doAction1"));
button2.addActionListener(new ActionTrampoline("doAction2"));
...

To generalize this example, you'd need one trampoline class per
EventListener type employed by your application. Chances
are good that you'd elect to make the actions class,
which is private inner and anonymous in the previous example, an
ordinary top-level class. Similarly, it's usually not a good idea to
dump all event handling methods into a single class; it's often
helpful to segregate them according to purpose, requirements for
access to shared state, and so on.

A Trampoline Based on EventHandler

The EventHandler interface, included in the 1.4 Java
release, makes it possible to create this kind of trampoline (as well
as more elaborate variations) automatically and at runtime. Here's
the previous example written using EventHandler:

public class MyActions {
    public void doAction1() { ... }
    public void doAction2() { ... }
    ...
};
Class alc = ActionListener.class;
button1.addActionListener(
    EventHandler.create(alc, actions, "doAction1"));
button2.addActionListener(
    EventHandler.create(alc, actions, "doAction2"));

This approach has the same space-saving advantages as the first
example did. EventHandler uses

java.lang.reflect.Proxy

to create just one (internal to the
implementation) trampoline class (at runtime) for each listener
interface it encounters. And EventHandler can be used to
create listeners of any type; see

the EventHandler Javadoc

for more information. EventHandler, which was created to
support IDEs that allowed users to connect data derived from a GUI
control or event to a GUI-agnostic controller method, doesn't provide
any special support for ActionListeners. Most Swing GUIs
are mostly ActionListeners, so it makes sense to provide
additional support for them and Swing does, in the Action class.

Swing Actions: Sharing Behavior and Visuals

A Swing Action is just an ActionListener
with a little hashtable of properties that are used to define the
appearance of a control that has the action. Several controls can
share one action; e.g., the same action might appear in a pop-up
right-button menu and on a toolbar. Actions keep track of the
controls they've been added to, and one can disable all of them, by
disabling the action (see

Action.setEnabled()

). Menus and
toolbars even support adding Action objects by creating a
JMenuItem or a JButton, respectively. Just
to complete the review, here's an example of using an
Action that disables itself. The
AbstractAction constructor argument becomes the value of
the action's Action.NAME, which is used for the title of
buttons and menus and menu items.

Action action = new AbstractAction("Disable All") {
   public void actionPerformed(ActionEvent e) {
       setEnabled(false);
   }
};
KeyStroke accKey = KeyStroke.getKeyStroke("ctrl D"));
action.putValue(Action.SHORT_DESCRIPTION, "the tooltip");
action.putValue(Action.MNEMONIC_KEY, KeyEvent.VK_D);
action.putValue(Action.ACCELERATOR_KEY, accKey);

JButton button = new JButton(action);
JMenu menu = new JMenu(action);
menu.add(action);
JToolBar toolbar = new JToolBar();
toolbar.add(action);

The problem with this is that we're back to square one for big
applications with hundreds of ActionListeners. Once
again, we're defining one class for each listener.

Localizing Actions with Resource Bundles

If you're at all sensitive to the internationalization requirements
for applications, you'll have also noticed that we've wired the
action's tooltip and label text down in the code. That's not good,
either. The way to overcome both problems is to create an Action
trampoline class, like the one we defined earlier, and a subclass of
Action that configures itself from a resource file.

Java applications store localized text and other data in tables of
key/value pairs called ResourceBundles. Resource bundles
are usually loaded by the app at initialization time with a
statement like this:

ResourceBundle rb = 
    ResourceBundle.getResource("MyResources");

The getResource call looks for a class or a
.properties file named
MyResources_. An app developer would
store English localizations in a
file called MyResources_en.properties (or a
ResourceBundle subclass called
MyResources_en),
German values in MyResources_de.properties,
and MyResources.properties (no suffix) would
contain values to use if the default locale didn't match
a localized resource file.

The Swing UIDefaults class supports
ResourceBundles. An application can load its
ResourceBundles into an instance of
UIDefaults and then use typed methods, like
UIDefaults.getString(), to look up localized values for
Action properties like
Action.SHORT_DESCRIPTION. The only useful types that the
current UIDefaults class lacks are KeyStroke and
KeyCode, so we'll add those in a UIDefaults
subclass called AppDefaults:

public class AppDefaults extends UIDefaults {
    public KeyStroke getKeyStroke(String key) {
        return KeyStroke.getKeyStroke(getString(key));
    }
    public Integer getKeyCode(String key) {
        KeyStroke ks = getKeyStroke(key);
        return (ks != null)
            ? new Integer(ks.getKeyCode())
            : null;
    }
}

Commands: International Actions

Now we can define an Action trampoline class, called
Command, that initializes its Action
properties from a ResourceBundle property file through an
instance of AppDefaults. That means that instead of
initializing component labels or tooltips in the code, we'll define
all of them in a .properties file. For example, our
application's quit Command is defined by the following property file
entries:

quit.Name=Quit
quit.AcceleratorKey=control Q
quit.MnemonicKey=Q
quit.ShortDescription=Exit the application

The application creates and uses the quit command as you might expect. However, note that we've created an AppDefaults object and passed
it along to the Command class:

public class MyCommands {
    public void quit() { ... }
}
AppDefaults defaults = new AppDefaults();
defaults.addResourceBundle("MyApplication");
...
MyCommands commands = new MyCommands();
Command quitCommand = new Command("quit", commands, defaults);
myMenu.add(quitCommand);  // invokes commands.quit();

The Command class is similar to
ActionTrampoline except that rather
than invoking a method on a implicit and private target object,
the target (commands in the example above) is explicit.
The other important difference is that Commands
initialize themselves from an AppDefaults object:

private final static String actionKeys[] = {
    Action.NAME,
    Action.SHORT_DESCRIPTION,
    Action.LONG_DESCRIPTION,
    Action.SMALL_ICON,
    Action.ACTION_COMMAND_KEY,
    Action.ACCELERATOR_KEY,
    Action.MNEMONIC_KEY
};
public Command(Object target, String methodName, AppDefaults defaults) {
    super(methodName);  // methodName is the default label text
    this.target = target;
    this.methodName = methodName;
    for(String k : actionKeys) {
        String mk = methodName + "." + k;
        if (k == Action.MNEMONIC_KEY) {
            putValue(k, defaults.getKeyCode(mk));
        }
        else if (k == Action.ACCELERATOR_KEY) {
            putValue(k, defaults.getKeyStroke(mk));
        }
        else if (k == Action.SMALL_ICON) {
            putValue(k, defaults.getIcon(mk));
        }
        else {
            putValue(k, defaults.get(mk));
        }
    }
}

So there you have it. We've taken a pretty comprehensive tour of the
support in Swing for binding GUI controls to behavior and have
concluded with a some small but useful classes that separate a
control's presentation into a ResourceBundle property file.

Disclaimers and Thanks

None of the material I've presented here is particularly novel. Variations
on this theme have been produced by many other developers for as long
as Swing has been around. In fact, if I may indulge in an analogy,
action frameworks are to desktop Java developers what web application
frameworks are to our browser-obsessed brethren. There's a reason for
this. In a large application, it's often helpful to create an action
framework (or to extend an existing one) that captures features and
constraints that are unique to the application or its domain.
Frameworks for applications with very large GUIs--think thousands of
forms--often blend an action framework with a declarative
representation for the forms, so that the forms can be loaded, discarded,
and even generated dynamically. Applications that can be extended with
plugin modules often use the kind of loose coupling described here
to simplify morphing a live API.

My goal in writing this article was to show that
a simple combination of the current J2SE classes are sufficient to
bind Swing GUI components to actions in a declarative form
that's easily localized.
I hope that I've presented the material
clearly enough for Swing novices and engagingly enough for old hands.

I'd like to thank Scott Violet for the time spent talking over the
ideas presented here, notably the connection between Actions
and ResourceBundles.

Resources

All of the code for the examples can be found in this source code .zip file.

width="1" height="1" border="0" alt=" " />
Hans Muller is the CTO for Sun's Desktop division. He's been at Sun for over 15 years and has been involved with desktop GUI work of one kind another for nearly all of that time.