Subpages: 1. JComponent properties, size, and positioning
2. Event handling and dispatching
5. AppContext service
6. Inside Timers & the TimerQueue
7. JavaBeans architecture
8. Fonts, Colors, Graphics and text
9. Using the Graphics clipping area
10. Graphics debugging
11. Painting and validation
12. Focus Management
13. Keyboard input, KeyStrokes, and Actions
2.13 Keyboard input, KeyStrokes, and Actions
2.13.1 Listening for keyboard input
KeyEvents are fired by a component whenever that component has the current focus and the user presses a key. To listen for these events on a particular component we can attach KeyListeners using the addKeyListener() method. KeyEvent extends InputEvent and, contrary to most events, KeyEvents are dispatched before the corresponding operation takes place (e.g. in a text field the operation might be adding a specific character to the document content). We can devour these events using the consume() method before they are handled further by key bindings or other listeners (below we'll discuss exactly who gets notification of keyboard input, and what order this occurs in).
There are three KeyEvent event types, each of which normally occurs at least once per keyboard activation (i.e. a press and release of a single keyboard key):
KEY_PRESSED: this type of key event is generated whenever a keyboard key is pressed. The key that is pressed is specified by the keyCode property and a virtual key code representing it can be retrieved with KeyEvent's getKeyCode() method. A virtual key code is used to report the exact keyboard key that caused the event, such as KeyEvent.VK_ENTER. KeyEvent defines numerous static int constants each starting with prefix "VK," meaning Virtual Key (see KeyEvent API docs for a complete list). For example, if CTRL-C is typed, two KEY_PRESSED events will be fired. The int returned by getKeyCode() corresponding to pressing CTRL will be a value matching KeyEvent.VK_CTRL. Similarly, the int returned by getKeyCode() corresponding to pressing the "C" key will be a value matching KeyEvent.VK_C. (Note that the order in which these are fired depends on the order in which they are pressed.) KeyEvent also maintains a keyChar property which specifies the Unicode representation of the character pressed (if there is no Unicode representation KeyEvent.CHAR_UNDEFINED is used--e.g. the function keys on a typical PC keyboard). We can retrieve the keyChar character corresponding to any KeyEvent using the getKeyChar() method. For example, the character returned by getKeyChar() corresponding to pressing the "C" key will be 'c'. If SHIFT was pressed and held while the "C" key was pressed, the character returned by getKeyChar() corresponding to the "C" key press would be 'C'. (Note that distinct keyChars are returned for upper and lower case characters, wheras the same keyCode is used in both situations--e.g. the value of VK_C will be returned by getKeyCode() regardless of whether SHIFT is held down when the "C" key is pressed or not. Also note that there is no keyChar associated with keys such as CTRL, and getKeyChar() will simply return '' in this case.)
KEY_RELEASED: this type of key event is generated whenever a keyboard key is released. Other than this difference, KEY_RELEASED events are identical to KEY_PRESSED events (however, as we will discuss below, they occur much less frequently).
KEY_TYPED: this type of event is fired somewhere between a KEY_PRESSED and KEY_RELEASED event. It never carries a keyCode property corresponding to the actual key pressed, and 0 will be returned whenever getKeyCode() is called on an event of this type. Note that for keys with no Unicode representation (such as PAGE UP, PRINT SCREEN, etc.), no KEY_TYPED event will be generated at all.
Most keys with Unicode representations, when held down for longer than a few moments, repeatedly generate KEY_PRESSED and KEY_TYPED events (in this order). The set of keys that exhibit this behavior, and the rate at which they do so, cannot be controlled and is platform-specific.
Each KeyEvent maintains a set of modifiers which specifies the state of the SHIFT, CTRL, ALT, and META keys. This is an int value that is the result of the bit-wise OR of InputEvent.SHIFT_MASK, InputEvent.CTRL_MASK, InputEvent.ALT_MASK, and InputEvent.META_MASK (depending on which keys are pressed at the time of the event). We can retrieve this value with getModifiers(), and we can query specifically whether any of these keys was pressed at the time the event was fired using isShiftDown(), isControlDown(), isAltDown(), and isMetaDown().
KeyEvent also maintains the boolean actionKey property which specifies whether the invoking keyboard key corresponds to an action that should be performed by that app (true) vs. data that is normally used for such things as addition to a text component's document content (false). We can use KeyEvent's isActionKey() method to retrieve the value of this property.
Using KeyListeners to handle all keyboard input on a component-by-component basis was required pre-Java 2. Because of this, a significant, and often tedious, amount of time needed to spent planning and debugging keyboard operations. The Swing team recognized this, and thankfully included functionality for key event interception regardless of which component currently has the focus. This functionality is implemented by binding instances of the javax.swing.KeyStroke class with ActionListeners (normally instances of javax.swing.Action).
Each KeyStroke instance encapsulates a KeyEvent keyCode (see above), a modifiers value (identical to that of KeyEvent -- see above), and a boolean property specifying whether it should be activated on a key press (false -- default) or on a key release (true). The KeyStroke class provides five static methods for creating KeyStroke objects (note that all KeyStrokes are cached, and it is not necessarily the case that these methods will always return a brand new instance):
getKeyStroke(int keyCode, int modifiers)
getKeyStroke(int keyCode, int modifiers, boolean onKeyRelease)
The last method will return a KeyStroke with properties corresponding to the given KeyEvent's attributes. The keyCode, keyChar, and modifiers properties are taken from the KeyEvent and the onKeyRelease property is set to true if the event is of type KEY_RELEASED and false otherwise.
To register a KeyStroke/ActionListener combination with any JComponent we can use its registerKeyBoardAction(ActionListener action, KeyStroke stroke, int condition) method. The ActionListener parameter is expected to be defined such that its actionPerformed() method performs the necessary operations when keyboard input corresponding to the KeyStroke parameter is intercepted. The int parameter specifies under what conditions the given KeyStroke is considered to be valid:
JComponent.WHEN_FOCUSED: the corresponding ActionListener will only be invoked if the component this KeyStroke is registered with has the current focus.
JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT: the corresponding ActionListener will only be invoked if the component this KeyStroke is registered with is the ancestor of (i.e. it contains) the component with the current focus.
JComponent.WHEN_IN_FOCUSED_WINDOW: the corresponding ActionListener will be invoked if the component this KeyStroke is registered with is anywhere within the peer-level window (i.e. JFrame, JDialog, JWindow, JApplet, or any other heavyweight component) that has the current focus. Note that keyboard actions registered with this condition are handled in an instance of the private KeyBoardManager service class (see 2.13.4) rather than the component itself.
For example, to associate the invocation of an ActionListener corresponding to ALT-H no matter what component has the focus in a given JFrame, we can do the following:
KeyStroke myKeyStroke =
Each JComponent maintains a Hashtable client property containing all bound KeyStrokes. Whenever a KeyStroke is registered using the registerKeyboardAction() method, it is added to this structure. Only one ActionListener can be registered corresponding to each KeyStroke, and if there is already an ActionListener mapped to a particular KeyStroke, the new one will effectively overwrite the previous one. We can retrieve an array of KeyStrokes corresponding to the current bindings stored in this Hashtable using JComponent's getRegisteredKeyStrokes() method, and we can wipe out all bindings by with the resetKeyboardActions() method. Given a KeyStroke object we can retrieve the corresponding ActionListener with JComponent's getActionForKeyStroke() method, and we can retrive the corresponding condition property with the getConditionForKeyStroke() method.
An Action instance is basically a convenient ActionListener implementation that encapsulates a Hashtable of bound properties similar JComponent's client properties (see chapter 12 for details about working with Action implementations and their properties). We often use Action instances when registering keyboard actions.
2.13.4 The flow of keyboard input
Each KeyEvent is first dispatched to the focused component. The FocusManager gets first crack at processing it. If the FocusManager doesn't want it, then the focused JComponent it is sent to calls super.processKeyEvent() which allows any KeyListeners a chance to process the event. If the listeners don't consume it and the focused component is a JTextComponent, the KeyMap hierarchy is traversed (see chapters 11 and 19 for more about KeyMaps). If the event is not consumed by this time the key bindings registered with the focused component get a shot. First, KeyStrokes defined with the WHEN_FOCUSED condition get a chance. If none of these handle the event, then the component walks though it's parent containers (up until a JRootPane is reached) looking for KeyStrokes defined with the WHEN_ANCESTOR_OF_FOCUSED_COMPONENT condition. If the event hasn't been handled after the top-most container is reached, it is sent to KeyboardManager, a package private service class (note that unlike most service classes in Swing, KeyboardManager does not register its shared instance with AppContext -- see section 2.5). KeyboardManager looks for components with registered KeyStrokes with the WHEN_IN_FOCUSED_WINDOW condition and sends the event to them. If none of these are found then KeyboardManager passes the event to any JMenuBars in the current window and lets their accelerators have a crack at it. If the event is still not handled, we check if the current focus resides in a JInternalFrame (because it is the only RootPaneContainer that can be contained inside another Swing component). If this is the case, we move up to the JInternalFrame's parent. This process continues until either the event is consumed or the top-level window is reached.