Reputation: 4010
If you add a Key Binding in java with a mask - let's just say the ActionEvent.ALT_MASK with KeyEvent.VK_A - and then you perform that key (ALT + A) BUT, you release the alt key just before the 'A' key, you will usually encounter a problem where the actionPerformed() in a class (implementing ActionListener) will keep being activated. This probably (98% sure) means that the Key Binding never registered that the key was released. If you release the 'A' key before the alt key, you are fine, but - like I said - if you release the alt key perhaps 1/10 of a second before the other key, it keeps repeating.
Note: This only happens - apparently - in my program (here)
Try it for yourself if you don't believe me. Here is a snippet of my code:
public ConwayPanel() {
super();
setBackground(new Color(245, 255, 245, 255)); // BG slightly green - all ready
paused = true; // nothing to play... in FUTURE put cool organism in
startX = 0; // starting position of the left of the grid
startY = 0; // starting position of the top of the grid
zoom = 15; // the width of each cell (EXCLUDING the lines that make up the boundaries)
cellNum = 1000; // The number of cells
cells = new boolean[cellNum][cellNum]; // populate cells with false/dead
currentX = 0; // current x cursor position
currentY = 0; // current y cursor position
flipBoundaries = new int[4];
hideCurrentPos = false; // don't want to hide cursor position unless explicitly told to do so
defineMaps(); // creates Key enums
setKeyBindings(); // defines Key and KeyNoMask key bindings
Timer timer = new Timer(100, new KeyListener());
timer.start();
setupMouseListeners(); // creates MouseListener, MouseMotionListener and MouseWheelListener
setFocusable(true); // make isFocusable() true
requestFocusInWindow(); // get focus for listeners
}
private void defineMaps() {
for (KeyAltMask key : KeyAltMask.values()) {
keyMap.put(key, false); // value true when key is pressed - all initiated to false
}
for (KeyNoMask key : KeyNoMask.values()) {
keyNoMaskMap.put(key, false); // value true when key is pressed - all initiated to false
}
}
private void setKeyBindings() {
InputMap inMap = getInputMap(JComponent.WHEN_FOCUSED/* or... WHEN_IN_FOCUSED_WINDOW*/);
ActionMap actMap = getActionMap();
for (final KeyAltMask key : KeyAltMask.values()) {
KeyStroke pressed = KeyStroke.getKeyStroke(key.getKeyCode(), ActionEvent.ALT_MASK, false); // just right! (not blocking shortcut key and preventing accidental keyboard mishaps)
KeyStroke released = KeyStroke.getKeyStroke(key.getKeyCode(), ActionEvent.ALT_MASK, true); // just right! (not blocking shortcut key and preventing accidental keyboard mishaps)
inMap.put(pressed, key.toString() + "pressed");
inMap.put(released, key.toString() + "released");
actMap.put(key.toString() + "pressed", new AbstractAction() { // adds each value of Key into a HashMap (when the key is pressed) and puts that HashMap action into ActionMap
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
keyMap.put(key, true);
}
});
actMap.put(key.toString() + "released", new AbstractAction() { // adds each value of Key into a HashMap (when the key is released) and puts that HashMap action into ActionMap
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
keyMap.put(key, false);
}
});
}
for (final KeyNoMask key : KeyNoMask.values()) {
KeyStroke pressed = KeyStroke.getKeyStroke(key.getKeyCode(), 0, false);
KeyStroke released = KeyStroke.getKeyStroke(key.getKeyCode(), 0, true);
inMap.put(pressed, key.toString() + "pressed");
inMap.put(released, key.toString() + "released");
actMap.put(key.toString() + "pressed", new AbstractAction() { // adds each value of KeyNoMask into a HashMap (when the key is pressed) and puts that HashMap action into ActionMap
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
keyNoMaskMap.put(key, true);
}
});
actMap.put(key.toString() + "released", new AbstractAction() { // adds each value of KeyNoMask into a HashMap (when the key is released) and puts that HashMap action into ActionMap
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
keyNoMaskMap.put(key, false);
}
});
}
}
private class KeyListener implements ActionListener { // probably not great to have same name, but "real" KeyListener not imported
@Override
public void actionPerformed(ActionEvent e) {
for (KeyAltMask key : KeyAltMask.values()) { // run through the ALL of the keys
if (keyMap.get(key)) { // if key in HashMap is true (i.e. the actionPerformed() above set it true)
switch(key.toString()) {
case "c": // clear all cells and pause if not paused
for (int y = 0; y < cellNum; y++) {
for (int x = 0; x < cellNum; x++) {
cells[x][y] = false;
}
}
if (!paused) {
paused = true;
}
break;
case "f": // fill all cells and pause if not paused
for (int y = 0; y < cellNum; y++) {
for (int x = 0; x < cellNum; x++) {
cells[x][y] = true;
}
if (!paused) {
paused = true;
}
}
break;
case "i": // invert all cells and pause if not paused
for (int y = 0; y < cellNum; y++) {
for (int x = 0; x < cellNum; x++) {
cells[x][y] = !cells[x][y];
}
if (!paused) {
paused = true;
}
}
break;
case "l": // lock all cells that have a live/true cell
for (int y = 0; y < cellNum; y++) {
for (int x = 0; x < cellNum; x++) {
if (cells[x][y]) {
//set Lock
}
}
}
break;
case "p": // pause/play
paused = !paused;
break;
case "s": // step once
step = true;
break;
case "h": // hide current cursor position
hideCurrentPos = !hideCurrentPos;
break;
// default:
}
}
}
for (KeyNoMask key : KeyNoMask.values()) { // run through ALL of the keys (this is the beauty of key bindings - you can move the cursor diagonally). I kinda like a pause after the first key press, though
if (keyNoMaskMap.get(key)) { // if key in HashMap is true (i.e. the actionPerformed() above returned true)
switch(key.toString()) { // move cursor position appropriately and pause if not paused
case "down":
currentY += currentY == cellNum - 1 ? 0 : 1;
if (!paused) {
paused = true;
}
break;
case "up":
currentY -= currentY == 0 ? 0 : 1;
if (!paused) {
paused = true;
}
break;
case "left":
currentX -= currentX == 0 ? 0 : 1;
if (!paused) {
paused = true;
}
break;
case "right":
currentX += currentX == cellNum - 1 ? 0 : 1;
if (!paused) {
paused = true;
}
break;
case "space": // flip pixel at current cursor position
flipCell(currentX, currentY);
if (!paused) {
paused = true;
}
// default:
}
}
}
}
}
It's a lot of code, but it's pretty standard for KeyBindings at least. So, I was wondering if there was a way to get around this. Is this the os's fault or is it Java's fault and how can I fix it. I would like to avoid having an else
in actionPerformed()
because I need this to be fast. Also, is there anyway to optimize the actionPerformed()
method because it seems like it might be a little bit screwy.
I just put this together, but it doesn't do it here! Small executable:
package bindingstest;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.HashMap;
import java.util.Map;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.Timer;
/**
*
* @author Dylan AND Hovercraft Full Of Eels
*/
public class BindingsTest {
static Map<Key, Boolean> keyMap = new HashMap<>();
enum Key { // possibly used in conjunction with mask in order to prevent keyboard mishaps - it will probably be ALT in FUTURE
a(KeyEvent.VK_A),
b(KeyEvent.VK_B),
c(KeyEvent.VK_C),
d(KeyEvent.VK_D),
e(KeyEvent.VK_E),
f(KeyEvent.VK_F);
private final int keyCode;
private Key(int keyCode) {
this.keyCode = keyCode; // KeyEvent.VK_...
}
public int getKeyCode() {
return keyCode;
}
}
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setVisible(true);
frame.setBounds(50, 50, 1000, 1000);
JPanel panel = new JPanel();
panel.setFocusable(true);
panel.requestFocusInWindow();
for (Key key : Key.values()) {
keyMap.put(key, false);
}
InputMap inMap = panel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
ActionMap actMap = panel.getActionMap();
for (final Key key : Key.values()) {
KeyStroke pressed = KeyStroke.getKeyStroke(key.getKeyCode(), ActionEvent.ALT_MASK, false);
KeyStroke released = KeyStroke.getKeyStroke(key.getKeyCode(), ActionEvent.ALT_MASK, true);
inMap.put(pressed, key.toString() + "pressed");
inMap.put(released, key.toString() + "released");
actMap.put(key.toString() + "pressed", new AbstractAction() {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
keyMap.put(key, true);
}
});
actMap.put(key.toString() + "released", new AbstractAction() {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
keyMap.put(key, false);
}
});
}
for (final Key key : Key.values()) {
KeyStroke pressed = KeyStroke.getKeyStroke(key.getKeyCode(), 0, false);
KeyStroke released = KeyStroke.getKeyStroke(key.getKeyCode(), 0, true);
inMap.put(pressed, key.toString() + "pressed");
inMap.put(released, key.toString() + "released");
actMap.put(key.toString() + "pressed", new AbstractAction() {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
keyMap.put(key, true);
}
});
actMap.put(key.toString() + "released", new AbstractAction() {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
keyMap.put(key, false);
}
});
}
Timer timer = new Timer(100, new KeyListener());
timer.start();
frame.add(panel);
}
private static class KeyListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
for (Key key : Key.values()) { // run through the ALL of the keys
if (keyMap.get(key)) { // if key in HashMap is true (i.e. the actionPerformed() above set it true)
switch(key.toString()) {
case "a":
System.out.println("a");
break;
case "b":
System.out.println("b");
break;
case "c":
System.out.println("c");
break;
case "d":
System.out.println("d");
break;
case "e":
System.out.println("e");
break;
case "f":
System.out.println("f");
}
}
}
}
}
}
Upvotes: 5
Views: 231
Reputation: 285405
OK, I see what you're saying, and thanks for posting compilable code. One solution to use both release KeyStrokes, one for alt-key and one for plain key. For example,
InputMap inMap = panel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
ActionMap actMap = panel.getActionMap();
for (final Key key : Key.values()) {
KeyStroke altPressed = KeyStroke.getKeyStroke(key.getKeyCode(),
InputEvent.ALT_DOWN_MASK, false);
KeyStroke altReleased = KeyStroke.getKeyStroke(key.getKeyCode(),
InputEvent.ALT_DOWN_MASK, true);
KeyStroke released = KeyStroke.getKeyStroke(key.getKeyCode(),
0, true);
inMap.put(altPressed, altPressed.toString());
inMap.put(altReleased, altReleased.toString());
inMap.put(released, released.toString());
actMap.put(altPressed.toString(), new AbstractAction() {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
keyMap.put(key, true);
}
});
Action releaseAction = new AbstractAction() {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
keyMap.put(key, false);
}
};
actMap.put(altReleased.toString(), releaseAction);
actMap.put(released.toString(), releaseAction);
Another solution is to not do the above, but rather to re-set the Map with each iteration of the Timer:
for (Key key : Key.values()) { // run through the ALL of the keys
if (keyMap.get(key)) { // if key in HashMap is true (i.e. the
// actionPerformed() above set it true)
switch (key.toString()) {
case "a":
System.out.println("a");
break;
case "b":
System.out.println("b");
break;
case "c":
System.out.println("c");
break;
case "d":
System.out.println("d");
break;
case "e":
System.out.println("e");
break;
case "f":
System.out.println("f");
}
// ***** add this *****
keyMap.put(key, Boolean.FALSE);
}
}
This second solution suffers from the delay that the OS puts in keystroke submission when the key is held down.
Upvotes: 5