KeyBinding 多个键按下不工作
KeyBinding multiple keys pressed not working
所以我有一个绘制的矩形,我想用箭头键移动,包括对角线移动,或者更确切地说允许同时按下多个键(换句话说,类似于玩家移动的移动2D 游戏)。我曾尝试使用无法正常工作的 KeyListener 来执行此操作。所以我决定转向 KeyBindings,我从这个网站找到了一个例子:https://coderanch.com/t/606742/java/key-bindings (Rob Camick's post)
我直接复制了代码,它按预期工作,只是它移动的是一个图标,而不是像我想做的那样移动一个绘制的矩形。我试图修改代码,以便它可以移动绘制的矩形,但只是失败了。这是我最近的尝试,也是最小的可重现性:
import java.awt.event.*;
import java.awt.*;
import javax.swing.*;
public class Test extends JPanel implements ActionListener
{
public JPanel component = this;
public int x = 100;
public int y = 100;
private Timer timer;
private int keysPressed;
private InputMap inputMap;
public void addAction(String name, int keyDeltaX, int keyDeltaY, int keyCode)
{
new NavigationAction(name, keyDeltaX, keyDeltaY, keyCode);
}
// Move the component to its new location
private void handleKeyEvent(boolean pressed, int deltaX, int deltaY)
{
keysPressed += pressed ? 1 : -1;
x += deltaX;
y += deltaY;
// Start the Timer when the first key is pressed
if (keysPressed == 1)
timer.start();
// Stop the Timer when all keys have been released
if (keysPressed == 0)
timer.stop();
}
// Invoked when the Timer fires
public void actionPerformed(ActionEvent e)
{
repaint();
}
class NavigationAction extends AbstractAction implements ActionListener
{
private int keyDeltaX;
private int keyDeltaY;
private KeyStroke pressedKeyStroke;
private boolean listeningForKeyPressed;
public NavigationAction(String name, int keyDeltaX, int keyDeltaY, int keyCode)
{
super(name);
this.keyDeltaX = keyDeltaX;
this.keyDeltaY = keyDeltaY;
pressedKeyStroke = KeyStroke.getKeyStroke(keyCode, 0, false);
KeyStroke releasedKeyStroke = KeyStroke.getKeyStroke(keyCode, 0, true);
inputMap.put(pressedKeyStroke, getValue(Action.NAME));
inputMap.put(releasedKeyStroke, getValue(Action.NAME));
component.getActionMap().put(getValue(Action.NAME), this);
listeningForKeyPressed = true;
}
public void actionPerformed(ActionEvent e)
{
// While the key is held down multiple keyPress events are generated,
// we only care about the first event generated
if (listeningForKeyPressed)
{
handleKeyEvent(true, keyDeltaX, keyDeltaY);
inputMap.remove(pressedKeyStroke);
listeningForKeyPressed = false;
}
else // listening for key released
{
handleKeyEvent(false, -keyDeltaX, -keyDeltaY);
inputMap.put(pressedKeyStroke, getValue(Action.NAME));
listeningForKeyPressed = true;
}
}
}
public void paintComponent(Graphics tool) {
super.paintComponent(tool);
System.out.println(x + ", " + y);
tool.drawRect(x, y, 50, 50);
}
public Test() {}
public static void main(String[] args)
{
new Test().create();
}
public void create() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
frame.setSize(600, 600);
frame.setLocationRelativeTo( null );
frame.getContentPane().add(component);
inputMap = component.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
timer = new Timer(24, this);
timer.setInitialDelay( 0 );
addAction("Left", -3, 0, KeyEvent.VK_LEFT);
addAction("Right", 3, 0, KeyEvent.VK_RIGHT);
addAction("Up", 0, -3, KeyEvent.VK_UP);
addAction("Down", 0, 3, KeyEvent.VK_DOWN);
frame.setVisible(true);
}
}
好的,所以我对您提供的代码存在的问题之一是您负有泄漏责任。 NavigationAction
不负责注册键绑定或修改 UI 的状态。它应该只负责生成状态已更改回负责处理程序的通知。
这可以通过使用某种模型来实现,该模型维护某种状态,由 NavigationAction
更新并由 UI 读取,或者,正如我'我们选择通过委托回调来完成。
另一个问题是,当用户释放密钥时会发生什么?另一个问题是,你如何处理重复按键的延迟(即当你按住按键时,第一个按键事件和重复按键事件之间有一个小的延迟)?
我们可以通过简单地改变思维方式来处理这些事情。与其使用 NavigationAction
来确定应该发生“什么”,不如用它来告诉我们 UI “什么时候”某事发生了。在此按下或释放方向键。
我们不是直接对击键做出反应,而是从状态模型“添加”或“删除”方向,并允许 Swing Timer
相应地更新 UI 的状态,这通常会更快更流畅。
它还有解耦逻辑的副作用,因为你不再需要关心状态“如何”改变了,只需要知道它是什么,然后你就可以决定你想如何对它做出反应。
这种方法通常(以某种形式)在游戏引擎中使用。
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.HashSet;
import java.util.Set;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.Timer;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public interface KeyActionHandler {
public void keyActionPerformed(Direction direction, boolean pressed);
}
public enum Direction {
LEFT, RIGHT, DOWN, UP;
}
class NavigationAction extends AbstractAction implements ActionListener {
private KeyActionHandler keyActionHandler;
private Direction direction;
private Boolean pressed;
public NavigationAction(Direction direction, boolean pressed, KeyActionHandler keyActionHandler) {
this.direction = direction;
this.pressed = pressed;
this.keyActionHandler = keyActionHandler;
}
public void actionPerformed(ActionEvent e) {
keyActionHandler.keyActionPerformed(direction, pressed);
}
}
public class TestPane extends JPanel implements ActionListener, KeyActionHandler {
private int xDelta = 0;
private int yDelta = 0;
public int x = 100;
public int y = 100;
private Timer timer;
public TestPane() {
timer = new Timer(24, this);
InputMap inputMap = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap actionMap = getActionMap();
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, false), "Pressed-Left");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false), "Pressed-Right");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, false), "Pressed-Up");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, false), "Pressed-Down");
actionMap.put("Pressed-Left", new NavigationAction(Direction.LEFT, true, this));
actionMap.put("Pressed-Right", new NavigationAction(Direction.RIGHT, true, this));
actionMap.put("Pressed-Up", new NavigationAction(Direction.UP, true, this));
actionMap.put("Pressed-Down", new NavigationAction(Direction.DOWN, true, this));
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, true), "Released-Left");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, true), "Released-Right");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, true), "Released-Up");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, true), "Released-Down");
actionMap.put("Released-Left", new NavigationAction(Direction.LEFT, false, this));
actionMap.put("Released-Right", new NavigationAction(Direction.RIGHT, false, this));
actionMap.put("Released-Up", new NavigationAction(Direction.UP, false, this));
actionMap.put("Released-Down", new NavigationAction(Direction.DOWN, false, this));
}
@Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
@Override
public void addNotify() {
super.addNotify();
timer.start();
}
@Override
public void removeNotify() {
super.removeNotify();
timer.stop();
}
private Set<Direction> directions = new HashSet<>();
// Invoked when the Timer fires
@Override
public void actionPerformed(ActionEvent e) {
if (directions.contains(Direction.LEFT)) {
x -= 3;
} else if (directions.contains(Direction.RIGHT)) {
x += 3;
}
if (directions.contains(Direction.UP)) {
y -= 3;
} else if (directions.contains(Direction.DOWN)) {
y += 3;
}
repaint();
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawRect(x, y, 50, 50);
}
@Override
public void keyActionPerformed(Direction direction, boolean pressed) {
if (pressed) {
directions.add(direction);
} else {
directions.remove(direction);
}
}
}
}
这只是解决同一问题的另一种方法 ;)
However, I am encountering a problem: it seems that removeNotify() is never called. I wanted to verify this and copied this post and pasted it into a new java file. I then added print statements in the add and remove notify methods, and only the print statement in the addNotify() method was printed. Also, if you add a print statement in the actionPerformed method, it prints repeatedly never ending
removeNotify
仅在移除组件或父容器时调用。
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setLayout(new GridBagLayout());
JButton btn = new JButton("Remove");
btn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent evt) {
Container parent = getParent();
parent.remove(TestPane.this);
parent.revalidate();
parent.repaint();
}
});
add(btn);
}
@Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
@Override
public void addNotify() {
super.addNotify();
System.out.println("Add");
}
@Override
public void removeNotify() {
super.removeNotify();
System.out.println("Remove");
}
}
}
我已验证 removeNotify
将在删除父容器时通过层次结构调用。
所以我有一个绘制的矩形,我想用箭头键移动,包括对角线移动,或者更确切地说允许同时按下多个键(换句话说,类似于玩家移动的移动2D 游戏)。我曾尝试使用无法正常工作的 KeyListener 来执行此操作。所以我决定转向 KeyBindings,我从这个网站找到了一个例子:https://coderanch.com/t/606742/java/key-bindings (Rob Camick's post)
我直接复制了代码,它按预期工作,只是它移动的是一个图标,而不是像我想做的那样移动一个绘制的矩形。我试图修改代码,以便它可以移动绘制的矩形,但只是失败了。这是我最近的尝试,也是最小的可重现性:
import java.awt.event.*;
import java.awt.*;
import javax.swing.*;
public class Test extends JPanel implements ActionListener
{
public JPanel component = this;
public int x = 100;
public int y = 100;
private Timer timer;
private int keysPressed;
private InputMap inputMap;
public void addAction(String name, int keyDeltaX, int keyDeltaY, int keyCode)
{
new NavigationAction(name, keyDeltaX, keyDeltaY, keyCode);
}
// Move the component to its new location
private void handleKeyEvent(boolean pressed, int deltaX, int deltaY)
{
keysPressed += pressed ? 1 : -1;
x += deltaX;
y += deltaY;
// Start the Timer when the first key is pressed
if (keysPressed == 1)
timer.start();
// Stop the Timer when all keys have been released
if (keysPressed == 0)
timer.stop();
}
// Invoked when the Timer fires
public void actionPerformed(ActionEvent e)
{
repaint();
}
class NavigationAction extends AbstractAction implements ActionListener
{
private int keyDeltaX;
private int keyDeltaY;
private KeyStroke pressedKeyStroke;
private boolean listeningForKeyPressed;
public NavigationAction(String name, int keyDeltaX, int keyDeltaY, int keyCode)
{
super(name);
this.keyDeltaX = keyDeltaX;
this.keyDeltaY = keyDeltaY;
pressedKeyStroke = KeyStroke.getKeyStroke(keyCode, 0, false);
KeyStroke releasedKeyStroke = KeyStroke.getKeyStroke(keyCode, 0, true);
inputMap.put(pressedKeyStroke, getValue(Action.NAME));
inputMap.put(releasedKeyStroke, getValue(Action.NAME));
component.getActionMap().put(getValue(Action.NAME), this);
listeningForKeyPressed = true;
}
public void actionPerformed(ActionEvent e)
{
// While the key is held down multiple keyPress events are generated,
// we only care about the first event generated
if (listeningForKeyPressed)
{
handleKeyEvent(true, keyDeltaX, keyDeltaY);
inputMap.remove(pressedKeyStroke);
listeningForKeyPressed = false;
}
else // listening for key released
{
handleKeyEvent(false, -keyDeltaX, -keyDeltaY);
inputMap.put(pressedKeyStroke, getValue(Action.NAME));
listeningForKeyPressed = true;
}
}
}
public void paintComponent(Graphics tool) {
super.paintComponent(tool);
System.out.println(x + ", " + y);
tool.drawRect(x, y, 50, 50);
}
public Test() {}
public static void main(String[] args)
{
new Test().create();
}
public void create() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
frame.setSize(600, 600);
frame.setLocationRelativeTo( null );
frame.getContentPane().add(component);
inputMap = component.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
timer = new Timer(24, this);
timer.setInitialDelay( 0 );
addAction("Left", -3, 0, KeyEvent.VK_LEFT);
addAction("Right", 3, 0, KeyEvent.VK_RIGHT);
addAction("Up", 0, -3, KeyEvent.VK_UP);
addAction("Down", 0, 3, KeyEvent.VK_DOWN);
frame.setVisible(true);
}
}
好的,所以我对您提供的代码存在的问题之一是您负有泄漏责任。 NavigationAction
不负责注册键绑定或修改 UI 的状态。它应该只负责生成状态已更改回负责处理程序的通知。
这可以通过使用某种模型来实现,该模型维护某种状态,由 NavigationAction
更新并由 UI 读取,或者,正如我'我们选择通过委托回调来完成。
另一个问题是,当用户释放密钥时会发生什么?另一个问题是,你如何处理重复按键的延迟(即当你按住按键时,第一个按键事件和重复按键事件之间有一个小的延迟)?
我们可以通过简单地改变思维方式来处理这些事情。与其使用 NavigationAction
来确定应该发生“什么”,不如用它来告诉我们 UI “什么时候”某事发生了。在此按下或释放方向键。
我们不是直接对击键做出反应,而是从状态模型“添加”或“删除”方向,并允许 Swing Timer
相应地更新 UI 的状态,这通常会更快更流畅。
它还有解耦逻辑的副作用,因为你不再需要关心状态“如何”改变了,只需要知道它是什么,然后你就可以决定你想如何对它做出反应。
这种方法通常(以某种形式)在游戏引擎中使用。
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.HashSet;
import java.util.Set;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.Timer;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public interface KeyActionHandler {
public void keyActionPerformed(Direction direction, boolean pressed);
}
public enum Direction {
LEFT, RIGHT, DOWN, UP;
}
class NavigationAction extends AbstractAction implements ActionListener {
private KeyActionHandler keyActionHandler;
private Direction direction;
private Boolean pressed;
public NavigationAction(Direction direction, boolean pressed, KeyActionHandler keyActionHandler) {
this.direction = direction;
this.pressed = pressed;
this.keyActionHandler = keyActionHandler;
}
public void actionPerformed(ActionEvent e) {
keyActionHandler.keyActionPerformed(direction, pressed);
}
}
public class TestPane extends JPanel implements ActionListener, KeyActionHandler {
private int xDelta = 0;
private int yDelta = 0;
public int x = 100;
public int y = 100;
private Timer timer;
public TestPane() {
timer = new Timer(24, this);
InputMap inputMap = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap actionMap = getActionMap();
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, false), "Pressed-Left");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false), "Pressed-Right");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, false), "Pressed-Up");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, false), "Pressed-Down");
actionMap.put("Pressed-Left", new NavigationAction(Direction.LEFT, true, this));
actionMap.put("Pressed-Right", new NavigationAction(Direction.RIGHT, true, this));
actionMap.put("Pressed-Up", new NavigationAction(Direction.UP, true, this));
actionMap.put("Pressed-Down", new NavigationAction(Direction.DOWN, true, this));
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, true), "Released-Left");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, true), "Released-Right");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, true), "Released-Up");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, true), "Released-Down");
actionMap.put("Released-Left", new NavigationAction(Direction.LEFT, false, this));
actionMap.put("Released-Right", new NavigationAction(Direction.RIGHT, false, this));
actionMap.put("Released-Up", new NavigationAction(Direction.UP, false, this));
actionMap.put("Released-Down", new NavigationAction(Direction.DOWN, false, this));
}
@Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
@Override
public void addNotify() {
super.addNotify();
timer.start();
}
@Override
public void removeNotify() {
super.removeNotify();
timer.stop();
}
private Set<Direction> directions = new HashSet<>();
// Invoked when the Timer fires
@Override
public void actionPerformed(ActionEvent e) {
if (directions.contains(Direction.LEFT)) {
x -= 3;
} else if (directions.contains(Direction.RIGHT)) {
x += 3;
}
if (directions.contains(Direction.UP)) {
y -= 3;
} else if (directions.contains(Direction.DOWN)) {
y += 3;
}
repaint();
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawRect(x, y, 50, 50);
}
@Override
public void keyActionPerformed(Direction direction, boolean pressed) {
if (pressed) {
directions.add(direction);
} else {
directions.remove(direction);
}
}
}
}
这只是解决同一问题的另一种方法 ;)
However, I am encountering a problem: it seems that removeNotify() is never called. I wanted to verify this and copied this post and pasted it into a new java file. I then added print statements in the add and remove notify methods, and only the print statement in the addNotify() method was printed. Also, if you add a print statement in the actionPerformed method, it prints repeatedly never ending
removeNotify
仅在移除组件或父容器时调用。
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setLayout(new GridBagLayout());
JButton btn = new JButton("Remove");
btn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent evt) {
Container parent = getParent();
parent.remove(TestPane.this);
parent.revalidate();
parent.repaint();
}
});
add(btn);
}
@Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
@Override
public void addNotify() {
super.addNotify();
System.out.println("Add");
}
@Override
public void removeNotify() {
super.removeNotify();
System.out.println("Remove");
}
}
}
我已验证 removeNotify
将在删除父容器时通过层次结构调用。