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.
An event which indicates that a keystroke occurred in a component. This low-level event is generated by a component object (such as a text field) when a key is pressed, released, or typed.
Interface KeyListenerA keyboard event is generated when a key is pressed, released, or typed. The relevant method in the listener object is then invoked, and the KeyEvent is passed to it.
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.
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
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);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With