如何使用Key Bindings而不是Key Listeners

我在我的代码(游戏或其他)中使用KeyListener作为我的屏幕对象对用户键input做出反应的方式。 这是我的代码:

 public class MyGame extends JFrame { static int up = KeyEvent.VK_UP; static int right = KeyEvent.VK_RIGHT; static int down = KeyEvent.VK_DOWN; static int left = KeyEvent.VK_LEFT; static int fire = KeyEvent.VK_Q; public MyGame() { // Do all the layout management and what not... JLabel obj1 = new JLabel(); JLabel obj2 = new JLabel(); obj1.addKeyListener(new MyKeyListener()); obj2.addKeyListener(new MyKeyListener()); add(obj1); add(obj2); // Do other GUI things... } static void move(int direction, Object source) { // do something } static void fire(Object source) { // do something } static void rebindKey(int newKey, String oldKey) { // Depends on your GUI implementation. // Detecting the new key by a KeyListener is the way to go this time. if (oldKey.equals("up")) up = newKey; if (oldKey.equals("down")) down = newKey; // ... } public static void main(String[] args) { new MyGame(); } private static class MyKeyListener extends KeyAdapter { @Override public void keyPressed(KeyEvent e) { Object source = e.getSource(); int action = e.getExtendedKeyCode(); /* Will not work if you want to allow rebinding keys since case variables must be constants. switch (action) { case up: move(1, source); case right: move(2, source); case down: move(3, source); case left: move(4, source); case fire: fire(source); ... } */ if (action == up) move(1, source); else if (action == right) move(2, source); else if (action == down) move(3, source); else if (action == left) move(4, source); else if (action == fire) fire(source); } } } 

我在响应方面遇到问题:

  • 我需要点击对象才能工作。
  • 我得到的按下其中一个键的响应不是我想要它的工作 – 太响应或太没有反应。

为什么会发生这种情况,我该如何解决这个问题?

这个答案解释和演示如何使用键绑定而不是关键听众的教育目的。 不是这样

  • 如何用Java编写游戏。
  • 代码的写法应该是什么样的(比如可见性)。
  • 实现密钥绑定的最有效的(性能或代码方式)方法。

它是

  • 我会作为一个答案给任何关键的听众有麻烦的人

回答; 阅读关于键绑定的Swing教程 。

我不想阅读手册,告诉我为什么我想要使用键绑定,而不是我已经漂亮的代码!

那么, Swing教程就解释了这一点

  • 键绑定不需要您单击该组件(以使其焦点):
    • 从用户的angular度去除意外的行为。
    • 如果您有两个对象,则它们不能同时移动,因为只有一个对象可以在给定时间拥有焦点(即使您将它们绑定到不同的键)。
  • 键绑定更容易维护和操作:
    • 禁用,重新绑定,重新分配用户操作要容易得多。
    • 代码更容易阅读。

好的,你说服我试试看。 它是如何工作的?

教程有一个很好的部分。 键绑定涉及2个对象InputMapActionMapInputMap将用户input映射到操作名称, ActionMap将操作名称映射到Action 。 当用户按下某个键时,会在input映射中search键并查找一个动作名称,然后在动作映射中search动作名称并执行动作。

看起来很麻烦。 为什么不把用户input直接绑定到动作上,并摆脱动作名称? 那么你只需要一个地图,而不是两个。

好问题! 你会看到这是使键绑定更易于pipe理(禁用,重新绑定等)的事情之一。

我希望你给我一个完整的工作代码。

否( Swing教程有工作示例 )。

你好烂! 我恨你!

这里是如何使一个键绑定:

 myComponent.getInputMap().put("userInput", "myAction"); myComponent.getActionMap().put("myAction", action); 

请注意,有3个InputMap对不同焦点状态作出反应:

 myComponent.getInputMap(JComponent.WHEN_FOCUSED); myComponent.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); myComponent.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); 
  • 当组件具有焦点时, WHEN_FOCUSED也用于不提供参数的情况。 这与关键听众的情况类似。
  • WHEN_ANCESTOR_OF_FOCUSED_COMPONENT被使用,当一个焦点组件在一个被注册接收这个动作的组件中。 如果你有一个宇宙飞船里面有许多船员,并且你希望宇宙飞船在任何一个船员都聚焦的情况下继续接收input,就用这个。
  • WHEN_IN_FOCUSED_WINDOW用于注册接收动作的组件在聚焦组件内。 如果在聚焦的窗口中有多个坦克,并且希望所有坦克同时接收input,请使用此窗口。

