如何使用键绑定而不是键侦听器 [英] How to use Key Bindings instead of Key Listeners

查看:144
本文介绍了如何使用键绑定而不是键侦听器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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:

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

我的响应能力有问题:


  • 我需要点击对象才能使用。

  • 我得到的回复按下其中一个键不是我想要的方式 - 反应太快或反应太迟。

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

Why does this happen and how do I fix this?

推荐答案

这个答案解释并演示了如何使用键绑定代替键侦听器用于教育目的。它不是


  • 如何用Java编写游戏。

  • 代码编写应该如何(例如,可见性)。

  • 最有效(性能或代码方式)的方式实现密钥绑定。

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

它是


  • 对于遇到关键听众问题的人,我会发布什么回复

  • What I would post as an answer to anyone who is having trouble with key listeners.

答案;阅读关键绑定的Swing教程


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

嗯, Swing教程解释了


  • 键绑定不需要您单击组件(以使其聚焦):

    • 从用户的角度移除意外行为。

    • 如果您有2个对象,则它们无法同时移动,因为在给定时间内只有1个对象可以具有焦点(即使您将它们绑定到不同的键)。


    • 禁用,重新绑定,重新分配用户动作要容易得多。

    • 代码更容易阅读。

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

    教程 有一个很好的部分。键绑定涉及2个对象 InputMap ActionMap InputMap 将用户输入映射到操作名称, ActionMap 将操作名称映射到操作。当用户按下某个键时,将在输入映射中搜索该键并找到一个操作名称,然后在操作映射中搜索该操作名称并执行该操作。

    The tutorial has a good section about it. Key bindings involve 2 objects InputMap and ActionMap. InputMap maps a user input to an action name, ActionMap maps 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.


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

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

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


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

    否( 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);
    

    注意有3 InputMap s的反应到不同的焦点状态:

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

    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 。如果您在焦点窗口中有许多坦克并且您希望所有坦克同时接收输入,请使用此。

      • 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_COMPONENT is 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_WINDOW is 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.
      • 假设两个对象同时被控制,问题中显示的代码将如下所示:

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

        你可以看到分开动作映射中的输入映射允许可重用​​代码并更好地控制绑定。此外,如果需要该功能,还可以直接控制Action。例如:

        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:

        FireAction p1Fire = new FireAction(1);
        p1Fire.setEnabled(false); // Disable the action (for both players in this 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.


        我看到了很多使用了 KeyStroke ,这些是什么?它们是否像 KeyEvent

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

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

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

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

        这篇关于如何使用键绑定而不是键侦听器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