Search code examples
javajavafxkeyboard-eventskeycode

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


I'm implementing a custom virtual keyboard (my customer had specific requirements for that). Basically its made of a lot of buttons simulating keyboard events to input characters into text fields.

The problem

I found that simulating keypress/keyrelease events for characters have absolutely no effect - even though I could prove that the text field receives those events (through logs). It just doesn't result in a character appearing in the text field.

BUT: Simulating a Backspace/Tab/Arrows with the exact same mechanism works fine. Simulating a character event with a keytype event also works.

Background

So why not use keytype events instead? Because of dead characters! I need to support letters with accent like ê (e with ^ on top). Real keyboards make you type those letters by first pressing ^ which is a dead key (no result at first) and then pressing the target letter.

If I send a keytype event for "^" and then another keytype for "e" I get "^e".

Code Sample

Here is a minimal code sample with 5 buttons:

  • Keypress e: Text field receives event (I see console output) but no effect
  • Keypress ^: Text field receives event (I see console output) but no effect
  • Keypress Backspace: Works, last character is deleted
  • Keytype e: works, letter "e" appears
  • Keytype ^: works, a single "^" appears but not on top of the "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);
    }

Solution

  • You will need to simulate the KEY_TYPED event as well, for keys that type a character. That's just how the framework works. It is possible to press and release keys without typing a character (for example if CTRL was already down), so they are separate events. You can also get multiple KEY_TYPED events if a key is held down.