Java ScreenShot
     

Screenshot Core Java 2: Volume I - Fundamentals

Table of Contents
 8.  Event Handling


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.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.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.

Screenshot-9. Buttons display the icons from the Action objects

Java graphics 08fig09.gif


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.

Java graphics notes_icon.gif

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

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 key strokes. 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");


Java graphics caution_icon.gif

The SDK 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. Make a class that extends the AbstractAction class. You may be able to use the same class for multiple related actions.

  2. Make 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.show();
 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. Container contentPane = getContentPane();
 29. contentPane.add(panel);
 30. }
 31.
 32. public static final int DEFAULT_WIDTH = 300;
 33. public static final int DEFAULT_HEIGHT = 200;
 34. }
 35.
 36. /**
 37. A panel with buttons and keyboard shortcuts to change
 38. the background color.
 39. */
 40. class ActionPanel extends JPanel
 41. {
 42. public ActionPanel()
 43. {
 44. // define actions
 45.
 46. Action yellowAction = new ColorAction("Yellow",
 47. new ImageIcon("yellow-ball.gif"),
 48. Color.YELLOW);
 49. Action blueAction = new ColorAction("Blue",
 50. new ImageIcon("blue-ball.gif"),
 51. Color.BLUE);
 52. Action redAction = new ColorAction("Red",
 53. new ImageIcon("red-ball.gif"),
 54. Color.RED);
 55.
 56. // add buttons for these actions
 57.
 58. add(new JButton(yellowAction)); // since 1.3
 59. add(new JButton(blueAction));
 60. add(new JButton(redAction));
 61.
 62. // associate the Y, B, and R keys with names
 63.
 64. InputMap imap = getInputMap( // since 1.3
 65. JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
 66.
 67. imap.put(KeyStroke.getKeyStroke("ctrl Y"), "panel.yellow");
 68. imap.put(KeyStroke.getKeyStroke("ctrl B"), "panel.blue");
 69. imap.put(KeyStroke.getKeyStroke("ctrl R"), "panel.red");
 70.
 71. // associate the names with actions
 72.
 73. ActionMap amap = getActionMap();
 74. amap.put("panel.yellow", yellowAction);
 75. amap.put("panel.blue", blueAction);
 76. amap.put("panel.red", redAction);
 77. }
 78.
 79. public class ColorAction extends AbstractAction
 80. {
 81. /**
 82. Constructs a color action.
 83. @param name the name to show on the button
 84. @param icon the icon to display on the button
 85. @param c the background color
 86. */
 87. public ColorAction(String name, Icon icon, Color c)
 88. {
 89. putValue(Action.NAME, name);
 90. putValue(Action.SMALL_ICON, icon);
 91. putValue(Action.SHORT_DESCRIPTION,
 92. "Set panel color to " + name.toLowerCase());
 93. putValue("color", c);
 94. }
 95.
 96. public void actionPerformed(ActionEvent event)
 97. {
 98. Color c = (Color)getValue("color");
 99. setBackground(c);
100. }
101. }
102. }


java.swing.Action 1.2

Java graphics api_icon.gif
  • void setEnabled(boolean b)

    enables or disables this action. User interface elements may query this status and disable themselves if the associated action is disabled.

  • 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 four names have predefined meanings:

    Name

    Value

    Action.NAME

    The action name, to be displayed in UI components

    Action.SMALL_ICON

    The action icon, to be displayed in UI components

    Action.SHORT_DESCRIPTION

    A short description, for example, for a tool tip hint

    Action.LONG_DESCRIPTION

    A longer description for on-line help

     

    value

    The object associated with the name.

  • Object getValue(String key)

    returns the value of a stored name/value pair.

java.swing.JMenu 1.2

Java graphics api_icon.gif
  • JMenuItem add(Action a)

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

java.swing.KeyStroke 1.2

Java graphics api_icon.gif
  • static KeyStroke getKeyStroke(char keyChar)

    creates a KeyStroke object that encapsulates a key stroke 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 key stroke 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 white-space-delimited tokens in the following format:

    1. Tokens that match shift control ctrl meta alt button1 button2 button3 are translated to the appropriate mask bits.

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

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

    4. Otherwise, the token, 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)

java.swing.JComponent 1.2

Java graphics api_icon.gif
  • 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:

    Name

    Value

    WHEN_FOCUSED

    In this component

    WHEN_IN_FOCUSED_WINDOW

    Anywhere in the window containing this component

    WHEN_ANCESTOR_OF_FOCUSED_COMPONENT

    Anywhere in a subcomponent contained in this component


Java ScreenShot
     
Top
 

Comments