clartaq
clartaq

Reputation: 5372

JavaFX KeyEvent.consume() Fails to Stop Event Propagation

I'm trying to reproduce some behavior of a JavaScript game in JavaFX. The program has several numeric text fields that can be edited as the game is running. It also accepts alphabetic keyboard commands while running.

In my version, I try to capture, react to, and remove keyboard commands even when one of the numeric text fields is focused. I thought using an EventFilter should work -- consuming the keyboard events before they reach the TextFields during the capture phase of handling the event. But so far I have not been able to stop the alphabetic keyboard input from reaching the text field.

Here is an SSCCE that illustrates what I have tried and what is not working.

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class KeyFilterSSCCE extends Application {

    @Override
    public void start(Stage stage) {
        stage.setTitle("Key Filter SSCCE");
        TextField tf = new TextField("XYZ abc");
        tf.setMaxWidth(150);
        Label label = new Label("Filtered Keys: ");
        VBox root = new VBox(20, tf, label);
        root.setAlignment(Pos.CENTER);
        root.setPadding(new Insets(20));
        Scene scene = new Scene(root, 325, 200);

        stage.addEventFilter(KeyEvent.KEY_PRESSED, e -> {
            if (e.getCode() == KeyCode.S) {
                label.setText(label.getText() + e.getCode().getChar());
                e.consume();
            }
        });

        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch();
    }
}

The program should let you type into the text field and capture and remove 'S' characters before they are input into the field. The code shows that the 'S' characters are detected correctly, but they still show up in the text field, even after being consume()ed.

After the call to consume(), the isConsumed() method confirms that the event is correctly marked as consumed, but the dispatch chain is not short-circuited as the documentation indicates.

I've tried adding the event filter to other Nodes in the path down to the text field with no change in behavior. I've tried filtering KeyEvent.ANY, KeyEvent.KEY_TYPED, and KeyEvent.KEY_RELEASED without and change in behavior. I've seen this similar question with an answer that proposed something like what I am trying.

Am I misunderstanding how this should work?

Since all of the TextFields in the real program have attached TextFormatters, I suppose I could add a filter function to the formatter to reject the keyboard command characters. That seems like a roundabout way of doing it though.

macOS, Java 17, JavaFX 17 if that makes any difference.

Upvotes: 4

Views: 684

Answers (1)

clartaq
clartaq

Reputation: 5372

After @kleopatras comment and a little more experimentation, here is a version of the event filter that operates as expected:

    stage.addEventFilter(KeyEvent.KEY_TYPED, e -> {
        if (e.getCharacter().toUpperCase().equals("S")) {
            label.setText(label.getText() + e.getCharacter());
            e.consume();
        }
    });

Filtering for the KeyEvent.KEY_TYPED event was what I needed. In addition, since the event getCode() method for that event returns UNDEFINED, the test had to be modified a bit to test against the result for getCharacter(), which, oddly, returns a String.

Upvotes: 2

Related Questions