Designing Event-Driven Java GUI Programs
Input focus decides which component receives keyboard events: exactly one element holds it at a time, and all keyboard events go there.
Why a Keystroke Needs Somewhere to Go
A graphical Java program often has many components on screen at once: text fields, buttons, panels, and more. When you press a key, the program must decide which of those components should hear about it. Mouse input solves this problem with a simple rule. A click belongs to whichever component sits under the cursor. Keyboard input does not work that way, because pressing a key does not depend on where the pointer is.
This article follows a single question through a small event-driven program: when a key goes down, comes back up, or produces a character, which listener method runs, and what information does the event object carry? By the end you should be able to predict the event counts for one keystroke and track down a handler that reads the wrong event type or the wrong value.
Input Focus and the Component That Holds It
Input focus is the rule that answers the question of where keyboard input goes. At any moment, exactly one interface element on the screen holds input focus, and every keyboard event is directed to that element. Focus is not determined by the pointer. It belongs to one component at a time, and that component is the one whose registered listeners receive the key events.
Read the diagram from left to right. A key press is not sent to a location on the screen. It is sent to the focused component. Only that component's KeyEvent object is created and delivered to the listeners registered with it. The component that lost focus receives no key events until it regains focus. When you trace a keyboard bug, the question is therefore never only what key was pressed, but which component was holding focus at that moment.
A keyboard-handling object needs three roles to work. The KeyEvent class describes the event itself. A listener object must implement the KeyListener interface, which requires the three methods keyPressed, keyReleased, and keyTyped. Finally, the listener must be registered with the component that will generate the events, written as component.addKeyListener(listener). That component is the one holding focus when the keys are pressed. The component and the listener can be the same object.
Tracing One Shift and A Press
Before reading the trace, decide what you expect. Suppose you want to type an uppercase A by holding Shift, pressing A, and then releasing both keys. The source says that an uppercase A can generate two keyPressed events, two keyReleased events, and one keyTyped event. The sequence below shows how those events come about, one step at a time.
Notice that the two keyPressed events come from two physical keys, Shift and A, and the two keyReleased events come from the same two keys going up. Only one keyTyped event appears, because keyTyped reports the character produced, and only one uppercase A appears on the screen. Pressing Shift by itself does not type a character, so Shift contributes to keyPressed and keyReleased but never to keyTyped. This is why the source describes keyTyped as tied to the character, while keyPressed and keyReleased are tied to physical keys.
What do you think happens?
How many keyPressed events does one Shift plus A action produce, and how many keyTyped events?
Reveal answer
Two keyPressed and one keyTyped
Shift and A each go down, so there are two keyPressed events. Only one character, the uppercase A, appears, so there is one keyTyped event. Shift alone types no character.
Matching Each Event Type to Its Question
The three KeyListener methods answer three different questions. keyPressed tells you that a physical key went down. keyReleased tells you that a physical key came back up. keyTyped tells you which character was produced, and a single character may come from several key presses. Use keyTyped when you need the character that appeared on screen. Use keyPressed and keyReleased when you need to notice that a key such as Shift has changed state.
- The class implements all three methods, because
KeyListenerrequires them. keyPressedandkeyReleasedread the key code to identify Shift.keyTypedreads the character withgetKeyChar().
The class above is only the listener. To receive events it must be registered with the focused component, for example by calling component.addKeyListener(watcher). If the component never holds focus, none of these methods will run for keys pressed while another component is active.
Reading Key Codes and Characters
Every key has an integer code, and the event parameter carries the code of the key pressed or released. You obtain it with evt.getKeyCode(). You do not need to memorize a table of numbers, because KeyEvent provides a named constant for each key, such as KeyEvent.VK_SHIFT. The arrow keys follow the same pattern with KeyEvent.VK_LEFT, KeyEvent.VK_RIGHT, KeyEvent.VK_UP, and KeyEvent.VK_DOWN. The VK prefix stands for Virtual Keyboard. Different keyboards send different raw codes, but Java translates them into its own virtual codes, so the same constants work across keyboards and platforms.
A key code tells you which key moved, but it does not tell you which character appeared on the screen. For that, call evt.getKeyChar() inside keyTyped. The method returns a char, so the result can be compared with character literals or stored as a single character.
The two methods disagree for Shift. getKeyCode() identifies the Shift key on keyPressed and keyReleased, while keyTyped never fires for Shift alone, so there is no character to read. For the letter A, getKeyCode() identifies the key that moved, and getKeyChar() inside keyTyped returns the character that appeared. A handler that expects a character from a key code, or a key code from a keyTyped character, will therefore read the wrong value even though the program compiles.
Mistakes That Break Keyboard Handlers
Common mistake Expecting one keyPressed for an uppercase A typed with Shift
What it looks likeHolding Shift and pressing A, then counting only one keyPressed call
Why it failsShift and A are two physical keys, and each one generates its own
keyPressedevent.Do this insteadExpect two keyPressed and two keyReleased events, and one keyTyped event for the character.
Common mistake Using keyTyped to detect the Shift key
What it looks likeWriting the Shift test inside keyTyped and wondering why it never runs
Why it failskeyTypedis tied to the character produced, and Shift does not type a character.Do this insteadTest for VK_SHIFT inside keyPressed or keyReleased with evt.getKeyCode().Common mistake Assuming the pointer decides where key events go
What it looks likeMoving the mouse over a text field and expecting it to receive keys while another component still has focus
Why it failsKeyboard events go to the single component that holds input focus, not the component under the cursor.
Do this insteadCheck which component holds focus when the key is pressed, and register the listener with that component.
Common mistake Reading a character when the program only has a key code
What it looks likeComparing evt.getKeyCode() to 'A' and expecting a match for the letter
Why it failsgetKeyCode()returns an integer key code, not a char.Do this insteadCompare key codes with KeyEvent constants, and compare characters from getKeyChar() inside keyTyped.
Common mistake Forgetting to call addKeyListener, so the three methods never run
When a handler prints an unexpected result, check the event type first, then the value it reads. If the wrong method is handling the key, the divergence is in the event type. If the right method runs but the value is wrong, the divergence is in the value. If nothing runs at all, check whether the component holding focus has the listener registered.
Practice: Counting Events for a Shift Press
Suppose you hold down only the Shift key, press it, and then release it, without pressing any other key. Predict how many keyPressed, keyReleased, and keyTyped events the listener receives. Then explain which of the three methods would be the right place to notice the change in Shift's state.
Hints
- Ask whether Shift produces a character at all.
- Think about which method is tied to physical keys and which to characters.
- The answer for
keyTypedshould be zero, because Shift types no character.
A text-entry handler should add each typed character to a string. Describe which listener method you would use to add characters, which method you would use to detect that Shift is currently held down, and how you would make sure the listener receives events from the text field that currently holds focus.
Hints
- Characters come from
keyTypedandgetKeyChar(). - Shift state comes from
keyPressedandkeyReleasedwithgetKeyCode(). - Registration uses
addKeyListeneron the component that holds focus.
Key Takeaways
- Input focus, not the pointer, decides which component receives keyboard events, and exactly one element holds focus at a time.
keyPressedandkeyReleasedare tied to physical keys, whilekeyTypedis tied to the character produced, so one uppercase A with Shift produces twokeyPressed, twokeyReleased, and onekeyTypedevent.evt.getKeyCode()identifies keys through named constants such asKeyEvent.VK_SHIFT, andevt.getKeyChar()insidekeyTypedreturns the character typed as a char.- A keyboard handler needs a
KeyEvent, aKeyListenerimplementation with all three methods, and registration with the focused component throughaddKeyListener. - When a handler misbehaves, check the event type first, then the value it reads, and confirm which component holds focus.