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");
}
}
}
}
}
}
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.
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