【问题标题】:How to pass parameters to a controller's constructor when using `<fx:include>`?使用`<fx:include>`时如何将参数传递给控制器​​的构造函数?
【发布时间】:2020-03-21 23:40:53
【问题描述】:

我正在学习 JavaFX,但遇到了一个涉及控制器实例化的问题,我似乎无法解决。本质上,我想知道是否可以执行以下操作之一:

  1. 在包含带有&lt;fx:include&gt; 的FXML 时将参数传递给控制器​​的构造函数;或
  2. 指定在包含带有&lt;fx:include&gt; 的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 文件控制器的对象,我使用 &lt;fx:include&gt; 将其包含到主 FXML 文件中。

现在,这种创建我自己的控制器实例并将其提供给FXMLLoader 的方法对我来说非常有效。它使我可以轻松地将参数传递给控制器​​的构造函数,而无需任何混乱的反射。但是,它仅在我有一个 FXMLoader 对象来提供控制器实例时才有效。

other 的情况下,我使用 &lt;fx:include&gt; 从主 FXML 文件中包含一个 FXML 文件,JavaFX 为我创建了控制器,但我无法 (1) 传递参数到控制器的构造函数,或者 (2) 使用我自己的控制器实例。


我的尝试


在研究这个问题时,我遇到了this StackOverflow question,它似乎至少与这个问题有一些关系。从中了解到FXMLLoader.setControllerFactory(),乍一看似乎可以解决这个问题。然而,为了使用它,我不得不使用一些相当混乱的反射来检查类型的构造函数是否可以接受我的对象,然后使用 more 反射来创建控制器,一直希望由于我的代码中存在漏洞,不会引发任何错误。我不得不承认这是行不通的。

我也尝试过,不是将我的对象传递给控制器​​的构造函数,而是在控制器初始化之后在控制器上设置对象。但是,这并没有很好地工作,因为我需要在控制器的initialize() 方法中使用该对象,该方法称为before,我将在控制器上设置对象。这可以通过添加 另一个 初始化方法来解决,该方法可以找到需要该对象的任何功能,可能称为objectInitialized();但是我必须将此方法添加到需要此功能的每个控制器中,并且我必须记住在某个时候调用所有这些方法。另外,我希望对象是控制器类中的final 字段;显然,如果需要在外部设置,它就不能是final的。

最后,我还考虑了一个选项,即对于需要包含到主 FXML 文件中的每个 FXML 文件,而不是将其包含在 FMXL 中,我可以从 Java 控制器中执行此操作。这样我就可以创建自己的FXMLLoader,在上面设置我自己的控制器实例,从而解决问题。但是,如果可能的话,我更愿意将所有 UI 代码保留在 FXML 文件中。


总结


总之,在使用&lt;fx:include&gt;时,我需要一种将参数传递给控制器​​的构造函数的方法。

我知道这是一个很长的问题,而且有点复杂,所以我非常感谢您提供的任何帮助。另外,如果我需要澄清任何事情或发布其他代码,请在 cmets 中告诉我。

感谢大家的帮助!
——雅各布

【问题讨论】:

  • 在这种情况下我能想到的唯一方法是使用controllerFactory。 Afaik 控制器工厂也用于FXMLLoaders 加载嵌套的 fxmls。
  • 控制器的创建本质上是基于反射的,不管它是如何发生的。我认为我对您所链接问题的回答是进入此处的唯一方法。
  • @Fabian, @James_D:好的,我明白了。您认为从我的 Java 控制器中包含 FXML 是否是一种好方法(上面的选项 (3),在我尝试过的内容下)?起初,我有点想避免这种情况,但似乎这可能是最好的选择。我什至可以创建一个辅助方法来执行此操作,我可以在需要此功能时使用它。还是使用setControllerFactory() 会更好吗?
  • @JacobLockard 那行得通;我想我还是更喜欢控制器工厂选项;但您的里程可能会有所不同。您可能会考虑的另一件事是依赖注入工厂,例如 Spring 或 Guice;这些基本上可以确保控制器中的字段根据需要进行初始化,但当然它们有自己的学习曲线。
  • @James_D,我决定从控制器中包含它(请参阅下面的答案)。不过感谢您的帮助;你们的 cmets 确实帮助我澄清了一些事情,并为我未来的任何项目提供了新的想法。

标签: java javafx controller fxml fxmlloader


【解决方案1】:

经过进一步考虑,我决定在“我尝试过的内容”下的问题中简单地实施选项 (3)。基本上,我没有使用&lt;fx:include&gt;,而是认为包含任何需要直接/手动从我的Java代码中获取参数的控制器会更容易、更灵活、更面向未来。


为什么?


当我更多地思考我的问题时,我意识到我绝对没有办法“从我的 FXML”将参数传递给我的控制器的构造函数。毕竟,由于我试图传递的对象是在我的 Java 代码(主应用程序类)中定义的,所以没有办法在 FXML 中使用它。因此,我对这个问题的整体看法发生了变化。

选项 1

