Kerruba
Kerruba

Reputation: 2019

Any better solution to ListenKey anywhere inside the frame?

I've a frame with a text field, a table and two buttons!

This frame should listen to key modifiers

I would like to add a KeyListener to listen when a modifier key is pressed and released, in order to change the text of the Ok button and let user select different methods. The listener should work regardless which component is in focus.

For example:

  1. No modifiers: "Ok" - Do Something;
  2. Shift key pressed: "Ok and close" - Do Something and close the frame;
  3. Alt key pressed... etc

Right now I've found this solution, but it seems a bit cluncky and not elegant at all.

public class MyFrame extends JFrame {

private boolean shiftPressed;
private MyDispatcher keyDispatcher;


public MyFrame () {
    super();
    initGUI();

    shiftPressed = false;

    KeyboardFocusManager currentManager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
    keyDispatcher = new MyDispatcher();
    currentManager.addKeyEventDispatcher(keyDispatcher);

    //...remain of code

}

@Override
public void dispose() {
    // I remove the dispatcher when the frame is closed;
    KeyboardFocusManager currentManager = KeyboardFocusManager
            .getCurrentKeyboardFocusManager();
    currentManager.removeKeyEventDispatcher(keyDispatcher);
    super.dispose();
}

private class MyDispatcher implements KeyEventDispatcher {
    @Override
    public boolean dispatchKeyEvent(KeyEvent e) {
        if (e.getModifiers() == KeyEvent.SHIFT_MASK) {
            if (e.getID() == KeyEvent.KEY_PRESSED) {
                System.out.println("Shift Pressed");
                shiftPressed = true;
                btnOk.setText("Ok and Close");
            } 
        } else if (e.getID() == KeyEvent.KEY_RELEASED) {
            if(!e.isShiftDown() && shiftPressed) {
                System.out.println("Shift Released");
                btnOk.setText("Ok");
            }
        }
        return false;
    }
}

}

Any suggestion on how to improve the code?

Upvotes: 1

Views: 137

Answers (2)

dic19
dic19

Reputation: 17971

I would like to add a KeyListener to listen when a modifier key is pressed and released, in order to change the text of the Ok button and let user select different methods. The listener should work regardless which component is in focus.

IMHO using Key bindings is a more flexible and reliable approach that brings with these benefits:

Having said that, we can create KeyStrokes using appropriate masks like follows:

KeyStroke shiftKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_SHIFT, KeyEvent.SHIFT_DOWN_MASK);

And we can even specify that the key stroke will be triggered on a key release event:

KeyStroke shiftReleasedKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_SHIFT, 0, true);

Note: 0 means no modifiers and true is a flag indicating the key stroke represents a key release event. See the API for more details.

Now, what I'd do is create a wrapper JPanel that contains all my components and attach actions using WHEN_IN_FOCUSED_WINDOW condition:

JPanel wrapperPanel = new JPanel();
...
InputMap wrapperPanelInputMap = wrapperPanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
wrapperPanelInputMap.put(shiftKeyStroke, "pressedAction");
wrapperPanelInputMap.put(shiftReleasedKeyStroke, "releasedAction");
...
wrapperPanel.getActionMap().put("pressedAction", pressedAction);
wrapperPanel.getActionMap().put("releasedAction", releasedAction);

Where both pressedAction and releasedAction are different Actions that can do whatever you want (i.e.: change button's text).

The only minor disadvantage is the LOC needed to set key bindings. However you can avoid boilerplate code by creating a reusable panel that can accept Actions for the different modifiers (SHIFT, ALT, CTRL, etc.) and set key bindings internally in that class.

Upvotes: 2

Sergiy Medvynskyy
Sergiy Medvynskyy

Reputation: 11327

Your approach is ok but it uses a global focus manager. It's a little bit dangerous and can provoke some errors when you for example forget to dispose your frame. Much better is to add your listener to all focusable components in the frame.

private static void addToAll(Component aStart, KeyListener aListener) {
    if (aStart.isFocusable()) {
        aStart.addKeyListener(aListener);
    }
    if (aStart instanceof Container) {
        Container container = (Container) aStart;
        for (Component comp : container.getComponents()) {
            addToAll(comp, aListener);
        }
    }
}

To use it:

public MyFrame () {
    super();
    initGUI();

    shiftPressed = false;
    keyDispatcher = new MyDispatcher();
    addToAll(this.getContentPane(), keyDispatcher);
    //...remain of code

}

Disadvantage of this approach: when you modify your GUI dynamically (add/remove components after your GUI is created and listener is added to all components) you need to remove the listener from all components and add it again.

Upvotes: 1

Related Questions