访问FXML控制器类

80
我希望随时与FXML控制器类进行通信,以便从主应用程序或其他阶段更新屏幕上的信息。
这个可能吗?我没有找到任何方法实现它。
静态函数可能是一种方式,但它们无法访问表单的控件。
有什么想法吗?
4个回答

116

您可以通过FXMLLoader获取控制器

FXMLLoader fxmlLoader = new FXMLLoader();
Pane p = fxmlLoader.load(getClass().getResource("foo.fxml").openStream());
FooController fooController = (FooController) fxmlLoader.getController();

将其存储到您的主舞台中并提供getFooController()获取器方法。从其他类或阶段,每次需要刷新加载的“foo.fxml”页面时,请从其控制器请求:

getFooController().updatePage(strData);

updatePage() 可以像这样:

// ...
@FXML private Label lblData;
// ...
public void updatePage(String data){
    lblData.setText(data);
}
// ...
在FooController类中。
这样其他页面用户就不需要关心页面的内部结构,比如Label lblData是什么和在哪里。
还可以查看https://dev59.com/sWkw5IYBdhLWcg3wrsnn#10718683。在JavaFX 2.2中,FXMLLoader得到了改进。

2
我发现使用静态函数比这个解决方案更容易且效果更好(至少对我来说是这样)。能够访问控件的关键是拥有一个公共静态实例,该实例可以访问所有控件和公共方法的类。 - betaman
我知道这是一个老问题...但是有人能够提供更多关于答案的细节吗?第一行代码和getFooController()放在哪里? - adeena
@UlukBiy 嗯...也许我正在尝试做一些与原帖不同的事情。我有一个FXML应用程序和一个主舞台上的TextArea。我已经弄清楚了如何从其他FXML控制器中访问它,但是无法从包/项目中的任何其他类型的类中访问它。 - adeena
1
@TannerSummers,一旦您获得了控制器实例,您需要将其放置在某个公共位置(类似于存储库),以便您应用程序的其他类(模块)可以访问它。我所说的“主舞台”是指具有JavaFX应用程序入口点的类,即具有start(Stage stage)方法的类。在该主类中,您可以选择将控制器实例存储在某个集合中。或者创建一个新的ControllerManager类,充当存储和访问它们的存储库。设计取决于您。 - Uluk Biy
当我尝试这样做时,即使我将它放置在所有应该初始化的东西之后,控制器实例返回为“null”。我不想创建另一个类似的问题,但我也不能在这里执行 SSCSE。还有其他人遇到这个问题吗? - Masked Coder
显示剩余11条评论

30
为了帮助澄清被接受的答案并可能为新手节省一些时间,以下是关于JavaFX的说明:

对于JavaFX FXML应用程序,NetBeans将自动生成您的主类中的start方法,如下所示:

@Override
public void start(Stage stage) throws Exception {
    Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));

    Scene scene = new Scene(root);

    stage.setScene(scene);
    stage.show();
}

现在,我们只需要将FXMLLoader的load()方法从静态实现更改为实例化实现,然后我们就可以使用实例的方法来获取控制器类,像这样:

//Static global variable for the controller (where MyController is the name of your controller class
static MyController myControllerHandle;

@Override
public void start(Stage stage) throws Exception {
    //Set up instance instead of using static load() method
    FXMLLoader loader = new FXMLLoader(getClass().getResource("FXMLDocument.fxml"));
    Parent root = loader.load();

    //Now we have access to getController() through the instance... don't forget the type cast
    myControllerHandle = (MyController)loader.getController();

    Scene scene = new Scene(root);

    stage.setScene(scene);
    stage.show();
}

8
另一种解决方案是从您的控制器类中设置控制器,方法如下...
public class Controller implements javafx.fxml.Initializable {

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        // Implementing the Initializable interface means that this method
        // will be called when the controller instance is created
        App.setController(this);
    }

}

这是我喜欢使用的解决方案,因为代码有点混乱,需要创建一个完全功能的FXMLLoader实例来正确处理本地资源等。
@Override
public void start(Stage stage) throws Exception {
    Parent root = FXMLLoader.load(getClass().getResource("/sample.fxml"));
}

对比
@Override
public void start(Stage stage) throws Exception {
    URL location = getClass().getResource("/sample.fxml");
    FXMLLoader loader = createFXMLLoader(location);
    Parent root = loader.load(location.openStream());
}

public FXMLLoader createFXMLLoader(URL location) {
    return new FXMLLoader(location, null, new JavaFXBuilderFactory(), null, Charset.forName(FXMLLoader.DEFAULT_CHARSET_NAME));
}

该解决方案同样适用于早期版本的JavaFX(<2.2)。 - Neil
总的来说,我认为选择的答案是更好的,尽管这很巧妙:首先,您可以拥有多个控制器,因此您真的希望在您的“应用程序”类中有一个静态映射,键控制器-->值加载器(请注意,您始终可以从加载器获取控制器)。其次,我们已经得到了一个“getController()”方法,因此使用它是有意义的。第三,当涉及实例操作时,应避免使用静态方法,并且可能作为一般编码偏好。 - mike rodent
不要把控制器存储在静态字段中,这不是一个好主意。 - Lii

3
在从主屏幕加载对象时,我发现并且成功地使用查找并将数据设置在一个不可见的标签内,稍后可以从控制器类中检索。代码如下:
Parent root = FXMLLoader.load(me.getClass().getResource("Form.fxml"));
Label lblData = (Label) root.lookup("#lblData");
if (lblData!=null) lblData.setText(strData); 

这个方法可以运行,但一定有更好的办法。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接