eventsjavafxevent-handlingtextfieldkeyboard-events

JavaFX TextField how to prevent key from getting typed in keypressed event


I need to detect the numpad key presses to do some stuff. here is the code:

textField.setOnKeyPressed(new EventHandler<KeyEvent>() {
            @Override
            public void handle(KeyEvent event) {
                if(event.getCode().isKeypadKey()) {
                    //do stuff here...
                    event.consume();
                }
            }
        });

lets say user presses '2' on the numpad, although the event is consumed, the charachter '2' will appear in the textfield. but i dont want that. Any ideas how to prevent this?

whats more interesting is that if i throw a dummy exception instead of consuming the event, it will prevent the keytyped event.


Solution

  • The problem you are facing can be explained by adding a logging listener:

    textField.addEventFilter(Event.ANY, e -> System.out.println(e));
    

    So when you type a key you get this:

    KeyEvent [source = TextField@42924cd[styleClass=text-input text-field], target = TextField@42924cd[styleClass=text-input text-field], eventType = KEY_PRESSED, consumed = false, character = , text = 1, code = NUMPAD1]
    KeyEvent [source = TextField@42924cd[styleClass=text-input text-field], target = TextField@42924cd[styleClass=text-input text-field], eventType = KEY_TYPED, consumed = false, character = 1, text = , code = UNDEFINED]
    KeyEvent [source = TextField@42924cd[styleClass=text-input text-field], target = TextField@42924cd[styleClass=text-input text-field], eventType = KEY_RELEASED, consumed = false, character = , text = 1, code = NUMPAD1]
    

    In other words the KeyTyped event can't distinguish the keypad press (code=UNDEFINED), so you can't catch it.

    The reason why it works with an exception is the same as if you just consume the event without checking anything. You can consume it and prevent the typing. However, not with the keypad check.

    But then again, you shouldn't prevent it anyway. It should be up to the user as to what he wants to use his keypad or whatever input mechanism for.

    But if you must, you must. Given that information a workaround for this problem could be:

    public class Main extends Application {
    
        public static void main(String[] args) {
            Application.launch(args);
        }
    
        @Override
        public void start(Stage primaryStage) {
    
            HBox root = new HBox();
    
            EventHandler<KeyEvent> handler = new EventHandler<KeyEvent>() {
    
                private boolean willConsume = false;
    
                @Override
                public void handle(KeyEvent event) {
    
                    if (willConsume) {
                        event.consume();
                    }
    
                    if (event.getCode().isKeypadKey()) {
                        if (event.getEventType() == KeyEvent.KEY_PRESSED) {
                            willConsume = true;
                        } else if (event.getEventType() == KeyEvent.KEY_RELEASED) {
                            willConsume = false;
                        }
                    }
                }
    
            };
    
            TextField textField = new TextField();
            textField.addEventFilter(KeyEvent.ANY, handler);
    
            // logging
            textField.addEventFilter(KeyEvent.ANY, e -> System.out.println(e));
    
            root.getChildren().addAll(textField);
    
            Scene scene = new Scene(root, 300, 100);
    
            primaryStage.setScene(scene);
            primaryStage.show();
    
        }
    
    }
    

    That's the simple version. Actually you'd have to use an extended mechanism in order to deal with multiple keypresses.