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

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

问题描述

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

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;公共我的游戏(){//做所有的布局管理和什么不...JLabel obj1 = new JLabel();JLabel obj2 = new JLabel();obj1.addKeyListener(new MyKeyListener());obj2.addKeyListener(new MyKeyListener());添加(对象1);添加(对象2);//做其他 GUI 的事情...}静态无效移动(int方向,对象源){//做点什么}静态无效火(对象源){//做点什么}static void rebindKey(int newKey, String oldKey) {//取决于您的 GUI 实现.//通过 KeyListener 检测新密钥是这次要走的路.if (oldKey.equals("up"))up = newKey;如果(oldKey.equals(向下"))向下 = 新键;//...}公共静态无效主(字符串 [] args){新我的游戏();}私有静态类 MyKeyListener 扩展了 KeyAdapter {@覆盖public void keyPressed(KeyEvent e) {对象源 = e.getSource();int action = e.getExtendedKeyCode();/* 如果你想允许重新绑定键,这将不起作用,因为 case 变量必须是常量.开关(动作){案例:移动(1,源);案例正确:移动(2,源);案例下降:移动(3,源);案例左:移动(4,源);案例火灾:火(来源);...}*/如果(动作==向上)移动(1,源);否则如果(动作==正确)移动(2,源);否则如果(动作==向下)移动(3,源);否则如果(动作==左)移动(4,源);否则如果(动作==火)火(来源);}}}

我的响应速度有问题:

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

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

解决方案

此答案解释并演示了如何使用键绑定代替键侦听器进行教育.不是

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

  • 我会发布什么作为对关键听众有问题的人的回答.
<小时>

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

<块引用>

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

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

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

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

教程有一个很好的部分关于它.键绑定涉及两个对象InputMapActionMap.InputMap 将用户输入映射到动作名称,ActionMap 将动作名称映射到 Action.当用户按下一个键时,在输入映射中搜索该键并找到一个动作名称,然后在动作映射中搜索动作名称并执行该动作.

<块引用>

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

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

<块引用>

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

否(Swing 教程工作示例a>).

<块引用>

你很烂!我恨你!

这里是如何进行单键绑定:

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

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

public class MyGame extends JFrame {私有静态最终 int IFW = JComponent.WHEN_IN_FOCUSED_WINDOW;private static final String MOVE_UP = "向上移动";private static final String MOVE_DOWN = "下移";private static final String FIRE =移动火";静态 JLabel obj1 = 新 JLabel();静态 JLabel obj2 = 新 JLabel();公共我的游戏(){//做所有的布局管理和什么不...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));//实际上,您可能会创建自己的对象而不是 JLabel.//然后就可以创建一个方便的方法 obj.inputMapPut(String ks, String a)//相当于 obj.getInputMap(IFW).put(KeyStroke.getKeyStroke(ks), a);//和动作地图类似的东西.添加(对象1);添加(对象2);//做其他 GUI 的事情...}静态无效rebindKey(KeyEvent ke,字符串oldKey){//取决于您的 GUI 实现.//通过 KeyListener 检测新密钥是这次要走的路.obj1.getInputMap(IFW).remove(KeyStroke.getKeyStroke(oldKey));//删除也可以通过指定动作名称none"来完成.obj1.getInputMap(IFW).put(KeyStroke.getKeyStrokeForEvent(ke),obj1.getInputMap(IFW).get(KeyStroke.getKeyStroke(oldKey)));//如果您想要操作的辅助键,您可以删除删除操作.}公共静态无效主(字符串 [] args){新我的游戏();}私有类 MoveAction 扩展 AbstractAction {内部方向;国际球员;MoveAction(int 方向,int 玩家){this.direction = 方向;this.player = 玩家;}@覆盖public void actionPerformed(ActionEvent e) {//与问题代码中的move方法相同.//玩家可以被 e.getSource() 检测到并调用它自己的移动方法.}}私有类 FireAction 扩展了 AbstractAction {国际球员;FireAction(int 玩家) {this.player = 玩家;}@覆盖public void actionPerformed(ActionEvent e) {//与问题代码中的fire方法相同.//玩家可以通过 e.getSource() 检测到,并调用自己的 fire 方法.//如果是,则移除构造函数.}}}

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

FireAction p1Fire = new FireAction(1);p1Fire.setEnabled(false);//禁用动作(在这种情况下对两个玩家).

有关详细信息,请参阅操作教程.

<块引用>

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

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

<块引用>

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

是的,它们具有相似的功能,但更适合在这里使用.请参阅他们的 API 了解信息以及如何创建它们.><小时>

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

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

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?

解决方案

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.

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.

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 tutorial explains that

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

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

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.

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 tutorial has working examples).

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:

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

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

See the Action tutorial for 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)?

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?

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


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

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

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