Skip to main content

The Usefulness of Actions

Posted by zixle on November 1, 2005 at 7:58 AM PST

I originally intended to blog on the usefullness of WeakReferences for
client apps. This was to be motivated by the internals of Actions and
why Swing internally needs to use a WeakReference to support
Actions. In writing that blog I ended up spending more time describing
Actions that I've decided to split it into two blogs. This blog
motivates the need for Actions as well as the changes to Actions
in 1.6. Later on I'll followup with why we use WeakReferences to
support Actions and why you might use WeakReferences in similar
situations.

An ActionListener encapsulates application specific functionality that
is usually triggered by a user interface gesture (this isn't always
true, javax.swing.Timer also uses ActionListener). For example
clicking the back button in a browser might notify an implementation
of ActionListener this is responsible for navigating to the previous
page. This might look something like:

private class GoBackActionListener implements ActionListener {
  public void actionPerformed(ActionEvent e) {
    browserComponent.show(getLastURL());
  }
}
backButton.addActionListener(new GoBackActionListener());
backButton.setIcon(...);
backButton.setEnabled(false);

If you've written any Swing/AWT application chances are you have code
like this. It's simple, and it works.

Consider what happens if you want to conditionally enable the button
based on whether you can go back a page. In this case you need a
handle to the backButton so that you can invoke setEnabled on
it. If you also have a menu item with the same functionality you'll
need to keep a handle to that menu item as well, or any other user
interface component that is to use the GoBackActionListener
functionality. This is a bit painful. In particular it leads to all
sorts of references back to the hosting components in your application.

The following code shows configuring a menu item and wiring it to a
GoBackActionListener:

backMenuItem.addActionListener(new GoBackActionListener());
backMenuItem.setText(...);
backMenuItem.setMnemonic(...);
backMenuItem.setDisplayedMnemonicIndex(...);
backMenuItem.setAccelerator(...);
backMenuItem.setEnabled(false);

The Action interface is meant to address these problems. Action not
only encapsulates the functionality part of ActionListener, but also a
description of that functionality as appropriate for user interface
components. Included in that description is whether or not the user
interface component should be enabled. When the enabled state of the
Action changes the user interface components listening to the Action
will update appropriately. If the previous examples were replaced
with Action you would change the enabled state of the Action, and as
a consequence the button and menu item would update appropriately. The
code for this could be something like:

private class GoBackAction extends AbstractAction {
  public GoBackAction() {
    putValue(Action.NAME, "...");
    putValue(Action.LARGE_ICON_KEY, ...);
    putValue(Action.SMALL_ICON, ...);
    putValue(Action.MNEMONIC_KEY, ...);
    putValue(Action.DISPLAYED_MNEMONIC_INDEX_KEY, ...);
    putValue(Action.ACCELERATOR_KEY, ...);
    putValue(Action.SHORT_DESCRIPTION, ...);
    setEnabled(false);
  }
  public void actionPerformed(ActionEvent e) {
    browserComponent.show(getLastURL());
  }
}
Action goAction = new GoBackAction();
backButton.setHideActionText(true);
backButton.setAction(goAction);
backMenuItem.setAction(goAction);

You'll notice a number of differences with this example and the
earlier ones. In particular rather than configuring the text, icon and
other properties of the user interface component the Action contains
all this information. When the Action is attached to the user interface
component the user interface component configures its state from the
Action. For buttons this includes the icon, text, mnemonic, mnemonic
index, tooltip text, action command key and enabled state. This is
nice in that the description is encapsulated in a single place and
does not need to be replicated to all the hosting components.

Those that haven't followed the latest changes in 1.6 will notice some
new keys and methods. In particular DISPLAYED_MNEMONIC_INDEX_KEY,
LARGE_ICON_KEY and setHideActionText are all new in 1.6. Action got
one more new key in 1.6, that is SELECTED_KEY. Included in the 1.6
additions is centralizing the description of what properties each
component supports. See the class level javadoc of Action for all the
specifics, as well as bugs 4133141 and 4626632.

When using ActionListener we had to explicitly update the enabled
state of the user interface components, eg:

backButton.setEnabled(...);
backMenuItem.setEnabled(...);

With Action the code becomes:

goBackAction.setEnabled(...);

Any user interface components attached to the Action will update
appropriately whenever the Action changes! Now we just reference the
Action and not all the various components.

The ability to change the Action directly and have it reflected
appropriately in the user interface component comes in very handy in a
number of situations. For example, undo/redo menu items often have
their text reflect what the undo/redo operation will do. For example,
'Undo Paste' or 'Undo Edit'... When using Action you change the NAME
property of the Action and the menu item will change appropriately.

If this is the first time you've read about Actions you should read
the Swing tutorial section on Actions. It has more examples and
details. In a later blog I'll cover using the new SELECTED_KEY. It's
interesting enough that it warrants it's own blog.

Related Topics >>