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 个按钮的最小代码示例:
- 按键 e:文本字段收到事件(我看到控制台输出)但没有效果
- 按键^:文本字段接收事件(我看到控制台输出)但没有效果
- 按键退格:有效,最后一个字符被删除
- 键类型 e:有效,字母“e”出现
- Keytype ^:有效,单个“^”出现但不在“e”之上
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 事件中的字母,包括重音符号。
我正在实施自定义虚拟键盘(我的客户对此有特定要求)。基本上它由许多模拟键盘事件的按钮组成,用于将字符输入文本字段。
问题
我发现为角色模拟 keypress/keyrelease 事件完全没有效果——尽管我可以证明文本字段接收到这些事件(通过日志)。它只是不会导致字符出现在文本字段中。
但是:使用完全相同的机制模拟 Backspace/Tab/Arrows 效果很好。用键类型事件模拟字符事件也可以。
背景
那么为什么不改用键类型事件呢?因为角色死了!我需要支持带有重音符号的字母,比如 ê(e 上面有 ^)。真正的键盘让你通过先按 ^ 来键入这些字母,这是一个死键(一开始没有结果),然后按目标字母。
如果我为“^”发送一个键类型事件,然后为“e”发送另一个键类型,我得到“^e”。
代码示例
这是一个包含 5 个按钮的最小代码示例:
- 按键 e:文本字段收到事件(我看到控制台输出)但没有效果
- 按键^:文本字段接收事件(我看到控制台输出)但没有效果
- 按键退格:有效,最后一个字符被删除
- 键类型 e:有效,字母“e”出现
- Keytype ^:有效,单个“^”出现但不在“e”之上
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 事件中的字母,包括重音符号。