简体   繁体   中英

java swing key bindings - missing action for released key

Having registered key bindings for "SPACE" and "released SPACE" which works as advertised when space is the only key pressed/released, I notice that pressing space, then pressing ctrl (or any other modifier key), then releasing space and finally releasing ctrl will cause the action associated with "SPACE" to be performed, but not the action associated with "released SPACE".

What is the preferred way to cause the action to be performed once space is no longer pressed (or a modifier key is pressed simultaneously)? I only tried this on Windows 7, 64-bit.

import javax.swing.SwingUtilities;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.AbstractAction;
import javax.swing.KeyStroke;
import java.awt.event.ActionEvent;
import java.awt.Cursor;

class Bind extends JPanel {
  {
    getInputMap().put(KeyStroke.getKeyStroke("SPACE"), "pressed");
    getInputMap().put(KeyStroke.getKeyStroke("released SPACE"), "released");
    getActionMap().put("pressed", new AbstractAction() {
      @Override public void actionPerformed(ActionEvent e) {
        System.out.println("pressed");
        setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR)); 
      }
    });
    getActionMap().put("released", new AbstractAction() {
      @Override public void actionPerformed(ActionEvent e) {
        System.out.println("released");
        setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); 
      }
    });
  }
  public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
      @Override public void run() {
        JFrame f = new JFrame("Key Bindings");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
        f.add(new Bind());
        f.setSize(640, 480);
        f.setVisible(true);
      }
    });
  }
}

UPDATE : This is the way to avoid sticky space when accidentally hitting ctrl, alt, or shift before releasing space:

import javax.swing.SwingUtilities;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.AbstractAction;
import javax.swing.KeyStroke;
import java.awt.event.ActionEvent;
import java.awt.Cursor;

class Bind extends JPanel {
  {
    getInputMap().put(KeyStroke.getKeyStroke("SPACE"), "pressed");
    getInputMap().put(KeyStroke.getKeyStroke("released SPACE"), "released");
    getInputMap().put(KeyStroke.getKeyStroke("ctrl released SPACE"), "released");
    getInputMap().put(KeyStroke.getKeyStroke("shift released SPACE"), "released");
    getInputMap().put(KeyStroke.getKeyStroke("shift ctrl released SPACE"), "released");
    getInputMap().put(KeyStroke.getKeyStroke("alt released SPACE"), "released");
    getInputMap().put(KeyStroke.getKeyStroke("alt ctrl released SPACE"), "released");
    getInputMap().put(KeyStroke.getKeyStroke("alt shift released SPACE"), "released");
    getInputMap().put(KeyStroke.getKeyStroke("alt shift ctrl released SPACE"), "released");
    getActionMap().put("pressed", new AbstractAction() {
      @Override public void actionPerformed(ActionEvent e) {
        System.out.println("pressed");
        setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR)); 
      }
    });
    getActionMap().put("released", new AbstractAction() {
      @Override public void actionPerformed(ActionEvent e) {
        System.out.println("released");
        setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); 
      }
    });
  }
  public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
      @Override public void run() {
        JFrame f = new JFrame("Key Bindings");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
        f.add(new Bind());
        f.setSize(640, 480);
        f.setVisible(true);
      }
    });
  }
}

Makes sense that the released SPACE event isn't fired when the Control key is still held down. I would expect a control released SPACE event to be fired.

Add the following to your code:

getInputMap().put(KeyStroke.getKeyStroke("control released SPACE"), "released");

For the same reason the SPACE event won't fire if you first hold the Control key down. So you would also need to add bindings for control SPACE .

You would need to do this for all the modifier keys, which may or may not be a simpler solution than tracking the key events.

It's possible that your OS doesn't fire keyReleased events, but only keyPressed and keyTyped events, or some other combination, so check for that first. You might just need to check for keyTyped events instead of keyReleased and you'll be done with it.

Short answer:

Use a bitmask or an array to keep track of which keys are currently in the "pressed" state, then use those valued to trigger events. That is, don't use the Swing events directly to trigger responses in your application - you need an extra layer that essentially stores the state of the keyboard, and from that state, takes the relevant actions.

There are also methods available ( see the end of this tutorial - "isAltDown", "isCtrlDown" etc. ) to check if modifier keys are pressed when you receive an event like the "Space" key being pressed.

Long answer:

You're correct that the events get fired when the keys get pressed and released. It kind of has to work that way so that you can support applications that should treat those events separately, as opposed to together. One example (though this isn't the only one) is video games on PC where you might be pressing multiple letter/modifier keys at once (for example, A to go left, and W to go forward) and the game has to treat these two event as distinct inputs, as opposed to composite inputs, resulting in your movement going forward-left.

So, what you basically want to do, if you need to deal with composite inputs, is have a simply array of the actions your app needs to respond to, and their associated key bindings (whether single or multi-keys doesn't really matter). When a key is pressed, you basically turn on a flag for that key that says it's currently "pressed", and clear the flag when it's released.

Then, to trigger your events, you just check all keys that are pressed (via checking which key "flags" are active), and if a particular event's key combination is pressed, then the event is fired.

If you have fewer than 32 keys that trigger events, then you can actually do this with a bitmask and a 32-bit int value, rather than an array. In fact, it's much simpler to do it this way if you can. If you need up to 64 keys, do the same thing with a long . If you have very few keys that trigger events (8 or less, for example) you can use the 8-bit short type.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM