Concepts / Designing Event-Driven Java GUI Programs

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.

  • Programming

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.

Key pressFocused componentholds input focusKeyEvent objectcreated for the focusedcomponentRegistered listeneradded with addKeyListenerOther componentlost focusrouted to focus holderbecomesdelivered to
When focus moves from one component to another, which component receives the next key event, and what happens to the component that lost focus?

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.

KeyboardFocused componentListenerShift down: keyPressedA down: keyPressedkeyTyped carrying AA up: keyReleasedShift up: keyReleased
For one press and release of Shift and A, what is the order of the events, and how many of each event type does the action produce?

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?

  • One keyPressed and one keyTyped

  • Two keyPressed and one keyTyped

  • Two keyPressed and two keyTyped

  • Two keyPressed and zero keyTyped

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

java
Notes on the code
  • The class implements all three methods, because KeyListener requires them.
  • keyPressed and keyReleased read the key code to identify Shift.
  • keyTyped reads the character with getKeyChar().

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.

VK_SHIFTidentifies the Shift keyNo characterShift types nothingVK_Aidentifies the A key'A'the character produced
For a given key, which value does getKeyCode() return and which does getKeyChar() return, and where do they differ?

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 like

    Holding Shift and pressing A, then counting only one keyPressed call

    Why it fails

    Shift and A are two physical keys, and each one generates its own keyPressed event.

    Do this instead

    Expect 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 like

    Writing the Shift test inside keyTyped and wondering why it never runs

    Why it fails

    keyTyped is tied to the character produced, and Shift does not type a character.

    Do this instead
    Test for VK_SHIFT inside keyPressed or keyReleased with evt.getKeyCode().
  • Common mistake

    Assuming the pointer decides where key events go

    What it looks like

    Moving the mouse over a text field and expecting it to receive keys while another component still has focus

    Why it fails

    Keyboard events go to the single component that holds input focus, not the component under the cursor.

    Do this instead

    Check 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 like

    Comparing evt.getKeyCode() to 'A' and expecting a match for the letter

    Why it fails

    getKeyCode() returns an integer key code, not a char.

    Do this instead

    Compare 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

MEDIUM

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 keyTyped should be zero, because Shift types no character.
MEDIUM

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 keyTyped and getKeyChar().
  • Shift state comes from keyPressed and keyReleased with getKeyCode().
  • Registration uses addKeyListener on 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.
  • keyPressed and keyReleased are tied to physical keys, while keyTyped is tied to the character produced, so one uppercase A with Shift produces two keyPressed, two keyReleased, and one keyTyped event.
  • evt.getKeyCode() identifies keys through named constants such as KeyEvent.VK_SHIFT, and evt.getKeyChar() inside keyTyped returns the character typed as a char.
  • A keyboard handler needs a KeyEvent, a KeyListener implementation with all three methods, and registration with the focused component through addKeyListener.
  • When a handler misbehaves, check the event type first, then the value it reads, and confirm which component holds focus.