在我重新评估了我的选择之后,我考虑过只使用FXMLLoader.setControllerFactor(); James_D 在他发布的关于我的问题的 cmets 中证实了这种方法:由于 all 控制器实例化是通过反射发生的,因此使用反射将我的对象传递给控制器​​并不一定像我想。事实上,它可能会很好用。

然而,如果我需要做的只是将一个 single 对象传递给控制器​​,这会很有效,但如果对于我的某些控制器,我想传递 multiple 对象(可能是不同类型的)?然后控制器工厂会变得相当笨拙,因为我必须检查可能不同类型的 多个 参数,然后传递正确的参数。

此外,如果我想将对象传递给控制器​​,而不是从定义控制器工厂的应用程序类,而是从其他地方传递,该怎么办?例如,假设我有一个应用程序类MyApplication.java、一个“主”控制器MainController.java,以及一个“在”主控制器“下”的控制器NavigationBarController.java?也许我在某个时候想要传递一个对象,不是从 MyApplicationNavigationBarController,而是从 MainControllerNavigationBarController。如果是这种情况(很有可能),那么我的控制器工厂将不再工作,因为它无法访问必要的对象。因此,我得出结论,setControllerFactory() 不适合我,至少不是很好。

选项 2(我使用的)

另一个选项(也是我最终使用的选项)是从我的 Java 代码中包含我的控制器,直接使用 FXMLLoader;并且,与其包含主应用程序类中的所有控制器,我还可以将每个控制器都包含在它“驻留”的 controller 中。

例如,使用上面选项 (1) 中提供的示例,而不是在 FXML 文件中使用控制器工厂和 fx:controller 属性,我将从任何 FXML 文件/控制器对中删除 fx:controller 属性需要访问我的对象。

相反,在MyApplication 中,我将直接使用FXMLLoader 来加载、初始化和添加MainController。然后,在MainController 中,我会使用FXMLLoader 来包含NavigationBarController

现在,这种方法最大的潜在弱点是,每次我想包含 FXML 文件/控制器对时,我基本上都需要重复加载代码。为了解决这个问题,我创建了一种名为 FXMLQuickLoader 的“实用程序类”,其中包含轻松快速地加载 FXML 文件/控制器对的方法,但使用自定义控制器对象。如果您有兴趣查看代码,我创建了一个包含FXMLQuickLoader.javaGitHub Gist

选项 3(未经测试)

现在,James_D 指出我可能会使用一种叫做“依赖注入工厂”的东西。如果我正确理解他,它将自动初始化我的控制器的字段,而不是我需要将对象传递给构造函数。该解决方案似乎可以完美运行;但是,由于这是我的第一个 JavaFX 项目,而且由于时间有限,我认为学习它可能需要很长时间。

但是,对于我的下一个 JavaFX 项目,我可能会研究它并(强烈)考虑使用它。如果我愿意,我稍后会尝试更新此答案。


总结


总之,对我来说,我认为最好使用FXMLLoader 手动包含我的控制器,而不是尝试使用其中一种替代方法。但是,我只是在学习 JavaFX,而且我对这个主题并不完全了解,所以你应该对这个答案持保留态度。不过,它可能会帮助其他出现的用户,并至少概述一些潜在解决方案。我仍然愿意接受有关该问题的进一步建议。

【讨论】:

    【解决方案2】:

    这可以通过实现一个单例(或 Spring 中的 Bean)来实现,然后每个想要访问“共享”数据的控制器只需使用 getInstance 方法访问单例然后恢复它。

    如果使用 Map 属性(而不是 User)实现并使用 put("key", objectValue) 方法放置数据,然后使用 get("key"); 会更灵活;

    基于此post

    //Bean or Singleton
    public final class UserHolder {
    
    private User user;
    private final static UserHolder INSTANCE = new UserHolder();
    
    private UserHolder() {}
    
    public static getInstance() {
      return INSTANCE;
    }
    
    public void setUser(User u) {
      this.user = u;
    }
    
    public User getUser() {
      return this.user;
    }
    }
    
    //put data in Controller 1
    @FXML
    private void sendData(MouseEvent event) {
    // Step 1
    User u = new User();
    Node node = (Node) event.getSource();
    Stage stage = (Stage) node.getScene().getWindow();
    stage.close();
    try {
      Parent root =     
      FXMLLoader.load(getClass().getClassLoader().getResource("fxml/SceneB.fxml"));
      // Step 2
      UserHolder holder = UserHolder.getInstance();
      // Step 3
      holder.setUser(u);
      // Step 4
      Scene scene = new Scene(root);
      stage.setScene(scene);
      stage.show();
    } catch (IOException e) {
      System.err.println(String.format("Error: %s", e.getMessage()));
    }
    }
    
    //recover data in Controller 2
    @FXML
    private void receiveData(MouseEvent event) {
      UserHolder holder = UserHolder.getInstance();
      User u = holder.getUser();
      String name = u.getName();
      String email = u.getEmail();
    }
    

    【讨论】:

      猜你喜欢
      • 2017-07-26
      • 2016-08-22
      • 2015-09-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多