我基本上可以通过三种方式执行此操作:
定义一个代表数据(Settings)的模型,并为其创建一个实例。每次都重新加载FXML文件,并将单个实例传递给控制器。将UI中的数据与模型中的数据绑定。这样,当您重新加载FXML时,它将使用相同的数据进行更新。(这是我的首选。)
一次创建控制器。每次重新加载FXML文件,每次设置相同的控制器。让该initialize()方法从本地存储的字段或模型更新UI。@FXML重新加载FXML文件时,-annotated字段将被替换,该initialize()方法将被调用,并使用现有数据更新新控件。(这感觉有些虚假。从道德上讲,任何调用的方法initialize()都只能执行一次。但是,这是完全可行的。)
加载每个FXML文件一次,并缓存UI(可能还包含控制器)。然后,当用户在列表视图中选择某项时,仅显示已加载的视图。这可能是最简单的方法,但是由于您始终将所有视图都保留在内存中,因此会在内存上花费更多。
假设您有一个模型,看起来可能像这样:
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 ; }}and
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...}(and similarly for
Preferences,
Appearance, 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); } }}(显然,您可以通过定义一个
简单的视图类来封装列表视图的处理程序,该类封装了资源名称,显示名称和
控制器的工厂,并用它填充列表视图,而不用
打开字符串。)
应当指出,由于要设置在控制器FXMLLoader中的代码,
UserInfo.fxml,Preferences.fxml并且Appearance.fxml应该不会有一个
fx:controller定义的属性。
第二种选择只是对此的适度重构。
一次创建控制器并对其进行引用。请注意,如果需要,您可以摆脱
此版本中的模型,因为控制器具有数据,因此您可以
参考它们。所以这看起来像
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); }}and then
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文件时不会创建新的控制器,并且控制器中的initialize方法会
使用已经存在的数据来更新控件。(请注意以哪种方式bindBidirectional
调用方法。)
第三个选项可以在主控制器中或在主fxml文件中实现。要在控制器中实现它,基本上
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); } } }}请注意,此处您将恢复为fx:controllerFXML文件中的常规属性。
这将起作用,因为您仅加载一次FXML文件,因此视图仅保留其所有状态。
如果要通过这种方法在FXML中定义视图,则可以:
主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注入的规则
,并将包含的那些文件的控制器(“嵌套控制器”)注入到名称为when的字段”Controller”中。的fx:id(例如,到userInfoController)。因此,这的主控制器现在看起来像
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); } } }}


