Actions
It is common to have multiple ways to activate the same command. The user can choose a certain function through a menu, a keystroke, or a button on a toolbar. This is easy to achieve in the AWT event model: link all events to the same listener. For example, suppose blueAction is an object of a class (such as ColorAction) implementing the ActionListener interface that changes the color to blue. You can attach the same object as a listener to several event sources:
- A toolbar button labeled "Blue"
- A menu item labeled "Blue"
- A keystroke CTRL+B
Then the color change command is handled in a uniform way, no matter whether it was caused by a button click, a menu selection, or a key press. The Swing package provides a very useful mechanism to encapsulate commands and to attach them to multiple event sources: the Action interface. An action is an object that encapsulates the following:
- A description of the command (as a text string and an optional icon);
- Parameters that are necessary to carry out the command (such as the requested color in our example).
The Action interface has the following methods:
void actionPerformed(ActionEvent event) void setEnabled(boolean b) boolean isEnabled() void putValue(String key, Object value) Object getValue(String key) void addPropertyChangeListener(PropertyChangeListener listener) void removePropertyChangeListener(PropertyChangeListener listener)
The first method is the familiar method in the ActionListener interface: in fact, the Action interface extends the ActionListener interface. Therefore, you can use an Action object whenever an ActionListener object is expected. The next two methods let you enable or disable the action and check whether the action is currently enabled. When an action is attached to a menu or toolbar and the action is disabled, then the option is grayed out. The putValue and getValue methods let you store and retrieve arbitrary name/value pairs in the action object. There are a couple of important predefined strings, namely, Action.NAME and Action.SMALL_ICON, for storing action names and icons into an action object:
action.putValue(Action.NAME, "Blue"); action.putValue(Action.SMALL_ICON, new ImageIcon("blue-ball-java-image.gif"));
Table 8-3 shows all predefined action table names.
Table 8-3. Predefined action table names
Name |
Value | |||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
NAME | The name of the action; displayed on buttons and menu items. | |||||||||||||||||||||||||||||||||||||||||||||
SMALL_ICON | A place to store a small icon; for display in a button, menu item or toolbar. | |||||||||||||||||||||||||||||||||||||||||||||
SHORT_DESCRIPTION | A short description of the icon; for display in a tooltip. | |||||||||||||||||||||||||||||||||||||||||||||
LONG_DESCRIPTION | A long description of the icon; for potential use in online help. No Swing component uses this value. | |||||||||||||||||||||||||||||||||||||||||||||
MNEMONIC_KEY | A mnemonic abbreviation; for display in menu items (see ). | |||||||||||||||||||||||||||||||||||||||||||||
ACCELERATOR_KEY | A place to store an accelerator keystroke. No Swing component uses this value. | |||||||||||||||||||||||||||||||||||||||||||||
ACTION_COMMAND_KEY | Historically, used in the now obsolete registerKeyboardAction method. | |||||||||||||||||||||||||||||||||||||||||||||
DEFAULT | Potentially useful catchall property. No Swing component uses this value.If the action object is added to a menu or toolbar, then the name and icon are automatically retrieved and displayed in the menu item or toolbar button. The SHORT_DESCRIPTION value turns into a tooltip. The final two methods of the Action interface allow other objects, in particular menus or toolbars that trigger the action, to be notified when the properties of the action object change. For example, if a menu is added as a property change listener of an action object and the action object is subsequently disabled, then the menu is called and can gray out the action name. Property change listeners are a general construct that is a part of the "Java beans" component model. You can find out more about Java beans and their properties in Volume 2. Note that Action is an interface, not a class. Any class implementing this interface must implement the seven methods that we just discussed. Fortunately, that is easy because a friendly soul has implemented all but the first method in a class AbstractAction. That class takes care of storing all name/value pairs and managing the property change listeners. All you have to add is an actionPerformed method. Let's build an action object that can execute color change commands. We store the name of the command, an icon, and the desired color. We will store the color in the table of name/value pairs that the AbstractAction class provides. Here is the code for the ColorAction class. The constructor sets the name/value pairs, and the actionPerformed method carries out the color change action.
public class ColorAction extends AbstractAction { public ColorAction(String name, Icon icon, Color c) { putValue(Action.NAME, name); putValue(Action.SMALL_ICON, icon); putValue("color", c); putValue(Action.SHORT_DESCRIPTION, "Set panel color to " + name.toLowerCase()); } public void actionPerformed(ActionEvent event) { Color c = (Color)getValue("color"); setBackground(c); } } Our test program creates three objects of this class, such as: Action blueAction = new ColorAction("Blue", new ImageIcon("blue-ball-java-image.gif"), Color.BLUE); Next, let's associate this action with a button. That is easy because there is a JButton constructor that takes an Action object. JButton blueButton = new JButton(blueAction); That constructor reads the name and icon from the action, sets the short description as the tooltip, and sets the action as the listener. You can see the icons and a tool tip in Screenshot-9. Buttons display the icons from the Action objects
As you will see in the next chapter, it is just as easy to add the same action to a menu. Finally, we want to add the action objects to keystrokes. Now we run into a technical complexity. Keystrokes are delivered to the component that has focus. Our sample app is made up of several components, namely, three buttons inside a panel. Therefore, at any time, any one of the three buttons may have focus. Each of the buttons would need to handle key events and listen to the CTRL+Y, CTRL+B, and CTRL+R keys. This is a common problem, and the Swing designers came up with a convenient solution for solving it. In fact, in SDK version 1.2, there were two different solutions for binding keys to actions: the registerKeyboardAction method of the JComponent class, and the KeyMap concept for JTextComponent commands. As of SDK version 1.3, these two mechanisms are unified. This section describes the unified approach.To associate actions with keystrokes, you first need to generate objects of the KeyStroke class. This is a convenience class that encapsulates the description of a key. To generate a KeyStroke object, you don't call a constructor, but instead use the static getKeyStroke method of the KeyStroke class. You specify the virtual key code and the flags (such as SHIFT and CONTROL key combinations): KeyStroke ctrlBKey = KeyStroke.getKeyStroke(KeyEvent.VK_B, Event.CTRL_MASK); There is a convenient method that lets you describe the keystroke as a string: KeyStroke ctrlBKey = KeyStroke.getKeyStroke("ctrl B"); Every JComponent has three input maps that map KeyStroke objects to actions. The three input maps correspond to three different conditions (see Table 8-4). Table 8-4. Input map conditions
|