Sunday, November 28, 2010

A pattern? for keyboard handling with Swing

Recently, working in a Swing-based project for System (OpenbravoPos), we needed to build a generic keyboard handling mechanism. The following requirements were mandatory:

  • It must be possible to fire key-mapped logic in two ways:
    • Regardless of current widget focus.
    • Capturing key event on an specific widget (because OpenbravoPos main sales panel uses a hidden text field in order to handle all the key events).
  • Logic can be specified using:
    • Custom Java code
    • BeanShell scripts, stored in an XML file.
  • Key mapping must be easily changed when active window changes.

We built a three classes schema, which is based on some functionalities provided by Swing, but it could be ported to other UI frameworks as well. I'll describe the approach (the names of classes and methods are not exactly the same, but I'm trying to give an overall idea).

First of all, a interface called KeyHandler acts as callback for keyboard events. This allows us:

  • Creating custom Java-based key handlers, using inner classes.
  • Create generic BeanShell key handlers, which are populated by a process that reads definitions from the before mentioned XML file.

The interface provides just a method:

public void onKey(int keyCode, int keyModifiers);

In second place, a class called KeyboardManager maps keys (and modifiers) to KeyHandlers:

public void registerKeyHandler(KeyHandler keyHandler, int keyCode, int keyModifiers);

The process that reads keys definitions from XML must also register the keys in the KeyboardManager. Also, this class can fire the logic associated with a specific key:

public void triggerKeyEvent(int keyCode, int keyModifiers);

Now, with a KeyboardManager properly created and configured, we can call its triggerKeyEvent on a widget key event listener in order to respond to key events.

And finally, in order to meet the requirement of being able of map keys without using a widget event listener, there is a 3rd class, KeyboardDispatcher. This class is responsible for activating/deactivating KeyboardManagers, with an stack-like approach:

public void pushKeyboardManager(KeyboardManager keyboardManager);
public void popKeyboardManager();

Typically, the push method is called when the panel is activated and the the pop method, on deactivate event. Internally, the class uses Swing's KeyboardFocusManager and KeyEventDispatcher classes.

This approach works well for panels and windows that are opened in a LIFO way (for example, modal windows called from a panel). The main problem is that it doesn't support mixing multiple KeyboardManagers into the same  KeyboardDispatcher.

By the way, the “pattern” word in the title is there just for marketing purposes :P

For updates, subscribe to