purring pigeon
purring pigeon

Reputation: 4209

KeyEvent on Dialog not consumed when closing in JavaFX

I have a requirement to navigate a table when the user hits the ENTER key. For this I have created an event filter similar to:

private EventHandler<KeyEvent> keyReleasedFilter = event -> {
    if ((event.getCode() == KeyCode.ENTER || event.getCode() == KeyCode.TAB)) {
        previousPosition = table.getFocusModel().getFocusedCell();
        //do my navigation
    }
}

I have run into an issue where JavaFX Modal Dialogs used during navigation of the table to indicate an error, cause issues with this filter. If the user closed the dialog with the ENTER key, that event is trapped by my event filter on the parent stage. I am not sure how to prevent that. It is causing inconsistent navigation.

Here is a simple application that demonstrates the behavior:

public void start(Stage primaryStage) {
    final Alert d = new Alert(Alert.AlertType.ERROR);
    d.initModality(Modality.WINDOW_MODAL);

    Button btn = new Button();
    btn.setText("Say 'Hello World'");
    btn.addEventFilter(KeyEvent.KEY_RELEASED, event -> {
        if ((event.getCode() == KeyCode.ENTER || event.getCode() == KeyCode.TAB)) {
            d.showAndWait();
        }
    });

    Scene scene = new Scene(new StackPane(btn), 300, 250);

    primaryStage.setTitle("Hello World!");
    primaryStage.setScene(scene);
    primaryStage.show();
}

I have noticed that the dialog is closed with the KEY_PRESSED event, and will not capture the KEY_RELEASED event.

I have tried adding an EVENT FILTER to the dialog (Button/DialogPane/Scene and even Stage) - none intercept the KEY_RELEASED event.

Thanks.

Upvotes: 1

Views: 760

Answers (2)

OttPrime
OttPrime

Reputation: 1938

Unless there is another reason to use the KEY_RELEASED event, then I would recommend switching to triggering on KEY_PRESSED for navigation as well and switch your EventFilter to an EventHandler. The example below will allow you to toggle the Alert on and off. When it's on, you'll notice that the button text doesn't change. When it's off, the button text will change. Take a look at how JavaFX constructs Event chains here if you haven't already.

boolean error = false;
int i = 0;

@Override
public void start(Stage primaryStage)
{
    final Alert d = new Alert(Alert.AlertType.ERROR);
    d.initModality(Modality.WINDOW_MODAL);

    Button err = new Button();
    err.setText("Error off");
    err.addEventHandler(ActionEvent.ACTION, t ->
    {
        error = !error;
        if (error)
            err.setText("Error on");
        else
            err.setText("Error off");
    });

    Button btn = new Button();
    btn.setText("Say 'Hello World'");

    btn.addEventHandler(KeyEvent.KEY_PRESSED, event ->
    {
        if ((event.getCode() == KeyCode.ENTER || event.getCode() == KeyCode.TAB)) {
            if (error)
            {
                d.showAndWait();
            }
            else
            {
                i++;
                btn.setText(String.valueOf(i));
            }
        }
    });


    Scene scene = new Scene(new VBox(btn, err), 300, 250);

    primaryStage.setTitle("Hello World!");
    primaryStage.setScene(scene);
    primaryStage.show();
}

Upvotes: 1

purring pigeon
purring pigeon

Reputation: 4209

So I have a hack that works - I really don't like it, so I am hoping that there is a better solution (a clean one) to solve this issue....

Basically, right before I open my dialog, I set a boolean so I know it's open. Then in my event filter, kick out if that is set to true.

public class EventTester extends Application{

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

private boolean modalWasShowing = false;
@Override
public void start(Stage primaryStage) {
    final Alert d = new Alert(Alert.AlertType.ERROR);
    d.initModality(Modality.WINDOW_MODAL);

    Button btn = new Button();
    btn.setText("Say 'Hello World'");
    btn.addEventFilter(KeyEvent.KEY_RELEASED, event -> {
        if(modalWasShowing){
            modalWasShowing=false;
            return;
        }
        if ((event.getCode() == KeyCode.ENTER || event.getCode() == KeyCode.TAB)) {
            modalWasShowing = true;
            d.showAndWait();
        }
    });

    Scene scene = new Scene(new StackPane(btn), 300, 250);

    primaryStage.setTitle("Hello World!");
    primaryStage.setScene(scene);
    primaryStage.show();
}

Please let me know if you know of a better way to handle this.

Upvotes: 0

Related Questions