erikvimz
erikvimz

Reputation: 5476

JavaFX modal stage behaving strangely on MAC

I have a JavaFX application that has child windows or stages. The way I handle how all stages interact with each other is like this: I have a static collection of classes called GuiContainer in my Main class. The GuiContainer looks like this:

public class GuiContainer {
    private final Stage stage;
    private final Scene scene;
    private final FXMLLoader loader;
    private final Controller controller;
    private final Parent parent;

    public GuiContainer(Stage stage, Scene scene, FXMLLoader loader, Controller controller, Parent parent) {
        this.stage = stage;
        this.scene = scene;
        this.loader = loader;
        this.controller = controller;
        this.parent = parent;
    }

    public Stage getStage() {
        return stage;
    }

    public Scene getScene() {
        return scene;
    }

    public FXMLLoader getLoader() {
        return loader;
    }

    public Controller getController() {
        return controller;
    }

    public Parent getParent() {
        return parent;
    }

    public static final GuiContainer createMain(Stage stage, String title, int width, int height, String pathToView) throws Exception {
        FXMLLoader loaderInner = new FXMLLoader(GuiContainer.class.getResource(pathToView));

        Parent parentInner = loaderInner.load();
        final Controller controllerInner = loaderInner.getController();

        Scene sceneInner = new Scene(parentInner, width, height);

        stage.setTitle(title);
        stage.setScene(sceneInner);

        GuiContainer guiContainer = new GuiContainer(
                stage, sceneInner, loaderInner, controllerInner, parentInner
        );

        controllerInner.start(guiContainer);

        return guiContainer;
    }

    public static final GuiContainer createModal(Window owner, String title, int width, int height, String pathToView) throws Exception {
        if (owner == null) {
            Log.error(GuiContainer.class.getSimpleName(), "Unable to create instance, missing window owner!");
            return null;
        }

        Stage stageInner = new Stage();

        FXMLLoader loaderInner = new FXMLLoader(GuiContainer.class.getResource(pathToView));

        Parent parentInner = loaderInner.load();
        Controller controllerInner = loaderInner.getController();

        Scene sceneInner = new Scene(parentInner, width, height);

        stageInner.setTitle(title);
        stageInner.setScene(sceneInner);

        stageInner.initStyle(StageStyle.DECORATED);
        stageInner.initModality(Modality.WINDOW_MODAL);
        stageInner.initOwner(owner);

        GuiContainer guiContainer = new GuiContainer(
                stageInner, sceneInner, loaderInner, controllerInner, parentInner
        );

        controllerInner.start(guiContainer);

        return guiContainer;
    }
}

This way I have access to any stage or controller from anywhere.

All possible GuiContainers are created only once at boot (in static main(String[] args)) and can then be statically accessed from anywhere, by anyone.

Now... in the main application (scene inside the primary stage) I have a TreeView that has a custom cell factory, and when a cell is right clicked an appropriate context menu is shown. Inside this context menu I open a child/modal stage, like so:

String containerName = "guiName";
GuiContainer container = Main.getGuiContainers().getOrDefault(containerName, null);
if(container == null) {
    Log.error(getClass().getSimpleName(), "Unable to find GuiContainer: " + containerName);
    return;
}

container.getStage().showAndWait();

Now, here comes the problem. JavaFX doesn't request focus on the child stage. For instance, I can't type into a TextField (on the child stage) because I have an onKeyPressed event registered on the primary stage and it captures all the keys I press. On this child stage I also have a ComboBox, and when I select an item from that ComboBox, the child stage finally comes into focus and the primary stage no longer captures the keys I press.

I also tried to change the modality to all posible values and read what they do on oracle.com, but none of the information helped me...

Is there something wrong with my code? Or could this be an issue with JavaFX? Any ideas?

EDIT: Here is my Application overriden start method: (I hope this provides more information)

