【发布时间】:2020-03-21 23:40:53
【问题描述】:
我正在学习 JavaFX,但遇到了一个涉及控制器实例化的问题,我似乎无法解决。本质上,我想知道是否可以执行以下操作之一:
- 在包含带有
<fx:include>的FXML 时将参数传递给控制器的构造函数;或 - 指定在包含带有
<fx:include>的FXML 文件时要使用的自定义控制器实例。
请注意,这些问题是相关的。事实上,我之所以询问选项(2)是因为它可以解决选项(1)。
我的设置
我有以下“主要”FXML 文件:
<!-- XML declaration, imports, etc. removed for brevity -->
<BorderPane xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml">
<!-- ... -->
<center>
<!-- Note that PageSwitcher is a custom control that is capable of switching between pages — you should be able to ignore it here. -->
<PageSwitcher fx:id="mainPageSwitcher" currentPageIndex="0">
<!-- ... -->
<fx:include source="dashboard.fxml" fx:id="dashboard" />
</PageSwitcher>
</center>
</BorderPane>
它有一个关联的控制器MainPaneController。我不会在这里展示它,但如果需要,我可以展示它。
您可能已经注意到,我的主 FXML 文件在其 BorderPane 上没有 fx:controller 属性,尽管我说过它有一个关联的控制器。这是因为,我没有让FXMLLoader 为我创建一个控制器(因此,我无法将参数传递给控制器类的构造函数),而是在我的 main 中加载主 FXML 页面时选择了应用程序类(即扩展应用程序的类),以创建我自己的MainPaneController 类实例。可以看到我的主应用类的start()方法:
@Override
public void start(Stage primaryStage) throws IOException {
FXMLLoader mainPaneLoader;
MainPaneController mainPaneController;
Parent mainPane;
// Initialize the project manager.
projectManager = new ProjectManager(primaryStage);
// Initialize the main pane loader.
mainPaneLoader = new FXMLLoader();
// Initialize the main pane controller.
mainPaneController = new MainPaneController(projectManager);
// Load the main pane.
mainPaneLoader.setController(mainPaneController);
mainPaneLoader.setLocation(getClass().getResource(MAIN_PANE_FXML_PATH));
mainPane = mainPaneLoader.load();
Scene mainScene;
// Create the main scene and add it to the primary scene.
mainScene = new Scene(mainPane);
primaryStage.setScene(mainScene);
// Initialize the primary stage.
primaryStage.setTitle(APPLICATION_TITLE);
// Show the primary stage.
primaryStage.show();
}
请注意,上面定义并传递给主窗格控制器的构造函数的“项目经理”对象实际上是整个问题背后的主要动机;它是(除了传递给主控制器之外)我需要传递给 FXML 文件控制器的对象,我使用 <fx:include> 将其包含到主 FXML 文件中。
现在,这种创建我自己的控制器实例并将其提供给FXMLLoader 的方法对我来说非常有效。它使我可以轻松地将参数传递给控制器的构造函数,而无需任何混乱的反射。但是,它仅在我有一个 FXMLoader 对象来提供控制器实例时才有效。
在 other 的情况下,我使用 <fx:include> 从主 FXML 文件中包含一个 FXML 文件,JavaFX 为我创建了控制器,但我无法 (1) 传递参数到控制器的构造函数,或者 (2) 使用我自己的控制器实例。
我的尝试
在研究这个问题时,我遇到了this StackOverflow question,它似乎至少与这个问题有一些关系。从中了解到FXMLLoader.setControllerFactory(),乍一看似乎可以解决这个问题。然而,为了使用它,我不得不使用一些相当混乱的反射来检查类型的构造函数是否可以接受我的对象,然后使用 more 反射来创建控制器,一直希望由于我的代码中存在漏洞,不会引发任何错误。我不得不承认这是行不通的。
我也尝试过,不是将我的对象传递给控制器的构造函数,而是在控制器初始化之后在控制器上设置对象。但是,这并没有很好地工作,因为我需要在控制器的initialize() 方法中使用该对象,该方法称为before,我将在控制器上设置对象。这可以通过添加 另一个 初始化方法来解决,该方法可以找到需要该对象的任何功能,可能称为objectInitialized();但是我必须将此方法添加到需要此功能的每个控制器中,并且我必须记住在某个时候调用所有这些方法。另外,我希望对象是控制器类中的final 字段;显然,如果需要在外部设置,它就不能是final的。
最后,我还考虑了一个选项,即对于需要包含到主 FXML 文件中的每个 FXML 文件,而不是将其包含在 FMXL 中,我可以从 Java 控制器中执行此操作。这样我就可以创建自己的FXMLLoader,在上面设置我自己的控制器实例,从而解决问题。但是,如果可能的话,我更愿意将所有 UI 代码保留在 FXML 文件中。
总结
总之,在使用<fx:include>时,我需要一种将参数传递给控制器的构造函数的方法。
我知道这是一个很长的问题,而且有点复杂,所以我非常感谢您提供的任何帮助。另外,如果我需要澄清任何事情或发布其他代码,请在 cmets 中告诉我。
感谢大家的帮助!
——雅各布
【问题讨论】:
-
在这种情况下我能想到的唯一方法是使用
controllerFactory。 Afaik 控制器工厂也用于FXMLLoaders 加载嵌套的 fxmls。 -
控制器的创建本质上是基于反射的,不管它是如何发生的。我认为我对您所链接问题的回答是进入此处的唯一方法。
-
@Fabian, @James_D:好的,我明白了。您认为从我的 Java 控制器中包含 FXML 是否是一种好方法(上面的选项 (3),在我尝试过的内容下)?起初,我有点想避免这种情况,但似乎这可能是最好的选择。我什至可以创建一个辅助方法来执行此操作,我可以在需要此功能时使用它。还是使用
setControllerFactory()会更好吗? -
@JacobLockard 那行得通;我想我还是更喜欢控制器工厂选项;但您的里程可能会有所不同。您可能会考虑的另一件事是依赖注入工厂,例如 Spring 或 Guice;这些基本上可以确保控制器中的字段根据需要进行初始化,但当然它们有自己的学习曲线。
-
@James_D,我决定从控制器中包含它(请参阅下面的答案)。不过感谢您的帮助;你们的 cmets 确实帮助我澄清了一些事情,并为我未来的任何项目提供了新的想法。
标签: java javafx controller fxml fxmlloader