是否可以重新加载相同的FXML / Controller实例? [英] Is it possible to reload the same FXML/Controller instance?

查看:129
本文介绍了是否可以重新加载相同的FXML / Controller实例?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

目标:实施标准的设置GUI窗口。左侧的 ListView 中的类别以及右侧的 Pane 中的相应选项。

(请忽略重复类别的明显错误;仍在处理)

Goal: Implement a standard "Settings" GUI window. Categories in a ListView on the left and the corresponding options in a Pane on the right. (please ignore the obvious bug with repeated categories; still working on it)

我有一个整个设置窗口的主窗口,其中包含 ListView 以及所有类别的设置。窗口的右侧有一个 AnchorPane ,当从列表中选择一个时,它用于为每个类别加载单独的FXML文件。

I have a main window for the overall Settings window that contains a ListView with all the categories of settings. The right side of the window has an AnchorPane which is used to load separate FXML files for each category when one is selected from the list.

当用户选择一个类别时,我需要他们能够编辑右侧的设置,切换到另一个类别并进行更多更改。然而,如果他们返回第一类,那里所做的更改仍然存在。

When a user selects a category, I need them to be able to edit the settings on the right, switch to another category and make more changes. Yet, if they return to the first category, the changes made there persist.

我明显的问题是,每次用户更改类别时, FXMLLoader 重新加载FXML文件和控制器,将所有控件重置为默认值。

My obvious issue is that each time a user changes categories, the FXMLLoader reloads the FXML file and controller, resetting all controls within to their default values.

因此可以重用FXML文件已被装载和改变?

So is it possible to reuse an FXML file that has already been loaded and altered?

研究:

我发现的唯一答案似乎解决了这个问题是如何在不重新加载FXML文件的情况下切换javafx应用程序控制器? 。这提到使用Singleton作为FXML控制器,但没有解决每次重新加载FXML文件本身的问题。

The only answer I found that seems to address the issue is How to swich javafx application controller without reloading FXML file?. That mentions using a Singleton for the FXML controller, but does not address the issue with the FXML file itself being reloaded each time.

如果有人能指出我会很高兴这种设置菜单的一个基本示例。

I would be happy if someone could point to a basic example of this type of Settings menu.

推荐答案

我基本上有三种方法可以做到这一点:

There are basically three ways I see to do this:


  1. 定义表示数据的模型(设置),并创建一个实例。每次重新加载FXML文件,并将单个实例传递给控制器​​。使用模型中的数据绑定UI中的数据。这样,当您重新加载FXML时,它将使用相同的数据进行更新。 (这是我的首选选项。)

  2. 创建一次控制器。每次重新加载FXML文件,每次都设置相同的控制器。让 initialize()方法从本地存储的字段或模型更新UI。重新加载FXML文件时将替换 @FXML - 注释字段,并将调用 initialize()方法,使用现有数据更新新控件。 (这感觉有点人为。从口头上讲,任何名为 initialize()的方法都只能执行一次。但是,这是完全可行的。)

  3. 加载每个FXML文件一次并缓存UI(可能还有控制器)。然后,当用户在列表视图中选择某些内容时,只显示已加载的视图。这可能是最简单的,但在内存中花费更多,因为您始终将所有视图保留在内存中。

  1. Define a model representing the data (Settings), and create a single instance of it. Reload the FXML files each time, and pass the single instance to the controller. Bind the data in the UI with the data in the model. That way, when you reload the FXML, it will be updated with the same data. (This is my preferred option.)
  2. Create the controllers once. Reload the FXML files each time, setting the same controller each time. Have the initialize() method update the UI from either locally-stored fields, or a model. The @FXML-annotated fields will be replaced when you reload the FXML file and the initialize() method will be called, updating the new controls with the existing data. (This feels a little artificial. Morally speaking, any method called initialize() should only be executed once. However, this is perfectly workable.)
  3. Load each FXML file once and cache the UI (and probably the controller). Then when the user selects something in the list view, just display the already-loaded view. This is probably the simplest, but costs a little more in memory as you are keeping all views in memory at all times.

假设您有一个模型,可能如下所示:

Suppose you have a model, which might look like this:

public class Settings {

    private final UserInfo userInfo ;
    private final Preferences prefs ;
    private final Appearance appearance ;

    public Settings(UserInfo userInfo, Preferences prefs, Appearance appearance) {
        this.userInfo = userInfo ;
        this.prefs = prefs ;
        this.appearance = appearance ;
    }

    public Settings() {
        this(new UserInfo(), new Preferences(), new Appearance());
    }

    public UserInfo getUserInfo() {
        return userInfo ;
    }

    public Preferences getPreferences() {
        return prefs ;
    }

    public Appearance getAppearance() {
       return appearance ;
    }
}

public class UserInfo {

