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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/22741215/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 17:37:02  来源:igfitidea点击:

How to use Key Bindings instead of Key Listeners

javaswingkey-bindingskeyeventkey-events

提问by user1803551

I'm using KeyListeners in my code (game or otherwise) as the way for my on-screen objects to react to user key input. Here is my code:

KeyListener在我的代码(游戏或其他)中使用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);
        }
    }
}

I have problems with the responsiveness:

我的响应能力有问题:

  • I need to click on the object for it to work.
  • The response I get for pressing one of the keys is not how I wanted it to work - too responsive or too unresponsive.
  • 我需要单击该对象才能使其工作。
  • 我按其中一个键得到的响应不是我希望它工作的方式 - 太敏感或太迟钝。

Why does this happen and how do I fix this?

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

采纳答案by user1803551

This answer explains and demonstrates how to use key bindings instead of key listeners for educational purpose. It is not

此答案解释并演示了如何出于教育目的使用键绑定而不是键侦听器。它不是

  • How to write a game in Java.
  • How good code writing should look like (e.g. visibility).
  • The most efficient (performance- or code-wise) way to implement key bindings.
  • 如何用Java编写游戏。
  • 好的代码编写应该是什么样子(例如可见性)。
  • 实现键绑定的最有效(性能或代码方面)方式。

It is

这是

  • What I would post as an answer to anyone who is having trouble with key listeners.
  • 我将发布的内容作为对关键听众有问题的任何人的回答


Answer; Read the Swing tutorial on key bindings.

回答; 阅读有关键绑定Swing 教程

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!

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

Well, the Swing tutorialexplains that

好吧,Swing 教程解释了这一点

  • Key bindings don't require you to click the component (to give it focus):
    • Removes unexpected behavior from the user's point of view.
    • If you have 2 objects, they can't move simultaneously as only 1 of the objects can have the focus at a given time (even if you bind them to different keys).
  • Key bindings are easier to maintain and manipulate:
    • Disabling, rebinding, re-assigning user actions is much easier.
    • The code is easier to read.
  • 键绑定不需要您单击组件(为其提供焦点):
    • 从用户的角度删除意外行为。
    • 如果您有 2 个对象,则它们不能同时移动,因为在给定时间只有 1 个对象可以具有焦点(即使您将它们绑定到不同的键)。
  • 键绑定更容易维护和操作:
    • 禁用、重新绑定、重新分配用户操作要容易得多。
    • 代码更容易阅读。

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

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

The tutorialhas a good section about it. Key bindings involve 2 objects InputMapand ActionMap. InputMapmaps a user input to an action name, ActionMapmaps an action name to an Action. When the user presses a key, the input map is searched for the key and finds an action name, then the action map is searched for the action name and executes the action.

教程有一个很好的部分。键绑定涉及 2 个对象InputMapActionMap. InputMap将用户输入映射到操作名称,ActionMap将操作名称映射到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.

看起来很麻烦。为什么不将用户输入直接绑定到动作并去掉动作名称?那么你只需要一张地图而不是两张。

Good question! You will see that this is one of the things that make key bindings more manageable (disable, rebind etc.).

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

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

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

No (the Swing tutorialhas working examples).

否(Swing 教程工作示例)。

You suck!I hate you!

你好烂!我恨你!

Here is how to make a single key binding:

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

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

Note that there are 3 InputMaps reacting to different focus states:

请注意,InputMap对不同的焦点状态有 3秒的反应时间:

myComponent.getInputMap(JComponent.WHEN_FOCUSED);
myComponent.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
myComponent.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
  • WHEN_FOCUSED, which is also the one used when no argument is supplied, is used when the component has focus. This is similar to the key listener case.
  • WHEN_ANCESTOR_OF_FOCUSED_COMPONENTis used when a focused component is inside a component which is registered to receive the action. If you have many crew members inside a spaceship and you want the spaceship to continue receiving input while any of the crew members has focus, use this.
  • WHEN_IN_FOCUSED_WINDOWis used when a component which is registered to receive the action is inside a focused component. If you have many tanks in a focused window and you want all of them to receive input at the same time, use this.
  • WHEN_FOCUSED,这也是在不提供参数时使用的,当组件具有焦点时使用。这类似于关键侦听器的情况。
  • WHEN_ANCESTOR_OF_FOCUSED_COMPONENT当焦点组件位于注册接收操作的组件内时使用。如果您在宇宙飞船内有许多船员,并且您希望宇宙飞船在任何船员有焦点时继续接收输入,请使用此选项。
  • WHEN_IN_FOCUSED_WINDOW当注册接收动作的组件位于焦点组件内时使用。如果您在一个聚焦窗口中有许多坦克,并且您希望所有坦克同时接收输入,请使用此选项。

The code presented in the question will look something like this assuming both objects are to be controlled at the same time:

假设同时控制两个对象,问题中提供的代码将如下所示:

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

