Sun Java Solaris Communities My SDN Account Join SDN
 
Article

Using the Swing Action Architecture

 

Using the Swing Action Architecture

by Mark Davidson

This article briefly discusses the Swing Action architecture and how you can use it to implement shared behavior between two or more user interface components like menu items and toolbar buttons. New Action features added to J2SE version 1.3 are highlighted. How to send a description of the Action to a status bar when the mouse moves over an action-based component is shown. A strategy is presented to delegate the Action.actionPerformed() method to another class. Finally, this article presents a simple application which uses button graphics and text from the Java Look and Feel Graphics Repository (JLF GR) and demonstrates the mechanisms discussed.

What is an Action?

The javax.swing.Action interface extends ActionListener and is an abstraction of a command that doesn't have an explicit UI component bound to it. The action architecture is an implementation of the Command design pattern. This is a powerful concept because it allows the separation of the controller logic of an application from its visual representation. This is useful because applications can be easily configured to use different UI elements without having to rewrite the control or callback logic.


    public interface Action extends ActionListener {

    public static final String DEFAULT = "Default";

    public static final String NAME = "Name";

    public static final String SHORT_DESCRIPTION = "ShortDescription";

    public static final String LONG_DESCRIPTION = "LongDescription";

    public static final String SMALL_ICON = "SmallIcon";

    public static final String ACTION_COMMAND_KEY = "ActionCommandKey";

    public static final String ACCELERATOR_KEY="AcceleratorKey";

    public static final String MNEMONIC_KEY="MnemonicKey";

    public Object getValue(String key);

    public void putValue(String key, Object value);

    public void setEnabled(boolean b);

    public boolean isEnabled();

    public void addPropertyChangeListener(PropertyChangeListener l);

    public void removePropertyChangeListener(PropertyChangeListener l);

}




The Action interface defines a set of keys, such as Action.SHORT_DESCRIPTION, which can be used to configure an Action. For example:

    cutAction.putValue(Action.SHORT_DESCRIPTION, "Cut Command");  

These keys and methods are used to access for properties such as the mnemonic, description and icon which represent the action.

The AbstractAction class implements the Action interface and adds property change support and storage and retrieval for key values. All that remains is to subclass the AbstractAction to add specific key values (name, description or icon) and implement the actionPerformed() method.

Many components, such as JButton and JMenuItem, use an action as a template for construction. Some Swing container components, such as JToolBar and JMenu, have add methods which will take an Action as an argument and create a component from that action.

Components which have been constructed from the same Action will share some properties such that the state of the components will be in sync with the state of the Action. For example, if the action becomes disabled, then all components which use that action will also be disabled. When a property on an action is changed, it fires a property change event. All components that have been constructed from that Action will have implemented a property change listener which will act on the event and change the state of the component.

Disabling an Action

One of the distinct advantages of using actions is that when an action is disabled, all the components which use the action will become disabled.

When setEnabled() in AbstractAction is invoked it fires a property change event to registered property change listeners. A property change listener in AbstractButton and JMenuItem listens to the "enabled" property change and enables or disables the control. When an action is enabled or disabled, the property change listener which was added to the action in AbstractButton will enable or disable the corresponding Swing component.

Changes to the Action Architecture in J2SE version 1.3.

In Java 2 Standard Edition (J2SE) version 1.3, two convenient public methods were added to AbstractButton - the base class for most action based UI components. These methods are:

Other changes include adding a constructor to the button and menu classes which uses an action as an argument to configure itself.

The background for implementing these changes is described in the Action Changes for JDK 1.3 document. The basic motivation for these changes was to allow more control over the configuration of components which are based on Actions and move the existing action construction code into a common base class such as AbstractButton.

There were also some more keys added to the Action interface:

  • ACCELERATOR_KEY - The key used for storing a KeyStroke to be used as an accelerator for the action.
  • ACTION_COMMAND_KEY - The key used to determine the command string for the ActionEvent.
  • DEFAULT - Can be used as the default storage retrieval key for one of the object's properties (text or icon).
  • MNEMONIC_KEY - The key used for storing a key code to be used as a mnemonic for the action.

The ActionDemo Application

The ActionDemo application is a simple application which demonstrates the following Action mechanisms:

  • Components created from shared actions will be enabled or disabled in conjunction with the state of the action.
  • A mouse entered event over an action based component will send the value of the long description key of the Action to a status bar.
  • The actionPerformed() method for all the actions is delegated to another handler.

The demo also uses graphics and text from the Java Look and Feel Graphics Repository. The JLF Graphics Repository is an attempt to provide a consistent set of graphics for use in Java Applications which conform to the Java Look and Feel Design Guidelines.

The ActionDemo.zip archive contains all the source to this application and a convenient jar file which will allow execution of the application.

The ActionDemo requires the setAction/getAction API that was introduced in Java 2 Standard Edition (J2SE) version 1.3. You can download J2SE 1.3 from the Java 2 Standard Edition Page.

Unpack the zip file into a directory. To execute:

> java -jar ActionDemo.jar

Click to Launch!

You can also execute the ActionDemo using Java Web Start by clicking on the icon to the left.

Note: you must have Java Web Start installed on the client machine and it can be downloaded from the following link

Java Web Start is an application deployment tool from Java Software and acts as a helper application to launch Java applications over the Internet.

You can recompile the sources with this command:

> javac *.java

To run the compiled sources, you should have the JLF GR jar file in your classpath so that you can get the icons:

> java -cp .;jlfgr-1_0.jar ActionDemo [Win32] 
% java -cp .:jlfgr-1_0.jar ActionDemo [UNIX] 

Most of the mechanisms discussed in the remainder of the article have been implemented in the ActionDemo.java source file.

Enabling and Disabling of Components Which Share an Action

When the bound property "enabled" of an Action is enabled or disabled, it sends a property change event to its property change listeners. In the case of the Actions, the AbstractButton has an inner class property change listener which enables or disables the corresponding component.

In the ActionDemo application, the set of actions are initialized in the method initActions(). The same set of Actions are shared between the menu, toolbar and popup menu and are used to construct the corresponding UI controls in the createMenu(), createToolBar() and createPopupMenu() methods.

The "Enable All Actions" check box and the anonymous ItemListener class that were constructed in the createPanel() method does the actual enabling or disabling of the set of actions. This will automatically change the state of the corresponding components to reflect the current state of the action.

Sending a Description of an Action to a Status Bar

One often requested feature is an architecture that will support sending context messages to a status bar when the mouse is over menu items or toolbar buttons. The current action architecture will support that behavior quite elegantly.

When an action is added to a container like a JToolBar or a JMenu, the component that was added to the container is returned. A shared instance of a MouseAdapter is added to all the returned components. When the mouse is moved over the component, a mouse entered event is sent to the mouse adapter and the mouseEntered() method on the adapter will get the value of the Action.LONG_DESCRIPTION key of the component to set the text of a status bar.



   public void mouseEntered(MouseEvent evt)  {

       if (evt.getSource() instanceof AbstractButton)  {

           AbstractButton button = (AbstractButton)evt.getSource();

           // getAction is new in JDK 1.3

           Action action = button.getAction(); 


           if (action != null)  {

               Object message = action.getValue(Action.LONG_DESCRIPTION);

               label.setText(message.toString());

           }

       }

   }




AbstractButton is the common parent of JButton and JMenuItem. The behavior of getting an action from its component was greatly simplified because of the new 1.3 getAction method on the AbstractButton.

Delegation of the Action.actionPerformed method

For actions which extend the AbstractAction class, the only method that requires implementation is the actionPerformed method. The implementation of this method will be executed when a user action is performed on the component.

In some large scale applications, the developer may want to delegate the action callbacks to an intermediate class instead of the action itself. An example could be that a set of actions operates on some shared data. In the example, the Cut, Copy and Paste actions operate on the JTextArea component to get access to the system clipboard.

In the example, the ActionDemo.actionPerformed() method acts as the delegate in which all the individual actionPerformed() methods are sent. The JLFAbstractAction class is the base class for all the actions in the ActionDemo application. JLFAbstractAction extends AbstractAction and defines registration and forwarding methods to the ActionListener delegate as well as some accessor methods to the Action keys. The ActionListener delegate (ActionDemo) is added to each instance of a subclassed JLFAbstractAction.

When the actionPerformed() method is called on the JLFAbstractAction, the ActionEvent is modfied by stuffing the value of the ACTION_COMMAND_KEY for that action and forwarded to the delegate by calling its actionPerformed method:


    public void actionPerformed(ActionEvent evt)  {

        if (listeners != null) {

            Object[] listenerList = listeners.getListenerList();



            // Recreate the ActionEvent and stuff the value of the 

            // ACTION_COMMAND_KEY.

            ActionEvent e = new ActionEvent(evt.getSource(), evt.getID(), 

                              (String)getValue(Action.ACTION_COMMAND_KEY));

            for (int i = 0; i <= listenerList.length-2; i += 2) {

                ((ActionListener)listenerList[i+1]).actionPerformed(e);

            }

        }

    }


The actionPerformed() method in the delegate ActionDemo class retrieves the action command attribute from the ActionEvent and compares it to each action command from the set of actions that it manages. If a match is found then the code for that action is executed.


    public void actionPerformed(ActionEvent evt)  {

        String command = evt.getActionCommand();

        

        // Compare the action command to the known actions.

        if (command.equals(aboutAction.getActionCommand()))  {

            // The about action was invoked

            JOptionPane.showMessageDialog(this, 

                           aboutAction.getLongDescription(), 

                           aboutAction.getShortDescription(), 

			   JOptionPane.INFORMATION_MESSAGE);

        } else if (command.equals(cutAction.getActionCommand())) { 

	    textArea.cut();

        } else if (command.equals(copyAction.getActionCommand())) { 

	    textArea.copy();

        } else if (command.equals(pasteAction.getActionCommand())) { 

	    textArea.paste();

        }

    }


Summary

The javax.swing.Action interface defines a Command design pattern which allows for the abstraction of behavior away from the user interface components. The setAction/getAction API added to AbstractButton for Java 2 Standard Edition (J2SE) version 1.3. was added for greater flexibility in configuring components from actions.

A small application was introduced to illustrate how the new J2SE 1.3 API and keys could be used in a application to:

  • Enable or disable a set of controls based on actions.
  • Send a description of the action to a status bar in response to mouse entered events.
  • Delegate the action performed method of the actions to another class.

Please contact us.

Last Revision June 28, 2000