    private final StringProperty name = new SimpleStringProperty() ;
    private final StringProperty department = new SimpleStringProperty() ;
    // etc...

    public StringProperty nameProperty() {
        return name ;
    }

    public final String getName() {
        return nameProperty().get();
    }

    public final void setName(String name) {
        nameProperty().set(name);
    }

    // etc...
}

(同样适用于偏好外观等)

现在您为使用模型的各个屏幕定义控制器,例如

Now you define controllers for you individual screens that use a model, e.g.

public class UserInfoController {

    private final UserInfo userInfo ;

    @FXML
    private TextField name ;
    @FXML
    private ComboBox<String> department ;

    public UserInfoController(UserInfo userInfo) {
        this.userInfo = userInfo ;
    }

    public void initialize() {
        name.textProperty().bindBidirectional(userInfo.nameProperty());
        department.valueProperty().bindBidirectional(userInfo.departmentProperty());
    }
}

然后你的主控制器看起来像:

and then you main controller looks like:

public class MainController {

    @FXML
    private BorderPane root ;
    @FXML
    private ListView<String> selector ;

    private Settings settings = new Settings() ; // or pass in from somewhere else..

    public void initialize() {
        selector.getSelectionModel().selectedItemProperty().addListener((obs, oldSelection, newSelection) -> {
            if ("User Information".equals(newSelection)) {
                loadScreen("UserInfo.fxml", new UserInfoController(settings.getUserInfo()));
            } else if ("Preferences".equals(newSelection)) {
                loadScreen("Preferences.fxml", new PreferencesController(settings.getPreferences()));
            } else if ("Appearance".equals(newSelection)) {
                loadScreen("Appearance.fxml", new AppearanceController(settings.getAppearance()));
            } else {
                root.setCenter(null);
            }
    }

    private void loadScreen(String resource, Object controller) {
        try {
            FXMLLoader loader = new FXMLLoader(getClass().getResource(resource));
            loader.setController(controller);
            root.setCenter(loader.load());
        } catch (IOException exc) {
            exc.printStackTrace();
            root.setCenter(null);
        }
    }
}

(显然你可以制作列表视图清理程序的处理程序,通过定义封装控制器的资源名称,显示名称和工厂的简单视图类,并使用它填充列表视图,而不是打开字符串。)

(Obviously you can make the handler for the list view cleaner by defining a simple view class encapsulating the resource name, display name, and a factory for the controller, and populating the list view with it, instead of switching on strings.)

请注意,由于您在代码中的 FXMLLoader 上设置控制器, UserInfo.fxml Preferences.fxml Appearance.fxml 应该 fx:controller 属性定义。

Note that since you are setting the controller on the FXMLLoader in code, UserInfo.fxml, Preferences.fxml and Appearance.fxml should not have a fx:controller attribute defined.

第二个选项只是对此的温和重构。创建一次控制器并保持对它们的引用。请注意,如果您愿意,可以在此版本中删除模型,因为控制器具有数据,因此您可以只返回它们。所以这可能看起来像

The second option is just a mild refactoring of this. Create the controllers once and keep a reference to them. Note that if you wanted, you could get rid of the model in this version, as the controllers have the data, so you can just refer back to them. So this might look like

public class UserInfoController {

    @FXML
    private TextField name ;
    @FXML
    private ComboBox<String> department ;

    private final StringProperty nameProp = new SimpleStringProperty();
    private final ObjectProperty<String> departmentProp = new SimpleObjectProperty();

    public StringProperty nameProperty() {
        return nameProp;
    }

    public final String getName() {
        return nameProperty().get();
    }

    public final void setName(String name) {
        nameProperty().set(name);
    }

    public ObjectProperty<String> departmentProperty() {
        return departmentProp ;
    }

    public final String getDepartment() {
        return departmentProperty().get();
    }

    public final void setDepartment(String department) {
        departmentProperty().set(department);
    }

    public void initialize() {
        // initialize controls with data currently in properties, 
        // and ensure changes to controls are written back to properties:
        name.textProperty().bindBidirectional(nameProp);
        department.valueProperty().bindBidirectional(departmentProp);
    }
}

然后

public class MainController {

    @FXML
    private BorderPane root ;
    @FXML
    private ListView<String> selector ;

    private UserInfoController userInfoController = new UserInfoController();
    private PreferencesController preferencesController = new PreferencesController();
    private AppearanceController appearanceController = new AppearanceController();

    public void initialize() {
        // initialize controllers with data if necessary...

        selector.getSelectionModel().selectedItemProperty().addListener((obs, oldSelection, newSelection) -> {
        selector.getSelectionModel().selectedItemProperty().addListener((obs, oldSelection, newSelection) -> {
            if ("User Information".equals(newSelection)) {
                loadScreen("UserInfo.fxml", userInfoController);
            } else if ("Preferences".equals(newSelection)) {
                loadScreen("Preferences.fxml", preferencesController);
            } else if ("Appearance".equals(newSelection)) {
                loadScreen("Appearance.fxml", appearanceController);
            } else {
                root.setCenter(null);
            }
        }
    }

    private void loadScreen(String resource, Object controller) {
        // as before...
    }
}

这很有效,因为重新加载FXML文件时不会创建新的控制器,控制器中的初始化方法使用已存在的数据更新控件。 (注意调用 bindBidirectional 方法的哪个方法。)

This works because you don't create new controllers when the FXML files are reloaded, and the initialize methods in the controllers update the controls with the data already there. (Note which way round the bindBidirectional methods are invoked.)

第三个选项可以在主控制器中实现,也可以在主fxml文件中实现。要在控制器中实现它,你基本上可以

The third option can be implemented either in the main controller, or in the main fxml file. To implement it in the controller, you basically do

public class MainController {

    @FXML
    private BorderPane root ;
    @FXML
    private ListView<String> selector ;

    private Parent userInfo ;
    private Parent prefs;
    private Parent appearance;

    // need controllers to get data later...

    private UserInfoController userInfoController ;
    private PreferencesController prefsController ;
    private AppearanceController appearanceController ;

    public void initialize() throws IOException {

        FXMLLoader userInfoLoader = new FXMLLoader(getClass().getResource("userInfo.fxml));
        userInfo = userInfoLoader.load();
        userInfoController = userInfoLoader.getController();

        FXMLLoader prefsLoader = new FXMLLoader(getClass().getResource("preferences.fxml));
        prefs = prefsLoader.load();
        prefsController = prefsLoader.getController();

        FXMLLoader appearanceLoader = new FXMLLoader(getClass().getResource("appearance.fxml));
        appearance = appearanceLoader.load();
        appearanceController = appearanceLoader.getController();

        // configure controllers with data if needed...

        selector.getSelectionModel().selectedItemProperty().addListener((obs, oldSelection, newSelection) -> {
            if ("User Information".equals(newSelection)) {
                root.setCenter(userInfo);
            } else if ("Preferences".equals(newSelection)) {
                root.setCenter(prefs); 
            } else if ("Appearance".equals(newSelection)) {
                root.setCenter(prefs);
            } else {
                root.setCenter(null);
            }
        }
    }
}

请注意,您将恢复到FXML文件中通常的 fx:controller 属性s。

Note here you would revert to the usual fx:controller attribute in your FXML files.

这只会加载FXML文件一次,因此视图只会在其所有状态下持续存在。

This will work as you are only loading the FXML files once, so the view simply persists with all its state.

如果你想用这种方法在FXML中定义视图,你可以:

If you want to define the views in FXML in this approach, you can:

主fxml文件:

<!-- imports etc omitted -->
<BorderPane xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1"
    fx:controller="com.example.MainController">

    <left>
        <ListView fx:id="selector" />
    </left>

    <fx:define>
        <fx:include fx:id="userInfo" source="UserInfo.fxml" >
    </fx:define>

    <fx:define>
        <fx:include fx:id="prefs" source="Preferences.fxml" >
    </fx:define>

    <fx:define>
        <fx:include fx:id="appearance" source="Appearance.fxml" >
    </fx:define>

</BorderPane>

的FXML注入规则< fx:include> 是包含的FMXL文件的根用指定的 fx:id 注入(例如 userInfo )当控制器附加到 fx:id (例如, userInfoController )。所以这个主控制器现在看起来像

The rule for FXML-injection for <fx:include> is that the root of the included FMXL file is injected with the specified fx:id (e.g. userInfo) and the controllers for those included files ("nested controllers") are injected to the field with the name give when "Controller" is appended to the fx:id (e.g. to userInfoController). So the main controller for this would now look like

public class MainController {

    @FXML
    private BorderPane root ;
    @FXML
    private ListView<String> selector ;

    @FXML
    private Parent userInfo ;
    @FXML
    private Parent prefs;
    @FXML
    private Parent appearance;

    // need controllers to get data later...

    @FXML
    private UserInfoController userInfoController ;
    @FXML
    private PreferencesController prefsController ;
    @FXML
    private AppearanceController appearanceController ;

    public void initialize() {

        // configure controllers with data if needed...

        selector.getSelectionModel().selectedItemProperty().addListener((obs, oldSelection, newSelection) -> {
            if ("User Information".equals(newSelection)) {
                root.setCenter(userInfo);
            } else if ("Preferences".equals(newSelection)) {
                root.setCenter(prefs); 
            } else if ("Appearance".equals(newSelection)) {
                root.setCenter(prefs);
            } else {
                root.setCenter(null);
            }
        }
    }
}

这篇关于是否可以重新加载相同的FXML / Controller实例?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