You can see that separating the input map from the action map allow reusable code and better control of bindings. In addition, you can also control an Action directly if you need the functionality. For example:

您可以看到将输入映射与操作映射分离允许可重用​​代码和更好地控制绑定。此外,如果需要该功能,您还可以直接控制 Action。例如:

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

See the Action tutorialfor more information.

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

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)?

我看到您使用了 1 个动作,移动,用于 4 个键(方向)和 1 个动作,射击,用于 1 个键。为什么不给每个键自己的动作,或者给所有键一个相同的动作并整理出在动作内部做什么(就像在移动案例中一样)?

Good point. Technically you can do both, but you have to think what makes sense and what allows for easy management and reusable code. Here I assumed moving is similar for all directions and firing is different, so I chose this approach.

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

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

我看到很多KeyStroke使用了,那些是什么?他们像KeyEvent?

Yes, they have a similar function, but are more appropriate for use here. See their APIfor info and on how to create them.

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



Questions? Improvements? Suggestions? Leave a comment. Have a better answer? Post it.

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

回答by kleopatra

Note: this is notan answer, just a comment with too much code :-)

注意:这不是答案,只是代码过多的评论:-)

Getting keyStrokes via getKeyStroke(String) is the correct way - but needs careful reading of the api doc:

通过 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, i.e. the name following "VK_".

The last line should better be exact name, that is case matters: for the down key the exact key code name is VK_DOWN, so the parameter must be "DOWN" (not "Down" or any other variation of upper/lower case letters)

最后一行最好是确切的名称,即大小写问题:对于向下键,确切的键代号是VK_DOWN,因此参数必须为“DOWN”(不是“Down”或任何其他大写/小写字母的变体)

Not entirely intuitive (read: had to dig a bit myself) is getting a KeyStroke to a modifier key. Even with proper spelling, the following will not work:

不完全直观(阅读:必须自己挖掘一点)将 KeyStroke 设置为修饰键。即使拼写正确,以下内容也不起作用:

KeyStroke control = getKeyStroke("CONTROL"); 

Deeper down in the awt event queue, a keyEvent for a single modifier key is created with itself as modifier. To bind to the control key, you need the stroke:

在 awt 事件队列的更深处,为单个修饰键创建了一个 keyEvent,其自身作为修饰符。要绑定到控制键,您需要笔画:

KeyStroke control = getKeyStroke("ctrl CONTROL"); 

回答by WVrock

Here is an easyway that would not require you to read hundreds of lines of code just learn a few lines long trick.

这是一个简单的方法,不需要您阅读数百行代码,只需学习几行长的技巧即可。

declare a new JLabel and add it to your JFrame (I didn't test it in other components)

声明一个新的 JLabel 并将其添加到您的 JFrame(我没有在其他组件中测试它)

private static JLabel listener= new JLabel(); 

The focus needs to stay on this for the keys to work though.

不过,重点需要停留在这一点上,才能让钥匙发挥作用。

In constructor :

在构造函数中:

add(listener);

Use this method:

使用这个方法:

OLD METHOD:

旧方法:

 private void setKeyBinding(String keyString, AbstractAction action) {
        listener.getInputMap().put(KeyStroke.getKeyStroke(keyString), keyString);
        listener.getActionMap().put(keyString, action);
    }

KeyString must be written properly. It is not typesafe and you must consult the official listto learn what is the keyString(it is not an official term) for each button.

KeyString 必须正确写入。它不是类型安全的,您必须查阅官方列表以了解每个按钮的 keyString(它不是官方术语)是什么。

NEW METHOD

新方法

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

In this new method you can simply set it using KeyEvent.VK_WHATEVER

在这种新方法中,您可以简单地使用 KeyEvent.VK_WHATEVER

EXAMPLE CALL:

示例调用:

  setKeyBinding(KeyEvent.VK_CONTROL, new AbstractAction() {

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

        }
    });

Send an anonymous class (or use subclass) of AbstractAction. Override its public void actionPerformed(ActionEvent e)and make it do whatever you want the key to do.

发送 AbstractAction 的匿名类(或使用子类)。覆盖它 public void actionPerformed(ActionEvent e)并让它做任何你想让钥匙做的事情。

PROBLEM:

问题:

I couldn't get it running for VK_ALT_GRAPH.

我无法为 VK_ALT_GRAPH 运行它。

 case KeyEvent.VK_ALT_GRAPH:
            modifier = InputEvent.ALT_GRAPH_DOWN_MASK;
            break;

does not make it work for me for some reason.

由于某种原因,它对我不起作用。

回答by George_E

Here is an example of how to get key bindings working.

这是一个如何使键绑定工作的示例。

(Inside JFramesubclass using extends, which is called by the constructor)

(在JFrame子类 using 中extends,由构造函数调用)

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

Separate class to handle those key bindings created above (where Windowis the class that extendsfrom JFrame)

单独的类来处理上面创建的那些键绑定(来自Window的类在哪里)extendsJFrame

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