I'm working on a game using Swing and I'm using KeyBindings for input from the keyboard.
I'm having issues where the KeyBindings stop responding. It happens every time I run the application, but as far as I can tell, it's not when a certain chain of events occurs. The KeyBindings just stop receiving input from the keyboard. I also use mouse input, which continues to work, so I know it's not related to input in general.
Some things I've tried:
KeyListener
insteadNone of these worked.
I then copied the project (without changing any code) to a windows machine, and after testing, I could not get the problem to occur once. I didn't paste code here because I'm 99% positive this is not related to my code, but related to the operating system it's running on.
Is this a problem with the macOS, or the JDK for Mac, or something else I don't know about?
I'm using JDK version 8 update 112, the computer is running macOS Sierra version 10.12.2.
It seems someone else had the same problem I now do, but they never got an answer.
EDIT
Here's a code example where the problem occurs:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
//an example of the problem where the keyboard stops receiving input randomly
public class ProblemExample extends JPanel {
private static final long serialVersionUID = 1L;
private int xPos = 200, yPos = 200;
private boolean wKey, aKey, sKey, dKey;
private BufferedImage image; //sprite
public ProblemExample() {
JFrame frame = new JFrame();
frame.add(this);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
//makes the sprite a red square
image = new BufferedImage(50, 50, BufferedImage.TYPE_INT_ARGB);
int[] redPixels = new int[50 * 50];
for (int i = 0; i < redPixels.length; i++) {
redPixels[i] = 0xffff0000;
}
image.setRGB(0, 0, 50, 50, redPixels, 0, 50);
initializeKeys();
}
@Override
public Dimension getPreferredSize() {
return new Dimension(800, 600);
}
//sets up Key Bindings
private void initializeKeys() {
final String W = "W",
A = "A",
S = "S",
D = "D",
PRESSED = "PRESSED",
RELEASED = "RELEASED";
InputMap inputMap = this.getInputMap(JPanel.WHEN_IN_FOCUSED_WINDOW);
ActionMap actionMap = this.getActionMap();
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, false), W + PRESSED);
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, false), A + PRESSED);
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, false), S + PRESSED);
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, false), D + PRESSED);
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, true), W + RELEASED);
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, true), A + RELEASED);
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, true), S + RELEASED);
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, true), D + RELEASED);
Action wActionPressed = new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
wKey = true;
}
};
Action aActionPressed = new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
aKey = true;
}
};
Action sActionPressed = new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
sKey = true;
}
};
Action dActionPressed = new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
dKey = true;
}
};
Action wActionReleased = new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
wKey = false;
}
};
Action aActionReleased = new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
aKey = false;
}
};
Action sActionReleased = new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
sKey = false;
}
};
Action dActionReleased = new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
dKey = false;
}
};
actionMap.put(W + PRESSED, wActionPressed);
actionMap.put(A + PRESSED, aActionPressed);
actionMap.put(S + PRESSED, sActionPressed);
actionMap.put(D + PRESSED, dActionPressed);
actionMap.put(W + RELEASED, wActionReleased);
actionMap.put(A + RELEASED, aActionReleased);
actionMap.put(S + RELEASED, sActionReleased);
actionMap.put(D + RELEASED, dActionReleased);
}
public void loop() {
if (wKey) yPos -= 5;
if (aKey) xPos -= 5;
if (sKey) yPos += 5;
if (dKey) xPos += 5;
repaint();
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image, xPos, yPos, null);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
ProblemExample example = new ProblemExample();
Timer timer = new Timer(60, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
example.loop();
}
});
timer.start();
}
});
}
}
The JComponent class supports key bindings as a way of responding to individual keys typed by a user. Here are some examples of when key bindings are appropriate:
How Key Bindings Work. The key binding support provided by JComponent relies on the InputMap and ActionMap classes. An input map binds key strokes to action names, and an action map specifies the action corresponding to each action name. Technically, you don't need to use action names in the maps; you can use any object as the "key" into the maps.
Here are some examples of when key bindings are appropriate: You're creating a custom component and want to support keyboard access to it. For example, you might want the component to react when it has the focus and the user presses the Space key. You want to override the behavior of an existing key binding.
An alternative to key bindings is using key listeners. Key listeners have their place as a low-level interface to keyboard input, but for responding to individual keys key bindings are more appropriate and tend to result in more easily maintained code.
You might think this is a bug in MAC, while it is not Because MAC has the feature of secondary keys.
As you are the user of MAC, I hope you are aware of this feature. This feature might be the the cause of your problem.
The Secondary key feature of MAC : is used where holding down a letter key will display variations of that letter, like holding down “u” to get “ü.” This comes in handy when spelling non-English words.
There’s a simple way to take control and change the behavior of long key presses to accommodate your needs.
Open the Terminal app and write:
defaults write NSGlobalDomain ApplePressAndHoldEnabled -bool false
Then restart any open app in which you want this setting to activate.
REVERTING BACK: Just,simply add true
in place of false
to the previous command like this:
defaults write NSGlobalDomain ApplePressAndHoldEnabled -bool true
UPDATE:
You can speed up the rate of key repeats or decrease the delay before a held key starts repeating, by going to the System Preferences, and make changes under the keyboard header.
Besides what Tahir Hussain Mir suggest about changing the Mac key settings, I find the problem lies with the looping, it'd be more efficient to implement it in a event-driven way, I took the liberty to change your code a bit. Together with Hussain Mir suggest 's solution, it should solve your problem.
You could also handle key-repeating yourself, by e.g., starting a timer on key press and stoping it on key release, but then the key pressing behavior will differ between Windows and Mac, which isn't really the way you want to go.
package swing;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
//an example of the problem where the keyboard stops receiving input randomly
public class SOMacKeyBindings extends JPanel
{
private BufferedImage image; //sprite
private Point point = new Point(200, 200);
private int steps = 5;
private class KeyAction extends AbstractAction
{
private Runnable runnable;
public KeyAction(Runnable runnable)
{
this.runnable = runnable;
}
@Override
public void actionPerformed(ActionEvent e)
{
runnable.run();
}
}
public SOMacKeyBindings()
{
JFrame frame = new JFrame();
frame.add(this);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
//makes the sprite a red square
image = new BufferedImage(50, 50, BufferedImage.TYPE_INT_ARGB);
int[] redPixels = new int[50 * 50];
for (int i = 0; i < redPixels.length; i++)
{
redPixels[i] = 0xffff0000;
}
image.setRGB(0, 0, 50, 50, redPixels, 0, 50);
initializeKeys();
}
@Override
public Dimension getPreferredSize()
{
return new Dimension(800, 600);
}
//sets up Key Bindings
private void initializeKeys()
{
final String W = "W",
A = "A",
S = "S",
D = "D",
PRESSED = "PRESSED";
InputMap inputMap = this.getInputMap(JPanel.WHEN_IN_FOCUSED_WINDOW);
ActionMap actionMap = this.getActionMap();
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, false), W + PRESSED);
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, false), A + PRESSED);
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, false), S + PRESSED);
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, false), D + PRESSED);
actionMap.put(W + PRESSED, new KeyAction(() -> { point.y -= steps; repaint(); }));
actionMap.put(A + PRESSED, new KeyAction(() -> { point.x -= steps; repaint(); }));
actionMap.put(S + PRESSED, new KeyAction(() -> { point.y += steps; repaint(); }));
actionMap.put(D + PRESSED, new KeyAction(() -> { point.x += steps; repaint(); }));
}
@Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
g.drawImage(image, point.x, point.y, null);
}
public static void main(String[] args)
{
SwingUtilities.invokeLater(() -> new SOMacKeyBindings());
}
}
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