Adam Crume
Adam Crume

Reputation: 15844

Listening to key events for a component hierarchy

I have a Swing app that needs to display different sets of controls based on whether the control or alt keys are pressed. I added a KeyListener to the main component, but it is notified only if that component is selected, not if a subcomponent is selected. Is there a way to listen to events for a component and all descendents?

Edit:

I tried using the main component's InputMap, but no event is fired when pressing a modifier key. Specifically, I have the following code:

InputMap inputMap = panel.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
inputMap.put(KeyStroke.getKeyStroke("pressed CONTROL"), "test1");
inputMap.put(KeyStroke.getKeyStroke("released CONTROL"), "test2");
ActionMap actionMap = panel.getActionMap();
actionMap.put("test1", new AbstractAction() {
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("pressed");
    }
});
actionMap.put("test2", new AbstractAction() {
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("released");
    }
});

When pressing and releasing the control key, this will print "released" but not "pressed". Nothing else is registering anything in any InputMap, so it's not as though something else is registered for the same key stroke.

Upvotes: 6

Views: 4715

Answers (4)

Christopher Bruns
Christopher Bruns

Reputation: 9528

Just change your getKeyStroke(...) invocation like so:

InputMap inputMap = panel.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_CONTROL, KeyEvent.CTRL_DOWN_MASK, false), "test1");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_CONTROL, 0, true), "test2");
ActionMap actionMap = panel.getActionMap();
actionMap.put("test1", new AbstractAction() {
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("pressed");
    }
});
actionMap.put("test2", new AbstractAction() {
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("released");
    }
});

I took me a lot of trial and error to come up with the correct incantation.

It is important to use getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT), to be able to listen to child and deeper widgets.

It is especially important to specify KeyEvent.CTRL_DOWN_MASK, in addition to KeyEvent.VK_CONTROL when looking for control key PRESS events. This particular detail was missing from your modified code example.

Upvotes: 4

camickr
camickr

Reputation: 324207

You might be able to use a Global Event Listener.

Upvotes: 1

Zach L
Zach L

Reputation: 16272

You may consider using the InputMap and ActionMap of some swing components. You can specify with JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT to indicate you want to respond to the given keystroke when either the component or its subcomponents are in focus.

If you don't want to use the InputMap or ActionMap, you could just add the KeyListener to all the child components:

for (Component child : parent.getComponents())
{
 child.addKeyListener(keyListener);
}

Upvotes: 2

Hovercraft Full Of Eels
Hovercraft Full Of Eels

Reputation: 285460

You may want to try to use key binding rather than KeyListeners. Key binding is a higher level construct and can listen for key presses even if the component that "listens" doesn't have the focus -- as opposed to KeyListeners. You can find more on these at the Swing tutorials: How to use key bindings

Upvotes: 2

Related Questions