JavaFX:如何在初始化期间从控制器获取阶段?

Posted

技术标签:

【中文标题】JavaFX:如何在初始化期间从控制器获取阶段?【英文标题】:JavaFX: How to get stage from controller during initialization? 【发布时间】:2012-10-26 02:54:44 【问题描述】:

我想从我的控制器类中处理舞台事件(即隐藏)。所以我所要做的就是通过添加一个监听器

((Stage)myPane.getScene().getWindow()).setOn*whatIwant*(...);

但问题是初始化在之后开始

Parent root = FXMLLoader.load(getClass().getResource("MyGui.fxml"));

之前

Scene scene = new Scene(root);
stage.setScene(scene);

因此 .getScene() 返回 null。

我自己找到的唯一解决方法是向 myPane.sceneProperty() 添加一个侦听器,当它变为非空时,我得到场景,添加到它的 .windowProperty() 我的!该死的!我最终检索到的监听器处理阶段。这一切都以设置所需的侦听器为舞台事件而告终。 我觉得听众太多了。 这是解决我问题的唯一方法吗?

【问题讨论】:

【参考方案1】:

你可以通过getController()初始化后从FXMLLoader获取控制器的实例,但是你需要实例化一个FXMLLoader而不是使用静态方法。

我会在之后直接调用load() 到控制器后通过阶段:

FXMLLoader loader = new FXMLLoader(getClass().getResource("MyGui.fxml"));
Parent root = (Parent)loader.load();
MyController controller = (MyController)loader.getController();
controller.setStageAndSetupListeners(stage); // or what you want to do

【讨论】:

认为您缺少演员表?父根 = (Parent)loader.load(); 这真的是最好的方法吗? JavaFX 框架没有提供任何东西来存档? 由于某种原因,如果您使用不带参数的构造函数并将 URL 提交给 load() 方法,这将不起作用。 (另外,getController 上的 javadoc 听起来应该在 setController 上。) @Bombe 这是因为带有 URL 参数的 load() 方法是一个静态方法,不会在您调用它的实例上设置任何内容。 controller.setStageAndSetupListeners(stage); 没有这种方法。我该怎么办?【参考方案2】:

在控制器中获取舞台对象的最简单方法是:

    在自己创建的控制器类中添加一个额外的方法,例如(在控制器类中设置舞台的设置方法),

    private Stage myStage;
    public void setStage(Stage stage) 
         myStage = stage;
    
    

    在启动方法中获取控制器并设置阶段

    FXMLLoader loader = new FXMLLoader(getClass().getResource("MyFXML.fxml"));
    OwnController controller = loader.getController();
    controller.setStage(this.stage);
    

    现在您可以在控制器中访问舞台

【讨论】:

这是我的赢家。罗伯特马丁的回答起初有效,但后来开始抛出一个错误,我通过像这样得到stage 解决了这个错误。【参考方案3】:

我知道这不是您想要的答案,但 IMO 提出的解决方案并不好(而您自己的方式是)。 为什么? 因为它们取决于应用程序状态。在 JavaFX 中,控件、场景和舞台不相互依赖。这意味着控件可以在不添加到场景的情况下存在,并且场景可以在不附加到舞台的情况下存在。然后,在时刻 t1,控制可以附加到场景,在时刻 t2,可以将该场景添加到舞台(这解释了为什么它们是彼此可观察的属性)。

因此,建议获取控制器引用并调用方法,将阶段传递给它的方法会为您的应用程序添加一个状态。这意味着您需要在创建阶段之后的正确时刻调用该方法。换句话说,你现在需要遵循一个命令: 1- 创建舞台 2- 通过方法将此创建的阶段传递给控制器​​。

您不能(或不应该)在这种方法中更改此顺序。所以你失去了无国籍状态。在软件中,一般来说,状态是邪恶的。理想情况下,方法不应要求任何调用顺序。

那么正确的解决方案是什么? 有两种选择:

1-您的方法,在控制器侦听属性中获取阶段。我认为这是正确的做法。像这样:

pane.sceneProperty().addListener((observableScene, oldScene, newScene) -> 
    if (oldScene == null && newScene != null) 
        // scene is set for the first time. Now its the time to listen stage changes.
        newScene.windowProperty().addListener((observableWindow, oldWindow, newWindow) -> 
            if (oldWindow == null && newWindow != null) 
                // stage is set. now is the right time to do whatever we need to the stage in the controller.
                ((Stage) newWindow).maximizedProperty().addListener((a, b, c) -> 
                    if (c) 
                        System.out.println("I am maximized!");
                    
                );
            
        );
    
);

