Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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);
      }
    });
  }
}
like image 808
Aksel Avatar asked Dec 15 '11 18:12

Aksel


People also ask

How do I trigger a JButton with a key press?

How to trigger a JButton with a key press? Add an ActionListener to the button. It will fire an event when the button is in focus and the user presses the enter key.

What is a JTextField in Java?

JTextField is a lightweight component that allows the editing of a single line of text. For information on and examples of using text fields, see How to Use Text Fields in The Java Tutorial. JTextField is intended to be source-compatible with java.

What is key binding in Java?

Key bindings involve 2 objects InputMap and ActionMap . InputMap maps a user input to an action name, ActionMap maps an action name to an Action . When the user presses a key, the input map is searched for the key and finds an action name, then the action map is searched for the action name and executes the action.

How can we define action code through Java explain with example program?

An Action can be used to separate functionality and state from a component. For example, if you have two or more components that perform the same function, consider using an Action object to implement the function.


2 Answers

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.

like image 27
jefflunt Avatar answered Oct 18 '22 08:10

jefflunt


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.

like image 107
camickr Avatar answered Oct 18 '22 09:10

camickr