Using the Swing Action Architectureby Mark DavidsonThis article briefly discusses the Swing What is an
|
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.
ActionOne 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.
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.ActionDemo ApplicationThe ActionDemo application
is a simple application which demonstrates the following Action
mechanisms:
Action to a
status bar.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.
ActionWhen 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.
Action to a Status BarOne 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.Action.actionPerformed methodFor 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();
}
}
|
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:
Please contact us.
Last Revision June 28, 2000
|
| ||||||||||||