JavaFX:需要帮助来了解setControllerFactory

时间:2019-05-13 18:07:44

标签: 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-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的视频)。

1 个答案:

答案 0 :(得分: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) {}
}