2- 你在创建Stage 的地方做你需要做的事(这不是你想要的):

Stage stage = new Stage();
stage.maximizedProperty().addListener((a, b, c) -> 
            if (c) 
                System.out.println("I am maximized!");
            
        );
stage.setScene(someScene);
...

【讨论】:

我正在尝试使用这种方法来填充 TableView 并显示以 TableView 为中心的 ProgressIndicator 但事实证明 getX() 和 getY() 的事件值不一样好像您在所有初始化过程结束后(在按钮中的单击事件内)使用场景和舞台,甚至舞台 getX() 和 getY() 返回 NaN,知道吗? @leobelizquierdo ,此时遇到 getY() 问题,你找到解决办法了吗? 如果我将pane 添加到已经附加到舞台的场景中,这将不起作用,对吧?不应该检查sceneProperty 侦听器,并且仅在舞台仍为空时添加stageProperty 侦听器吗?否则,就立即做我需要做的事?【参考方案4】:

您只需为AnchorPane 提供一个ID,然后您就可以从中获取Stage

@FXML private AnchorPane ap;
Stage stage = (Stage) ap.getScene().getWindow();

从这里,您可以添加您需要的Listener

编辑:正如下面 EarthMind 所述,它不必是 AnchorPane 元素;它可以是您定义的任何元素。

【讨论】:

请注意,element 可以是该窗口中带有fx:id 的任何元素:Stage stage = (Stage) element.getScene().getWindow();。例如,如果您的窗口中只有 Buttonfx:id,则使用它来获取舞台。 getScene() 仍然返回null 在初始化完成之前(例如在控制器初始化方法中)这将不起作用,因为 getScene() 返回 null。原始海报暗示这是他的情况。在这种情况下,Utku Özdemir 下面给出的备选方案 1 更好。如果您不需要舞台,那么一个监听器就足够了,我在 initialize() 中使用它来拉伸 ImageView:bgimage.sceneProperty().addListener((observableScene, oldScene, newScene) -> if (oldScene == null && newScene != null) bgimage.fitWidthProperty().bind(newScene.widthProperty()); ... ); 它解决了我的问题,我相信这是最好的方法。我认为值得注意的是,'ap' 是在 .fxml 文件中的 fx:id 字段中给出的 AnchorPane 的 ID(如果您正在使用 FXML 进行 UI 设计),或者是您给 AnchorPane 对象的名称。通常它适用于所有 XXXPane 对象。【参考方案5】:

可以用node.getScene获取,如果不从Platform.runLater调用,结果为空值。

空值示例:

node.getScene();

例子没有空值:

Platform.runLater(() -> 
    node.getScene().addEventFilter(KeyEvent.KEY_PRESSED, event -> 
               //your event
     );
);

【讨论】:

【参考方案6】:

分配 fx:id 或声明任何节点的变量:锚窗格、按钮等。然后向其添加事件处理程序并在该事件处理程序中插入以下给定代码:

Stage stage = (Stage)((Node)((EventObject) eventVariable).getSource()).getScene().getWindow();

希望,这对你有用!!

【讨论】:

【参考方案7】:

Platform.runLater 在初始化完成之前阻止执行。在这种情况下,我想在每次调整窗口宽度时刷新列表视图。

Platform.runLater(() -> 
    ((Stage) listView.getScene().getWindow()).widthProperty().addListener((obs, oldVal, newVal) -> 
        listView.refresh();
    );
);

在你的情况下

Platform.runLater(()->
    ((Stage)myPane.getScene().getWindow()).setOn*whatIwant*(...);
);

【讨论】:

以上是关于JavaFX:如何在初始化期间从控制器获取阶段?的主要内容,如果未能解决你的问题,请参考以下文章

如何从 JavaFX 中的 UI 控件中检索阶段

如何在一定时间后关闭一个阶段JavaFX

JavaFX如何改变阶段

JavaFX 初始化方法中的 NullPointerException

Vue生命周期四个阶段

如何在 JavaFX 中获得一个阶段的关闭事件?