JavaFX 模态阶段在 MAC 上表现异常

标签 java javafx javafx-8

我有一个具有子窗口或阶段的 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;
    }
}

这样我就可以从任何地方访问任何阶段或 Controller 。

所有可能的 GuiContainers 仅在启动时创建一次(在 static 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 事件,并且它捕获了我按下的所有键。在这个子阶段上,我还有一个组合框,当我从该组合框中选择一个项目时,子阶段最终成为焦点,并且主阶段不再捕获我按下的键。

我还尝试将模式更改为所有可能的值并阅读他们在 oracle.com 上所做的操作,但没有任何信息对我有帮助......

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

编辑:这是我的应用程序重写启动方法:(我希望这提供更多信息)

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

编辑2:这是我注册 onKeyPressed 监听器的方式:(在我的父 Controller 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:这就是我的抽象 Controller 类的样子 - 我希望这可以提供更好的见解

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

}

重要编辑:这只发生在 MAC 平台上,我在 Windows 上运行相同的应用程序,没有出现此类问题。

最佳答案

您能发布一下您的舞台构造函数的代码吗?

请注意,来自 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.

<小时/>

编辑 - 我的错误,我没有注意到 createModal() 方法。 我最终在一些自定义阶段中所做的是:

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

(java 8,显然,如果 lambda 在您当前的语言级别 (< 8) 中不可用,请重写 run() 方法。

但我只在非模态阶段这样做。模态阶段应该自动获取焦点。

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

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

<小时/>

在调用 initOwner 之前,您可以使用 WINDOW_MODAL 调用 initModality。请参阅:

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.

如果requestFocus()(在模态阶段显示之后调用,并且在UI线程中调用,对吧?)没有获得焦点,我猜你的主窗口会自动将其收回。

关于JavaFX 模态阶段在 MAC 上表现异常,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27553484/

相关文章:

binding - JavaFx : binding of a ObjectProperty<Integer> with a ObjectProperty<Unit>

java - 支持实现接口(interface)的泛型类的静态函数的最佳设计模式

java - 通用子类型和通配符在 Collections.copy 中如何工作?

java - 有没有办法在 JavaFX 的同一文本行中使用两种不同的字体大小?

JavaFX:使用 StringProperty 和 ChangeListener 根据 TimerTask 定期更新标签

JavaFX 8 间距在 Windows 上无法缩放

java - 如何在Android的Qt上使用HockeyApp SDK

java - 为什么在 java 6 和 java 7 中获取字符 ®(U+00AE) 不同?

java - 显示 Unity WebPlayer 内容

java - OSX 系统菜单栏在 JavaFX 中不工作