Java 如何使用键绑定而不是键侦听器

Java 如何使用键绑定而不是键侦听器,java,swing,key-bindings,keyevent,key-events,Java,Swing,Key Bindings,Keyevent,Key Events,我在代码(游戏或其他)中使用s作为屏幕对象对用户键输入作出反应的方式。这是我的密码: 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

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

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教程解释了这一点

  • 键绑定不要求您单击组件(以使其具有焦点):
    • 从用户的角度删除意外行为
    • 如果有两个对象,它们不能同时移动,因为在给定时间只有一个对象可以具有焦点(即使将它们绑定到不同的关键点)
  • 键绑定更易于维护和操作:
    • 禁用、重新绑定和重新分配用户操作要容易得多
    • 代码更容易阅读
好吧,你说服我试试。它是如何工作的?

这本书有一个很好的章节。键绑定涉及两个对象
InputMap
ActionMap
InputMap
将用户输入映射到动作名称,
ActionMap
将动作名称映射到动作。当用户按下一个键时,将在输入映射中搜索该键并找到一个动作名称,然后在动作映射中搜索动作名称并执行该动作

看起来很笨重。为什么不直接将用户输入绑定到操作并去掉操作名呢?那么您只需要一张地图,而不需要两张。

好问题!您将看到,这是使键绑定更易于管理(禁用、重新绑定等)的因素之一

我想让你给我一个完整的工作代码。

否(Swing教程中有)

你真烂!我恨你

以下是如何进行单键绑定:

myComponent.getInputMap().put("userInput", "myAction");
myComponent.getActionMap().put("myAction", action);
请注意,有3个
InputMap
s对不同的焦点状态作出反应:

myComponent.getInputMap(JComponent.WHEN_FOCUSED);
myComponent.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
myComponent.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
  • WHEN_FOCUSED
    ,也就是在没有提供参数时使用的,在组件具有焦点时使用。这与密钥侦听器的情况类似
  • 当聚焦组件位于注册以接收操作的组件内时,使用聚焦组件的祖先时
    。如果你在一艘宇宙飞船里有许多宇航员,并且你想让飞船在任何一名宇航员都有焦点的情况下继续接收输入,那么使用这个
  • 当注册接收动作的组件位于聚焦组件内部时,使用聚焦窗口中的
    时。如果在一个聚焦窗口中有许多坦克,并且希望所有坦克同时接收输入,请使用此选项
假设同时控制两个对象,则问题中给出的代码将类似于此:

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.
        }
    }
}
您可以看到,将输入映射与操作映射分离可以实现可重用代码和更好的绑定控制。此外,如果需要该功能,还可以直接控制操作。例如:

FireAction p1Fire = new FireAction(1);
p1Fire.setEnabled(false); // Disable the action (for both players in this case).
有关更多信息,请参阅

我看到您使用了1个动作,移动,4个键(方向),1个动作,开火,1个键。为什么不让每个键都有自己的动作,或者让所有键都有相同的动作,并在动作中找出要做的事情(如移动案例)?

说得好。从技术上讲,您可以同时做到这两个方面,但您必须考虑什么是有意义的,以及什么允许轻松管理和可重用代码。在这里,我假设所有方向的移动都是相似的,而射击是不同的,所以我选择了这种方法

我看到很多人用
按键,这些是什么?它们是否像一个
KeyEvent

是的,它们有类似的功能,但更适合在这里使用。有关信息以及如何创建它们的信息,请参见它们


有问题吗?改进?建议?留下评论。 有更好的答案吗?发帖。

此答案解释并演示了如何使用键绑定而不是键侦听器进行教育。事实并非如此

  • 如何用Java编写游戏
  • 代码编写的好坏(例如可见性)
  • 实现键绑定的最有效(性能或代码方面)的方法
是的

  • 我想把这篇文章作为对那些在关键听众方面有困难的人的回答

答复;读这本书

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

Swing教程解释了这一点

  • 键绑定不要求您单击组件(以使其具有焦点):
    • 从用户的角度删除意外行为
    • 如果有两个对象,它们不能同时移动,因为在给定时间只有一个对象可以具有焦点(即使将它们绑定到不同的关键点)
  • 键绑定更易于维护和操作:
    • 禁用、重新绑定和重新分配用户操作要容易得多
    • 代码更容易阅读
好吧,你说服我试试。怎么办
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, i.e. the name following "VK_".
KeyStroke control = getKeyStroke("CONTROL"); 
KeyStroke control = getKeyStroke("ctrl CONTROL"); 
private static JLabel listener= new JLabel(); 
add(listener);
 private void setKeyBinding(String keyString, AbstractAction action) {
        listener.getInputMap().put(KeyStroke.getKeyStroke(keyString), keyString);
        listener.getActionMap().put(keyString, action);
    }
private void setKeyBinding(int keyCode, AbstractAction action) {
    int modifier = 0;
    switch (keyCode) {
        case KeyEvent.VK_CONTROL:
            modifier = InputEvent.CTRL_DOWN_MASK;
            break;
        case KeyEvent.VK_SHIFT:
            modifier = InputEvent.SHIFT_DOWN_MASK;
            break;
        case KeyEvent.VK_ALT:
            modifier = InputEvent.ALT_DOWN_MASK;
            break;

    }

    listener.getInputMap().put(KeyStroke.getKeyStroke(keyCode, modifier), keyCode);
    listener.getActionMap().put(keyCode, action);
}
  setKeyBinding(KeyEvent.VK_CONTROL, new AbstractAction() {

        @Override
        public void actionPerformed(ActionEvent e) {
            System.out.println("ctrl pressed");

        }
    });
 case KeyEvent.VK_ALT_GRAPH:
            modifier = InputEvent.ALT_GRAPH_DOWN_MASK;
            break;
// Create key bindings for controls
private void createKeyBindings(JPanel p) {
    InputMap im = p.getInputMap(JPanel.WHEN_IN_FOCUSED_WINDOW);
    ActionMap am = p.getActionMap();
    im.put(KeyStroke.getKeyStroke("W"), MoveAction.Action.MOVE_UP);
    im.put(KeyStroke.getKeyStroke("S"), MoveAction.Action.MOVE_DOWN);
    im.put(KeyStroke.getKeyStroke("A"), MoveAction.Action.MOVE_LEFT);
    im.put(KeyStroke.getKeyStroke("D"), MoveAction.Action.MOVE_RIGHT);
    am.put(MoveAction.Action.MOVE_UP, new MoveAction(this, MoveAction.Action.MOVE_UP));
    am.put(MoveAction.Action.MOVE_DOWN, new MoveAction(this, MoveAction.Action.MOVE_DOWN));
    am.put(MoveAction.Action.MOVE_LEFT, new MoveAction(this, MoveAction.Action.MOVE_LEFT));
    am.put(MoveAction.Action.MOVE_RIGHT, new MoveAction(this, MoveAction.Action.MOVE_RIGHT));
}
// Handles the key bindings
class MoveAction extends AbstractAction {

    enum Action {
        MOVE_UP, MOVE_DOWN, MOVE_LEFT, MOVE_RIGHT;
    }


    private static final long serialVersionUID = /* Some ID */;

    Window window;
    Action action;

    public MoveAction(Window window, Action action) {
        this.window = window;
        this.action = action;
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        switch (action) {
        case MOVE_UP:
            /* ... */
            break;
        case MOVE_DOWN:
            /* ... */
            break;
        case MOVE_LEFT:
            /* ... */
            break;
        case MOVE_RIGHT:
            /* ... */
            break;
        }
    }
}