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 action listener whose actionPerformed method changes the background 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

  • A description of the command (as a text string and an optional icon); and

  • 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. A couple of important predefined strings, namely, Action.NAME and Action.SMALL_ICON, store action names and icons into an action object:

 action.putValue(Action.NAME, "Blue"); action.putValue(Action.SMALL_ICON, new ImageIcon("blue-ball.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 Chapter 9)

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 catch-all 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 "JavaBeans" component model. You can find out more about 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 we just discussed. Fortunately, a friendly soul has provided a class AbstractAction that implements all methods except for actionPerformed. That class takes care of storing all name/value pairs and managing the property change listeners. You simply extend AbstractAction and supply 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 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.gif"), Color.BLUE); 

Next, let's associate this action with a button. That is easy because we can use 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 tooltip in Figure 8-9.

Figure 8-9. Buttons display the icons from the action objects


As we demonstrate 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 so that the actions are carried out when the user types keyboard commands. Now we run into a technical complexity. Keystrokes are delivered to the component that has focus. Our sample application 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.

NOTE

In fact, in JDK 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 JDK 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, InputEvent.CTRL_MASK); 

Alternatively, you can describe the keystroke as a string:

 KeyStroke ctrlBKey = KeyStroke.getKeyStroke("ctrl B"); 

Every JComponent has three input maps, each mapping KeyStroke objects to associated actions. The three input maps correspond to three different conditions (see Table 8-4).

Table 8-4. Input Map Conditions

Flag

Invoke Action

WHEN_FOCUSED

When this component has keyboard focus

WHEN_ANCESTOR_OF_FOCUSED_COMPONENT

When this component contains the component that has keyboard focus

WHEN_IN_FOCUSED_WINDOW

When this component is contained in the same window as the component that has keyboard focus


Keystroke processing checks these maps in the following order:

  1. Check the WHEN_FOCUSED map of the component with input focus. If the keystroke exists, execute the corresponding action. If the action is enabled, stop processing.

  2. Starting from the component with input focus, check the WHEN_ANCESTOR_OF_FOCUSED_COMPONENT maps of its parent components. As soon as a map with the keystroke is found, execute the corresponding action. If the action is enabled, stop processing.

  3. Look at all visible and enabled components in the window with input focus that have this keystroke registered in a WHEN_IN_FOCUSED_WINDOW map. Give these components (in the order of their keystroke registration) a chance to execute the corresponding action. As soon as the first enabled action is executed, stop processing. This part of the process is somewhat fragile if a keystroke appears in more than one WHEN_IN_FOCUSED_WINDOW map.

You obtain an input map from the component with the getInputMap method, for example:

 InputMap imap = panel.getInputMap(JComponent.WHEN_FOCUSED); 

The WHEN_FOCUSED condition means that this map is consulted when the current component has the keyboard focus. In our situation, that isn't the map we want. One of the buttons, not the panel, has the input focus. Either of the other two map choices works fine for inserting the color change keystrokes. We use WHEN_ANCESTOR_OF_FOCUSED_COMPONENT in our example program.

The InputMap doesn't directly map KeyStroke objects to Action objects. Instead, it maps to arbitrary objects, and a second map, implemented by the ActionMap class, maps objects to actions. That makes it easier to share the same actions among keystrokes that come from different input maps.

Thus, each component has three input maps and one action map. To tie them together, you need to come up with names for the actions. Here is how you can tie a key to an action:

 imap.put(KeyStroke.getKeyStroke("ctrl Y"), "panel.yellow"); ActionMap amap = panel.getActionMap(); amap.put("panel.yellow", yellowAction); 

It is customary to use the string "none" for a do-nothing action. That makes it easy to deactivate a key:

 imap.put(KeyStroke.getKeyStroke("ctrl C"), "none"); 

CAUTION

The JDK documentation suggests using the action name as the action's key. We don't think that is a good idea. The action name is displayed on buttons and menu items; thus, it can change at the whim of the UI designer and it may be translated into multiple languages. Such unstable strings are poor choices for lookup keys. Instead, we recommend that you come up with action names that are independent of the displayed names.


To summarize, here is what you do to carry out the same action in response to a button, a menu item, or a keystroke:

  1. Implement a class that extends the AbstractAction class. You may be able to use the same class for multiple related actions.

  2. Construct an object of the action class.

  3. Construct a button or menu item from the action object. The constructor will read the label text and icon from the action object.

  4. For actions that can be triggered by keystrokes, you have to carry out additional steps. First locate the top-level component of the window, such as a panel that contains all other components.

  5. Then get the WHEN_ANCESTOR_OF_FOCUSED_COMPONENT input map of the top-level component. Make a KeyStroke object for the desired keystroke. Make an action key object, such as a string that describes your action. Add the pair (keystroke, action key) into the input map.

  6. Finally, get the action map of the top-level component. Add the pair (action key, action object) into the map.

Example 8-5 shows the complete code of the program that maps both buttons and keystrokes to action objects. Try it out clicking either the buttons or pressing CTRL+Y, CTRL+B, or CTRL+R changes the panel color.

Example 8-5. ActionTest.java
  1. import java.awt.*;  2. import java.awt.event.*;  3. import javax.swing.*;  4.  5. public class ActionTest  6. {  7.    public static void main(String[] args)  8.    {  9.       ActionFrame frame = new ActionFrame(); 10.       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 11.       frame.setVisible(true); 12.    } 13. } 14. 15. /** 16.    A frame with a panel that demonstrates color change actions. 17. */ 18. class ActionFrame extends JFrame 19. { 20.    public ActionFrame() 21.    { 22.       setTitle("ActionTest"); 23.       setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); 24. 25.       // add panel to frame 26. 27.       ActionPanel panel = new ActionPanel(); 28.       add(panel); 29.    } 30. 31.    public static final int DEFAULT_WIDTH = 300; 32.    public static final int DEFAULT_HEIGHT = 200; 33. } 34. 35. /** 36.    A panel with buttons and keyboard shortcuts to change 37.    the background color. 38. */ 39. class ActionPanel extends JPanel 40. { 41.    public ActionPanel() 42.    { 43.       // define actions 44. 45.       Action yellowAction = new ColorAction("Yellow", 46.          new ImageIcon("yellow-ball.gif"), 47.          Color.YELLOW); 48.       Action blueAction = new ColorAction("Blue", 49.          new ImageIcon("blue-ball.gif"), 50.          Color.BLUE); 51.       Action redAction = new ColorAction("Red", 52.          new ImageIcon("red-ball.gif"), 53.          Color.RED); 54. 55.       // add buttons for these actions 56. 57.       add(new JButton(yellowAction)); 58.       add(new JButton(blueAction)); 59.       add(new JButton(redAction)); 60. 61.       // associate the Y, B, and R keys with names 62. 63.       InputMap imap = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); 64. 65.       imap.put(KeyStroke.getKeyStroke("ctrl Y"), "panel.yellow"); 66.       imap.put(KeyStroke.getKeyStroke("ctrl B"), "panel.blue"); 67.       imap.put(KeyStroke.getKeyStroke("ctrl R"), "panel.red"); 68. 69.       // associate the names with actions 70. 71.       ActionMap amap = getActionMap(); 72.       amap.put("panel.yellow", yellowAction); 73.       amap.put("panel.blue", blueAction); 74.       amap.put("panel.red", redAction); 75.    } 76. 77.    public class ColorAction extends AbstractAction 78.    { 79.       /** 80.          Constructs a color action. 81.          @param name the name to show on the button 82.          @param icon the icon to display on the button 83.          @param c the background color 84.       */ 85.       public ColorAction(String name, Icon icon, Color c) 86.       { 87.          putValue(Action.NAME, name); 88.          putValue(Action.SMALL_ICON, icon); 89.          putValue(Action.SHORT_DESCRIPTION, "Set panel color to " + name.toLowerCase()); 90.          putValue("color", c); 91.       } 92. 93.       public void actionPerformed(ActionEvent event) 94.       { 95.          Color c = (Color) getValue("color"); 96.          setBackground(c); 97.       } 98.    } 99. } 


 javax.swing.Action 1.2 

  • void setEnabled(boolean b)

    enables or disables this action.

  • boolean isEnabled()

    returns TRue if this action is enabled.

  • void putValue(String key, Object value)

    places a name/value pair inside the action object.

    Parameters:

    key

    The name of the feature to store with the action object. This can be any string, but several names have predefined meanings see Table 8-3 on page 325.

     

    value

    The object associated with the name.


  • Object getValue(String key)

    returns the value of a stored name/value pair.


 javax.swing.JMenu 1.2 

  • JMenuItem add(Action a)

    adds a menu item to the menu that invokes the action a when selected; returns the added menu item.


 javax.swing.KeyStroke 1.2 

  • static KeyStroke getKeyStroke(char keyChar)

    creates a KeyStroke object that encapsulates a keystroke corresponding to a KEY_TYPED event.

  • static KeyStroke getKeyStroke(int keyCode, int modifiers)

  • static KeyStroke getKeyStroke(int keyCode, int modifiers, boolean onRelease)

    create a KeyStroke object that encapsulates a keystroke corresponding to a KEY_PRESSED or KEY_RELEASED event.

    Parameters:

    keyCode

    The virtual key code

     

    modifiers

    Any combination of InputEvent.SHIFT_MASK, InputEvent.CTRL_MASK, InputEvent.ALT_MASK, InputEvent.META_MASK

     

    onRelease

    TRue if the keystroke is to be recognized when the key is released


  • static KeyStroke getKeyStroke(String description)

    constructs a keystroke from a humanly readable description. The description is a sequence of whitespace-delimited strings in the following format:

    1. The strings shift control ctrl meta alt button1 button2 button3 are translated to the appropriate mask bits.

    2. The string typed must be followed by a one-character string, for example, "typed a".

    3. The string pressed or released indicates a key press or release. (Key press is the default.)

    4. Otherwise, the string, when prefixed with VK_, should correspond to a KeyEvent constant, for example, "INSERT" corresponds to KeyEvent.VK_INSERT.

    For example, "released ctrl Y" corresponds to: getKeyStroke(KeyEvent.VK_Y, Event.CTRL_MASK, true)


 javax.swing.JComponent 1.2 

  • ActionMap getActionMap() 1.3

    returns the action map that maps keystrokes to action keys.

  • InputMap getInputMap(int flag) 1.3

    gets the input map that maps action keys to action objects.

    Parameters:

    flag

    A condition on the keyboard focus to trigger the action, one of the values in Table 8-4 on page 327



       
    top



    Core Java 2 Volume I - Fundamentals
    Core Java(TM) 2, Volume I--Fundamentals (7th Edition) (Core Series) (Core Series)
    ISBN: 0131482025
    EAN: 2147483647
    Year: 2003
    Pages: 132

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net