【问题标题】:JavaFX: Need help understanding setControllerFactoryJavaFX:需要帮助理解 setControllerFactory
【发布时间】:2019-05-13 18:07:44
【问题描述】:

我在创建应用的过程中设置了多个自定义控制器,在使用 JavaFX 中的 setControllerFactory 组织这些控制器时需要一些帮助。

我对 JavaFX 相当缺乏经验,但我花了很多时间用 Scenebuilder 和 JavaFX 创建一个小应用程序。

应用背景 该应用程序包括: - 地图(实现为 imageView) - 带有用于拖放事件的按钮和图标的侧边栏。 - 地图还具有单独的图层作为拖放不同图标类型的目标。

作为我的拖放事件的原型,我使用了 Joel Graff (https://monograff76.wordpress.com/2015/02/17/developing-a-drag-and-drop-ui-in-javafx-part-i-skeleton-application/) 的说明。他写道:“为了让一个对象在容器边缘之外可见,它必须是父容器或其他祖先容器的子容器——它必须属于层次结构的更高级别。在我们的拖动图标的情况下,这意味着我们必须将其作为子项添加到 RootLayout 的顶级 AnchorPane。”他在他的项目中使用了动态根。

为了自学如何通过 FXML 使用自定义控件,我使用了 Irina Fedortsova 的教程 https://docs.oracle.com/javafx/2/fxml_get_started/custom_control.htm。 为了了解如何设置多个屏幕,我使用了视频 https://www.youtube.com/watch?v=5GsdaZWDcdY 和来自 https://github.com/acaicedo/JFX-MultiScreen 的关联代码。

在构建我的应用程序之后,我的应用程序的逻辑层越来越多地与表示层纠缠在一起,我觉得我的代码似乎会从一些重构中受益匪浅。 我的问题似乎是对控制器类的加载和初始化过程缺乏了解。由于拖动图标和 RootLayout 必须从一开始就加载,所以对我来说如何加载这些类以便以后再次调用它们是一个谜。

当我在寻找进一步的解决方案时,我反复遇到了 setControllerFactory 方法。不幸的是,对于如何正确使用它以及它的具体用途是什么,我找不到很好的解释。 我找到的唯一教程是:https://riptutorial.com/javafx/example/8805/passing-parameters-to-fxml---using-a-controllerfactory,不幸的是它似乎对我的目的有点不够。

我觉得我可以从一个方法/类中获益最多,我可以用它来组织我的所有自定义控制器,在适当的时间加载和初始化它们,然后再次访问它们(类似于接口和超类中的JFX-MultiScreen 的视频)。

【问题讨论】:

  • 控制器工厂是FXMLLoader 用来创建fx:controller 指定的控制器类的实例。默认情况下,它只是尝试通过反射使用无参数构造函数来实例化控制器。使用自定义控制器工厂可让您自定义控制器的实例化方式。例如,如果您有一个依赖注入框架(例如 Guice、CDI 等),那么您将使用自定义控制器工厂以便注入依赖项。
  • 这个问题可能会有所帮助:Passing Parameters JavaFX FXML.
  • 哇,真快!非常感谢。我希望我能尽快找到时间来完成这些建议。并提供我的最新进展。

标签: javafx scenebuilder


【解决方案1】:

我反复遇到 setControllerFactory 方法。不幸的是,我找不到一个很好的解释来说明如何正确使用它以及它的具体目的是什么

默认情况下,FXMLLoader.load() 方法使用 0-arg 构造函数实例化 fxml 文档中命名的控制器。当您希望 FXMLLoader 对象以某种方式实例化控制器时,使用 FXMLLoader.setControllerFactory​ 方法,例如在特定参数上使用不同的控制器构造函数,在控制器返回之前调用控制器上的方法等,如

FXMLLoader loader = new FXMLLoader(...);
loader.setControllerFactory(c -> {
   return new MyController("foo", "bar");
});

现在当您调用loader.load() 时,控制器将如上创建。但是,在预先存在的控制器上调用 FXMLLoader.setController​ 方法可能更容易。

我觉得我可以从一个方法/类中获益最多,我可以用它来组织我的所有自定义控制器,在适当的时间加载和初始化它们,然后再次访问它们

当我第一次遇到这个问题时,就像你一样,我尝试了很多方法。我最终决定将我的主要应用程序类变成一个单例。当您需要创建一个在整个程序中都可以访问的类的实例时,单例模式非常有用。我知道有很多人会对此提出异议(因为它本质上是一个具有附加结构的全局变量),但我发现它显着降低了复杂性,因为我不再需要管理对象引用的某种人工结构一路走来。

单例允许控制器通过调用例如 MyApp.getSingleton() 与您的主应用程序类进行通信。仍然在主应用程序类中,您可以在私有 HashMap 中组织所有视图,并添加可以添加或删除视图的公共 add(...)、remove(...) 和 activate(...) 方法从地图或激活地图中的视图(即将场景的根设置为新视图)。

对于一个有许多视图可能被放置在不同包中的应用程序,你可以用一个枚举来组织它们的位置:

public enum View {
    LOGIN("login/Login.fxml"),
    NEW_USER("register/NewUser.fxml"),
    USER_HOME("user/UserHome.fxml"),
    ADMIN_HOME("admin/AdminHome.fxml");

    public final String location;

    View(String location) {
        this.location = "/views/" + location;
    }
}

以下是主要应用程序类的示例:

public final class MyApp extends Application {

    // Singleton
    private static MyApp singleton;
    public MyApp() { singleton = this; }
    public static MyApp getSingleton() { return singleton; }

    // Main window
    private Stage stage;

    private Map<View, Parent> parents = new HashMap<>();

    @Override
    public void start(Stage primaryStage) {
        stage = primaryStage;
        stage.setTitle("My App");
        add(View.LOGIN);
        stage.setScene(new Scene(parents.get(View.LOGIN)));
        stage.show();
    }

    public void add(View view) {
        var loader = new FXMLLoader(getClass().getResource(view.location));

        try {
            Parent root = loader.load();
            parents.put(view, root);
        } catch (IOException e) { /* Do something */ }
    }

    public void remove(View view) {
        parents.remove(view);
    }

    public void activate(View view) {
        stage.getScene().setRoot(parents.get(view));
    }

    public void removeAllAndActivate(View view) {
        parents.clear();
        add(view);
        activate(view);
    }
}

如果您有应用程序范围的资源,您可以将它们放在应用程序类中并添加 getter/setter,以便您的控制器可以访问它们。这是一个示例控制器类:

public final class Login implements Initializable {

    MyApp app = MyApp.getSingleton();

    // Some @FXML variables here..

    @FXML private void login() {
        // Authenticate..
        app.removeAllAndActivate(View.USER_HOME);
    }

    @FXML private void createAccount() {
        app.add(View.NEW_USER);
        app.activate(View.NEW_USER);
    }

    @Override
    public void initialize(URL url, ResourceBundle rb) {}
}

【讨论】:

  • 非常感谢。对不起,我没有时间真正研究它。但是,一旦我有机会查看它,我会提供更新并将问题标记为已回答。无论如何提前感谢,非常感谢您的帮助。
猜你喜欢
  • 2013-04-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-23
  • 2022-01-15
  • 1970-01-01
  • 2012-09-01
相关资源
最近更新 更多