JavaFX:按键事件适用于 Backspace/Tab/Arrows... 但不适用于字符键码

JavaFX: Keypress-Event works for Backspace/Tab/Arrows... but not for character key-codes

我正在实施自定义虚拟键盘​​(我的客户对此有特定要求)。基本上它由许多模拟键盘事件的按钮组成,用于将字符输入文本字段。

问题

我发现为角色模拟 keypress/keyrelease 事件完全没有效果——尽管我可以证明文本字段接收到这些事件(通过日志)。它只是不会导致字符出现在文本字段中。

但是:使用完全相同的机制模拟 Backspace/Tab/Arrows 效果很好。用键类型事件模拟字符事件也可以。

背景

那么为什么不改用键类型事件呢?因为角色死了!我需要支持带有重音符号的字母,比如 ê(e 上面有 ^)。真正的键盘让你通过先按 ^ 来键入这些字母,这是一个死键(一开始没有结果),然后按目标字母。

如果我为“^”发送一个键类型事件,然后为“e”发送另一个键类型,我得到“^e”。

代码示例

这是一个包含 5 个按钮的最小代码示例:

public class TestApplication extends Application {

    public static void main(String[] args) {
        Application.launch(TestApplication.class, args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        TextField textField = new TextField();
        textField.setOnKeyPressed(e -> {
            log.info("Keypress Event: " + e.getCode());
        });
        textField.setOnKeyReleased(e -> {
            log.info("KeyRelease Event: " + e.getCode());
        });
        textField.setOnKeyTyped(e -> {
            log.info("KeyType Event: " + e.getCharacter());
        });

        // Doesn't work
        Button keypressLetterButton = new Button();
        keypressLetterButton.setText("e (keypress)");
        keypressLetterButton.setOnMouseClicked(e -> {
            pressKeyCode(KeyCode.E, textField);
        });

        Button keypressCircumflexButton = new Button();
        keypressCircumflexButton.setText("^ (keypress)");
        keypressCircumflexButton.setOnMouseClicked(e -> {
            pressKeyCode(KeyCode.DEAD_CIRCUMFLEX, textField);
        });

        // Works
        Button keypressBackspaceButton = new Button();
        keypressBackspaceButton.setText("Backspace");
        keypressBackspaceButton.setOnMouseClicked(e -> {
            pressKeyCode(KeyCode.BACK_SPACE, textField);
        });

        Button keytypeLetterButton = new Button();
        keytypeLetterButton.setText("e (keytype)");
        keytypeLetterButton.setOnMouseClicked(e -> {
            typeKey("e", textField);
        });

        // Works, but not as intended
        Button keytypeCircumflexButton = new Button();
        keytypeCircumflexButton.setText("^ (keytype)");
        keytypeCircumflexButton.setOnMouseClicked(e -> {
            typeKey("^", textField);
        });

        // Just to prevent stealing focus
        keypressLetterButton.setFocusTraversable(false);
        keypressCircumflexButton.setFocusTraversable(false);
        keypressBackspaceButton.setFocusTraversable(false);
        keytypeLetterButton.setFocusTraversable(false);
        keytypeCircumflexButton.setFocusTraversable(false);

        HBox hBox = new HBox(keypressLetterButton, keypressCircumflexButton, keypressBackspaceButton,
                keytypeLetterButton, keytypeCircumflexButton, textField);

        primaryStage.setScene(new Scene(hBox));
        primaryStage.show();
    }

    public void pressKeyCode(KeyCode keyCode, Node target) {
        KeyEvent pressEvent = new KeyEvent(KeyEvent.KEY_PRESSED, null, keyCode.getName(), keyCode, false, false, false,
                false);
        KeyEvent releaseEvent = new KeyEvent(KeyEvent.KEY_RELEASED, null, keyCode.getName(), keyCode, false, false,
                false, false);
        target.fireEvent(pressEvent);
        target.fireEvent(releaseEvent);
    }

    public void typeKey(String key, Node target) {
        KeyEvent event = new KeyEvent(KeyEvent.KEY_TYPED, key, null, null, false, false, false, false);
        target.fireEvent(event);
    }

对于键入字符的键,您还需要模拟 KEY_TYPED 事件。这就是框架的工作原理。可以在不键入字符的情况下按下和释放键(例如,如果 CTRL 已经按下),因此它们是单独的事件。 如果按住某个键,您还可以获得多个 KEY_TYPED 事件。

除了 swpalmer 的回答之外,我还想分享一个解决方法,一方面可以满足我的要求,但另一方面(错误)使用旧的 Java AWT class。重点是证明了KEY_TYPED事件的必要性

    private java.awt.Robot robot;

    public TestApplication() {
        try {
            robot = new Robot();
        } catch (AWTException e) {
            e.printStackTrace();
        }
    }

    public void pressKeyCode(KeyCode keyCode, Node target) {
        robot.keyPress(keyCode.impl_getCode()); // Deprecated
        robot.keyRelease(keyCode.impl_getCode()); // Deprecated
    }

按 Dead Circumflex 然后按 e 会生成此日志:

10:15:26.725 [JavaFX Application Thread] INFO test.TestApplication - Keypress Event: DEAD_CIRCUMFLEX
10:15:31.091 [JavaFX Application Thread] INFO test.TestApplication - Keypress Event: E
10:15:31.091 [JavaFX Application Thread] INFO test.TestApplication - KeyType Event: ê
10:15:31.092 [JavaFX Application Thread] INFO test.TestApplication - KeyRelease Event: E

因此机器人 class 在内部为 ê 触发了一个 KEY_TYPED 事件。

但是:

  • 机器人 class 很旧,主要用于测试自动化。
  • 它使用整数而不是 KeyCode
  • 并且 keyCode.impl_getCode() 方法在 Java 8 中已弃用,并且可能不再存在。所以将来我必须手动将 KeyCode 转换为整数。

所以我的最终解决方案是我自己管理 KEY_TYPED 事件中的字母,包括重音符号。