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


89

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

((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()中添加一个侦听器,当它变为非null时,我得到了场景,将其添加到.windowProperty()中,我的天哪!侦听器处理,这是我终于检索到的阶段。最后,设置所需的侦听器来安排事件。我认为听众太多。这是解决我的问题的唯一方法吗?

Answers:


116

您可以FXMLLoader在初始化之后通过通过来获取控制器的实例getController(),但是您需要实例化一个,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

1
认为您缺少演员表吗?父根=(父)loader.load();
Paul Eden

12
这真的是最好的方法吗?JavaFX框架没有提供任何存档文件吗?
汉尼斯

1
由于某些原因,如果您使用不带参数的构造函数并将URL传递给load()方法,则此方法将无效。(此外,getController听起来像应该在Javadoc上setController。)
Bombe 2015年

4
@Bombe这是因为带有URL参数的load()方法是静态方法,不会在调用它的实例上设置任何内容。
zhujik 2015年

@Sebastian,哎呀,的确如此。我的错。
孟买

109

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

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

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

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


2
简短而甜美。谢谢
塞德里克

7
请注意,该窗口中element可以是任何带有的元素fx:idStage stage = (Stage) element.getScene().getWindow();。例如,如果您的窗口中只有一个Button带有的fx:id,则可以使用来获得舞台。
EarthMind

28
getScene()仍然返回null
m0skit0

3
这就是答案,简洁!
德斯坦(Destan),2013年

12
在初始化完成之前(例如在控制器的初始化方法中),这将不起作用,因为getScene()返回null。原始海报暗示这就是他的情况。在这种情况下,以下UtkuÖzdemir给出的替代方案1更好。如果您不需要舞台,那么一个监听器就足够了,我在initialize()中使用它来进行ImageView拉伸:bgimage.sceneProperty().addListener((observableScene, oldScene, newScene) -> { if (oldScene == null && newScene != null) { bgimage.fitWidthProperty().bind(newScene.widthProperty()); ... } });
Georgie

32

我知道这不是您想要的答案,但是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);
...

1
这是地球上最短的JavaFX参考!!谢谢,Utku!
Aram Paronikyan 2015年

看它的有趣方式。谢谢:)
UtkuÖzdemir'15

我正在尝试使用这种方法来填充TableView并显示对TableView居中显示的ProgressIndicator,但是事实证明,在事件内部,getX()和getY()的值与所有初始化之后使用的值不同场景和舞台的处理结束(在按钮的click事件内),甚至舞台getX()和getY()返回NaN,有什么想法吗?
leobelizquierdo '16

@leobelizquierdo,此刻遇到了getY()问题,您找到解决方案了吗?
JonasAnon

如果我将pane场景添加到已经附加到舞台上的场景,将无法正常工作,对吗?不应在sceneProperty侦听器中进行检查,仅stageProperty在阶段仍然为空时才添加侦听器吗?否则,请立即执行我需要做的事情?
明天

14

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

  1. 在自己创建的控制器类中添加一个额外的方法,例如(设置控制器类中的阶段将是一个setter方法),

    private Stage myStage;
    public void setStage(Stage stage) {
         myStage = stage;
    }
  2. 在启动方法和设置阶段中获取控制器

    FXMLLoader loader = new FXMLLoader(getClass().getResource("MyFXML.fxml"));
    OwnController controller = loader.getController();
    controller.setStage(this.stage);
  3. 现在您可以访问控制器中的舞台


这是我的赢家。罗伯特·马丁(Robert Martin)的答案起初是有效的,但是随后引发了一个错误,我通过stage这样的方式解决了这个错误。
Dammeul

1

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

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

希望这对你有用!


1

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

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

在你的情况下

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

-3

node.getScene如果您不打来电话,可以与取得联系Platform.runLater,则结果为空值。

空值示例:

node.getScene();

示例没有空值:

Platform.runLater(() -> {
    node.getScene().addEventFilter(KeyEvent.KEY_PRESSED, event -> {
               //your event
     });
});
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.