java - 如何使用按键绑定(bind)而不是按键监听器

标签 java swing key-bindings keyevent key-events

我正在使用KeyListener在我的代码(游戏或其他)中作为我的屏幕对象对用户按键输入使用react的方式。这是我的代码:

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

我的响应能力有问题:

  • 我需要点击该对象才能使其工作。
  • 按下其中一个键时得到的响应不是我希望的工作方式 - 响应太快或响应太迟钝。

为什么会发生这种情况以及如何解决这个问题?

最佳答案

这个答案解释并演示了如何使用键绑定(bind)而不是键监听器来达到教育目的。事实并非如此

  • 如何用 Java 编写游戏。
  • 良好的代码编写应该是什么样的(例如可见性)。
  • 实现按键绑定(bind)的最有效(性能或代码方面)的方法。

  • 我将发布此内容作为对关键听众有困难的任何人的答案
<小时/>

回答;阅读Swing tutorial on key bindings .

I don't want to read manuals, tell me why I would want to use key bindings instead of the beautiful code I have already!

嗯,Swing 教程解释了这一点

  • 按键绑定(bind)不需要您单击组件(以使其获得焦点):
    • 消除从用户角度来看的意外行为。
    • 如果您有 2 个对象,它们无法同时移动,因为在给定时间只有 1 个对象可以获得焦点(即使您将它们绑定(bind)到不同的键)。
  • 按键绑定(bind)更容易维护和操作:
    • 禁用、重新绑定(bind)、重新分配用户操作变得更加容易。
    • 代码更容易阅读。

OK, you convinced me to try it out. How does it work?

tutorial 有一个关于它的很好的部分。键绑定(bind)涉及 2 个对象 InputMapActionMapInputMap 将用户输入映射到操作名称,ActionMap 将操作名称映射到Action。当用户按下某个键时,会在输入映射中搜索该键并找到 Action 名称,然后在 Action 映射中搜索该 Action 名称并执行该 Action 。

Looks cumbersome. Why not bind the user input to directly to the action and get rid of the action name? Then you need only one map and not two.

好问题!您会发现这是使键绑定(bind)更易于管理的事情之一(禁用、重新绑定(bind)等)。

I want you to give me a full working code of this.

否(Swing 教程working examples )。

You suck! I hate you!

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

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

请注意,有 3 个 InputMap 对不同的焦点状态使用react:

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 当焦点组件位于注册为接收操作的组件内部时使用。如果一艘宇宙飞船内有许多船员,并且您希望宇宙飞船在任何船员获得焦点时继续接收输入,请使用此选项。
  • WHEN_IN_FOCUSED_WINDOW 在注册接收操作的组件位于焦点组件内部时使用。如果您在聚焦窗口中有许多坦克,并且希望所有坦克同时接收输入,请使用此选项。

假设要同时控制两个对象,问题中提供的代码将类似于以下内容:

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.
        }
    }
}

您可以看到,将输入映射与操作映射分开可以实现可重用的代码和更好的绑定(bind)控制。此外,如果您需要该功能,还可以直接控制Action。例如:

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

请参阅Action tutorial了解更多信息。

I see that you used 1 action, move, for 4 keys (directions) and 1 action, fire, for 1 key. Why not give each key its own action, or give all keys the same action and sort out what to do inside the action (like in the move case)?

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

I see a lot of KeyStrokes used, what are those? Are they like a KeyEvent?

是的,它们有类似的功能,但更适合用在这里。查看他们的API获取信息以及如何创建它们。

<小时/>

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

关于java - 如何使用按键绑定(bind)而不是按键监听器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35543172/

相关文章:

Java:用类型方法覆盖泛型方法?

java - 如何添加由标签数组中使用的图片制作的玩家在船上的运动

java - 双重格式验证中的 JSpinner

java - 我最近更改为 JTextPane,但我的加载功能已损坏

java - KeyBinding 中的操作不会在 JTable 单元格上执行

java - 取消设置 ajaxcheckbox 时, ListView 中的 wicket 文本区域会被清除

java - 本地开发中的 JAX-RS "Faked" header

java - mac 上 gridbaglayout 中 JTextArea 的高度

Emacs for Erlang 带有 vi 之类的键绑定(bind)和方便的简短引用?

java - 键监听器与键绑定(bind)?