@Override
public void start(Stage stage) throws Exception {
    GuiContainer guiWindow1 = GuiContainer.createMain(stage, "Window1", 900, 460,
            "/com/project/app/gui/views/Window1.fxml");

    GuiContainer guiWindow2 = GuiContainer.createModal(stage, "Window2", 320, 240,
            "/com/project/app/gui/views/Window2.fxml");

    GuiContainer guiWindow3 = GuiContainer.createModal(stage, "Window3", 320, 240,
            "/com/project/app/gui/views/Window3.fxml");

    GuiContainer guiWindow4 = GuiContainer.createModal(stage, "Window4", 420, 360,
            "/com/project/app/gui/views/Window4.fxml");

    GuiContainer guiWindow5 = GuiContainer.createModal(stage, "Window5", 380, 460,
            "/com/project/app/gui/views/Window5.fxml");

    guiWindow5.getStage().setResizable(false);

    guiContainers.put("guiWindow1", guiWindow1);
    guiContainers.put("guiWindow2", guiWindow2);
    guiContainers.put("guiWindow3", guiWindow3);
    guiContainers.put("guiWindow4", guiWindow4);
    guiContainers.put("guiWindow5", guiWindow5);

    guiWindow1.getStage().show();
}

EDIT2: This is how I register the onKeyPressed listener: (in my parent controller init() method)

getGuiContainer().getScene().setOnKeyPressed((e) -> {
    Log.info(getClass().getSimpleName(), "Key pressed: " + e.getCode().getName());

    Macro macro = Main.getProject().getSelectedMacro();
    if(macro == null) {
        Log.error(getClass().getSimpleName(), "Unable to find selected macro");
        return;
    }

    if (e.getCode() == KeyCode.ESCAPE) {
        macro.getNodePlacement().reset();
        macro.cancelSelect();
        macro.repaint();
    } else if(e.getCode() == KeyCode.DELETE) {
        macro.deleteSelectedNodes();
    }
});

EDIT3: This is how my abstract controller class looks like - I hope this provides a better insight

public abstract class Controller {

    private GuiContainer guiContainer;

    public final GuiContainer getGuiContainer() {
        return guiContainer;
    }

    public final void start(GuiContainer guiContainer) {
        this.guiContainer = guiContainer;

        init();
    }

    public abstract void init(); // this is where the onKeyPressed is registered, when extending the abstract class

    public abstract void reset();

    public abstract void refresh();

}

IMPORTANT EDIT: This only happens on the MAC platform, I ran the same application on windows and there were no such problems.

Upvotes: 1

Views: 880

Answers (1)

VinceOPS
VinceOPS

Reputation: 2720

could you please post the code of the constructor of your stage?

Just a note, from the doc:

Note that showing a modal stage does not necessarily block the caller. The show() method returns immediately regardless of the modality of the stage. Use the showAndWait() method if you need to block the caller until the modal stage is hidden (closed). The modality must be initialized before the stage is made visible.


EDIT - My mistake, I didn't notice the createModal() method. What I ended up doing in some of my custom stages is:

    Platform.runLater(() -> somecontrol.requestFocus());

(java 8, obviously, override run() method if lambda is not available with your current language level (< 8)).

But I only do this for non-modal stage. Modal stage should take the focus automatically.

According to the doc of Modality, APPLICATION_MODAL is supposed to do what you expect (including Blocking the events for other windows):

APPLICATION_MODAL Defines a modal window that blocks events from being delivered to any other application window.


You call initModality with WINDOW_MODAL before calling initOwner. Please see:

WINDOW_MODAL public static final Modality WINDOW_MODAL Defines a modal window that block events from being delivered to its entire owner window hierarchy. Note: A Stage with modality set to WINDOW_MODAL, but its owner is null, is treated as if its modality is set to NONE.

If requestFocus() (called after the modal stage has been displayed, and in the UI thread right?) doesn't take the focus, I guess your main window is automatically taking it back.

Upvotes: 1

Related Questions