JavaFX:需要帮助理解 setControllerFactory

Bel*_*oso 1 javafx scenebuilder

我在创建应用程序期间设置了多个自定义控制器,并且需要一些帮助来使用 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-骨架-应用程序/)。他写道:“为了使一个对象在容器边缘之外可见,它必须是父容器或其他祖先容器的孩子——它必须属于更高层次的层次结构。在我们的拖拽图标的情况下,这意味着我们必须将其作为子项添加到 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 视频中的接口和超类-多屏)。

Rya*_*n C 5

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

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

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

现在,当您调用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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

如果您有应用程序范围的资源,您可以将它们放在应用程序类中并添加 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) {}
}
Run Code Online (Sandbox Code Playgroud)