首页 文章

JavaFX模态阶段在MAC上表现奇怪

提问于
浏览
1

我有一个具有子窗口或阶段的JavaFX应用程序 . 我处理所有阶段如何相互交互的方式是这样的:我在我的Main类中有一个名为GuiContainer的静态类集合 . GuiContainer看起来像这样:

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;
    }
}

这样我就可以从任何地方访问任何舞台或控制器 .

所有可能的GuiContainers仅在引导时创建一次(在静态main(String [] args)中),然后可以由任何人从任何地方静态访问 .

现在......在主应用程序(主要阶段内的场景)中,我有一个具有自定义单元工厂的TreeView,当右键单击一个单元格时,会显示相应的上下文菜单 . 在这个上下文菜单中,我打开一个子/模态阶段,如下所示:

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();

现在,问题来了 . JavaFX不会请求关注子阶段 . 例如,我无法键入TextField(在子舞台上),因为我在主舞台上注册了一个onKeyPressed事件,它捕获了我按下的所有键 . 在这个子阶段我也有一个ComboBox,当我从该ComboBox中选择一个项目时,子阶段终于成为焦点,主阶段不再捕获我按下的键 .

我也尝试将模态改为所有可能的值,并阅读他们在oracle.com上做的事情,但没有任何信息帮助我......

我的代码有问题吗?或者这可能是JavaFX的问题?有任何想法吗?

EDIT: 这是我的应用程序覆盖启动方法:(我希望这提供更多信息)

@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: 这是我如何注册onKeyPressed侦听器:(在我的父控制器init()方法中)

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: 这就是我的抽象控制器类的样子 - 我希望这能提供更好的洞察力

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.

1 回答

  • 1

    你能发布你的舞台构造函数的代码吗?

    只是一个注释,来自doc

    请注意,显示模态阶段不一定会阻止调用者 . 无论阶段的形式如何,show()方法都会立即返回 . 如果需要阻止调用者直到隐藏(关闭)模态阶段,请使用showAndWait()方法 . 必须在阶段可见之前初始化模态 .


    EDIT - 我的错误,我没注意到createModal()方法 . 我最后在一些自定义阶段做的是:

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

    (java 8,显然,如果lambda不适用于您当前的语言级别(<8),则覆盖run()方法 .

    但我只针对非模态阶段这样做 . 模态阶段应该自动关注焦点 .

    根据Modality的文档,APPLICATION_MODAL应该按照您的预期执行(包括阻止其他窗口的事件):

    APPLICATION_MODAL定义模块窗口,阻止事件传递到任何其他应用程序窗口 .


    在调用initOwner之前,使用WINDOW_MODAL调用initModality . 请参阅:

    WINDOW_MODAL public static final模态WINDOW_MODAL定义一个模式窗口,用于阻止事件传递到其整个所有者窗口层次结构 . 注意:模式设置为WINDOW_MODAL但其所有者为null的阶段被视为其模态设置为NONE .

    如果requestFocus()(已经显示模式阶段 after ,并且 UI thread 右边?)没有关注焦点,我猜你的主窗口会自动将其取回 .

相关问题