问题中提供的代码看起来像这样,假设两个对象将被同时控制:

 public class MyGame extends JFrame { private static final int IFW = JComponent.WHEN_IN_FOCUSED_WINDOW; private static final String MOVE_UP = "move up"; private static final String MOVE_DOWN = "move down"; private static final String FIRE = "move fire"; static JLabel obj1 = new JLabel(); static JLabel obj2 = new JLabel(); public MyGame() { // Do all the layout management and what not... obj1.getInputMap(IFW).put(KeyStroke.getKeyStroke("UP"), MOVE_UP); obj1.getInputMap(IFW).put(KeyStroke.getKeyStroke("DOWN"), MOVE_DOWN); // ... obj1.getInputMap(IFW).put(KeyStroke.getKeyStroke("control CONTROL"), FIRE); obj2.getInputMap(IFW).put(KeyStroke.getKeyStroke("W"), MOVE_UP); obj2.getInputMap(IFW).put(KeyStroke.getKeyStroke("S"), MOVE_DOWN); // ... obj2.getInputMap(IFW).put(KeyStroke.getKeyStroke("T"), FIRE); obj1.getActionMap().put(MOVE_UP, new MoveAction(1, 1)); obj1.getActionMap().put(MOVE_DOWN, new MoveAction(2, 1)); // ... obj1.getActionMap().put(FIRE, new FireAction(1)); obj2.getActionMap().put(MOVE_UP, new MoveAction(1, 2)); obj2.getActionMap().put(MOVE_DOWN, new MoveAction(2, 2)); // ... obj2.getActionMap().put(FIRE, new FireAction(2)); // In practice you would probably create your own objects instead of the JLabels. // Then you can create a convenience method obj.inputMapPut(String ks, String a) // equivalent to obj.getInputMap(IFW).put(KeyStroke.getKeyStroke(ks), a); // and something similar for the action map. add(obj1); add(obj2); // Do other GUI things... } static void rebindKey(KeyEvent ke, String oldKey) { // Depends on your GUI implementation. // Detecting the new key by a KeyListener is the way to go this time. obj1.getInputMap(IFW).remove(KeyStroke.getKeyStroke(oldKey)); // Removing can also be done by assigning the action name "none". obj1.getInputMap(IFW).put(KeyStroke.getKeyStrokeForEvent(ke), obj1.getInputMap(IFW).get(KeyStroke.getKeyStroke(oldKey))); // You can drop the remove action if you want a secondary key for the action. } public static void main(String[] args) { new MyGame(); } private class MoveAction extends AbstractAction { int direction; int player; MoveAction(int direction, int player) { this.direction = direction; this.player = player; } @Override public void actionPerformed(ActionEvent e) { // Same as the move method in the question code. // Player can be detected by e.getSource() instead and call its own move method. } } private class FireAction extends AbstractAction { int player; FireAction(int player) { this.player = player; } @Override public void actionPerformed(ActionEvent e) { // Same as the fire method in the question code. // Player can be detected by e.getSource() instead, and call its own fire method. // If so then remove the constructor. } } } 

您可以看到,将input映射与操作映射分开允许可重用​​的代码,并更好地控制绑定。 另外,如果你需要这个function,你也可以直接控制一个Action。 例如:

 FireAction p1Fire = new FireAction(1); p1Fire.setEnabled(false); // Disable the action (for both players in this case). 

有关更多信息,请参阅操作教程 。

我看到你用了1个动作,移动了4个键(方向)和1个动作,射击了1个键。 为什么不给每个键自己的行动,或给所有的键相同的行动,并解决在行动内部做什么(如在移动的情况下)?

好点子。 从技术上讲,你可以做到这一点,但你必须考虑什么是有意义的,什么允许简单的pipe理和可重用的代码。 在这里,我假设所有方向的移动都是相似的,而且射击是不同的,所以我select了这种方法。

我看到很多KeyStroke被使用,那是什么? 他们是不是一个KeyEvent

是的,他们有类似的function,但是更适合在这里使用。 查看他们的API获取信息和如何创build它们。


有问题吗? 改进? build议? 发表评论。 有更好的答案吗? 发表它。

注意:这不是一个答案,只是太多的代码注释:-)

通过getKeyStroke(String)获取keyStrokes是正确的方法 – 但需要仔细阅读api文档:

 modifiers := shift | control | ctrl | meta | alt | altGraph typedID := typed <typedKey> typedKey := string of length 1 giving Unicode character. pressedReleasedID := (pressed | released) key key := KeyEvent key code name, ie the name following "VK_". 

最后一行最好是确切的名称 ,这是大小写的问题:对于下键,确切的键码名称是VK_DOWN ,所以参数必须是“DOWN”(而不是“Down”或任何其他大写/小写字母的变体)

不完全直观的(阅读:不得不自己挖掘一点)获得修改键的KeyStroke。 即使正确的拼写,以下将无法正常工作:

 KeyStroke control = getKeyStroke("CONTROL"); 

在awt事件队列中更深入一点,单个修改键的keyEvent被创build,并且自己作为修饰符。 绑定到控制键,你需要中风:

 KeyStroke control = getKeyStroke("ctrl CONTROL");