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

2023-12-15

我在用着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;

    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 编写游戏。
  • 良好的代码编写应该是什么样的(例如可见性)。
  • 实现键绑定的最有效(性能或代码方面)的方法。

It is

  • 我要发布的内容作为对那些与关键听众有困难的人的答案.

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

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

嗯,摇摆教程解释说

  • Key bindings don't require you to click the component (to give it focus):
    • 从用户的角度消除意外行为。
    • 如果您有 2 个对象,它们无法同时移动,因为在给定时间只有 1 个对象可以获得焦点(即使您将它们绑定到不同的键)。
  • Key bindings are easier to maintain and manipulate:
    • 禁用、重新绑定、重新分配用户操作要容易得多。
    • 代码更容易阅读。

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

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

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

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

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

No (the 摇摆教程 has 工作示例).

You suck! I hate you!

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

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 {

    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。例如:

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

See the 动作教程了解更多信息。

我发现您对 4 个键(方向)使用了 1 个动作(移动),对 1 个键使用了 1 个动作(开火)。为什么不给每个键自己的操作,或者给所有键相同的操作并弄清楚在操作中要做什么(就像在移动情况下一样)?

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

我看到很多KeyStroke用过的,那些是什么?他们是否像一个KeyEvent?

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


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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何使用按键绑定而不是按键侦听器 的相关文章

随机推荐

  • AndroidManifest.xml 中的包“com.class.xxx.test”不是有效的 Java 包名称,因为“class”是 Java 关键字

    我是 Android 中浓缩咖啡测试的新手 当我尝试构建测试 apk 时 出现此错误 Package com class xxx test from AndroidManifest xml is not a valid Java packa
  • 获取给定月份中每天“created_at”的元素计数

    我想制作一个简单的图表 显示过去一个月在我的应用程序中创建的用户 基本上就像过去一个月的每一天一样 我想显示当天注册的用户数量 到目前为止我所拥有的 Controller users User count order gt DATE cre
  • 从终端读取 ANSI 转义

    维基百科文章在终端上 ANSI 转义代码显示一些可以发送到终端的代码AND然后一些数据返回给应用程序 请提供一个示例 说明如何发送代码 然后在 Node js 应用程序中读取结果 例如这个转义序列 CSI 6n DSR 设备状态报告 将光标
  • 如何设置 cron 每隔一个半小时运行某些命令?

    如何设置 cron 每隔一个半小时运行某些命令 这在正常情况下用单个表达式是不可能的cron 在不修改代码的情况下 您可以做的最好的事情是 0 0 3 6 9 12 15 18 21 cmd 30 1 4 7 10 13 16 19 22
  • 当之前使用 Get 检索过实体时,无法合并具有复合 ID 的实体

    我正在进行的项目要求我们系统中的数据与另一个系统的数据同步 另一个系统非常流行 这就是同步如此重要的原因 但是 当我尝试更新具有复合 ID 的现有实体时 我遇到了一个奇怪的问题 问题是 每当检索要更新的实体时 使用Get 调用之前Merge
  • Mercurial 初学者:权威实用指南

    Locked 这个问题及其答案是locked因为这个问题是题外话 但却具有历史意义 目前不接受新的答案或互动 灵感来自Git 初学者 权威的实用指南 这是有关使用 Mercurial 的信息汇编初学者 for 实际的 use 初学者 接触过
  • iOS Firebase 云消息通知在调试/测试飞行中不起作用,而不是发布

    我的通知在 Android 中正常工作 没有任何问题 但在 iOS 中我无法弄清楚问题出在哪里 我已经创建了 APN 文件并上传到 Firebase iOS 配置中 团队 ID 和应用程序 ID 均正确 仔细检查 推送通知 在 Apple
  • mysql_real_escape_string 与 Zend

    我正在使用 zend 框架开发一个 Web 应用程序 对于选择语句我使用了以下方式 Ex public function getData name sql SELECT from customer where Customer Name n
  • Microsoft AZURE blob 触发功能间歇性工作

    我们的 Blob 触发函数遇到了问题 该函数是用 JavaScript 编写的 我们很难为其制定自动化部署流程 以下是我们遵循的步骤 使用 ARM 模板和参数文件在现有资源组中创建函数应用New AzureRmResourceGroupDe
  • URL SQL 中的子字符串域名

    我有一组数据 www google com sg www yahoo com marketwatch bing com bbc co uk 一些数据有www 有些则不然 有些有 com com sg com ul 有些则不然 如何仅提取名称
  • 如何使用node js创建pdf/a-1b文件?

    我想创建 pdf a 1b 文件 我已经用谷歌搜索但无法用nodejs找到它 有人知道我们如何创建 pdf a 1b 文件吗 None
  • 从非标准事件创建 Observable(无 EventArgs / EventHandler)

    我想为定义如下的事件创建一个 Observable public event Func
  • Android USB 权限对话框永远不会出现

    我编写了一个简单的应用程序 用于将命令发送到通过 USB 连接到 Android 4 0 平板电脑的 USB 打印机 由于某种原因 我无法获得权限来声明接口并打开连接 这是相关代码 public class TestPrintActivit
  • 可翻译学说实体的 Symfony 形式

    我有一个已使用翻译的教义实体可翻译学说的扩展
  • 光标下有下划线

    我已经实现了自定义编辑文本 具有自定义样式
  • C++异步编程,如何不等待未来?

    我正在尝试学习 C 异步编程 在Python中 我们有await 我们可以用它从该点恢复函数 但在 C 中future等待结果并停止下一行代码 如果我们不想得到结果 而是继续执行下一行代码怎么办 我怎样才能做到这一点 您可以使用std fu
  • 在 R 中的 knit 文档中插入 HTML 表格

    我有许多不同的 HTML 文件 其中包含格式化的表格 我想将这些表格合并到 R 中的 knit 报告中 不幸的是 我在将 HTML 文件加载到 R 中并将表格包含在我的knitr 报告中时遇到了一些问题 HTML 文件是使用 MS Exce
  • 对于有效证书,X509Certificate2.Verify() 方法始终返回 false

    我正在使用智能卡进行身份验证 SecurityTokenService 身份验证服务 仅托管在我的计算机上 智能卡具有有效的证书 并且其根证书也安装在我的计算机上的本地计算机存储中 当我使用X509Certificate2 Verify在我
  • 如何在c代码中使用tcl api

    我想在另一个 c 代码文件中使用我的 tcl 代码的一些功能 API 但我不知道如何做到这一点 特别是如何链接它们 为此 我采用了一个非常简单的 tcl 代码 其中包含一个 API 该 API 将两个数字相加并打印总和 谁能告诉我如何调用这
  • 如何使用按键绑定而不是按键侦听器

    我在用着KeyListener在我的代码 游戏或其他 中作为我的屏幕对象对用户按键输入做出反应的方式 这是我的代码 public class MyGame extends JFrame static int up KeyEvent VK U