当前位置: 首页 > 面试题库 >

是否可以重新加载相同的FXML / Controller实例?

阙新觉
2023-03-14
问题内容

目标:实施标准的“设置” GUI窗口。类别位于
ListView左侧,而相应选项位于Pane右侧。 (请忽略具有重复类别的明显错误;仍在处理)
在此处输入图片说明

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

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

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

因此,可以重用已经加载和更改的FXML文件吗?

研究:

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

如果有人可以指出这种“
设置”菜单的基本示例,我将非常高兴。


问题答案:

我基本上可以通过三种方式执行此操作:

定义一个代表数据(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注入的规则 是,将包含的FMXL文件的根目录注入指定的fx:id(例如userInfo)
,并将包含的那些文件的控制器(“嵌套控制器”)注入到名称为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);
            }
        }
    }
}


 类似资料:
  • 我有一个用于总体设置窗口的主窗口,其中包含一个和所有设置类别。窗口的右侧有一个,用于在从列表中选择每个类别时为每个类别加载单独的FXML文件。 当一个用户选择一个类别,我需要他们能够编辑右边的设置,切换到另一个类别,并作出更多的改变。然而,如果他们回到第一类,那里所做的改变会持续下去。 我的明显问题是,每次用户更改类别时,都会重新加载FXML文件和控制器,将其中的所有控件重置为它们的默认值。 研究

  • 我已经为一个ScreenController类(一个由每个屏幕的单个控制器类扩展的类)创建了一个解决方案,它将处理我的应用程序中的基线屏幕层次结构。 在我的类中,我使用一个函数将另一个FXML文件的内容(加载)添加到当前控制器的当前主播上。 我的问题是: 1) 加载新的FXML时,FXML使用的类(或者更确切地说,特定的控制器)是否也被实例化/加载? 2) 执行此操作时,如果新FXMl的类被实例化

  • 问题内容: 我的.routing.ts文件中有这个 我的文件检查id参数并相应地加载数据。在路由器的早期版本中,如果我从/ page / 4转到/ page / 25,则该页面将“重新加载”并且组件将更新。 现在,当我尝试导航到/ page / X时,其中X是id,它只会第一次加载,然后url会更改,但是组件不会再次“重新加载”。 是否需要传递某些内容以强制重新加载组件并调用ngOnInit事件?

  • 问题内容: 设法获得logstash(1.3.1)以将数据发送到elasticsearch(0.9.5)。 我的logstash conf文件设置是 数据存储在ES中的索引logstash-2013.12.xx下 但是,如果我重新启动logstash,请说第二天-将相同的数据重新加载到新索引中。即使我再次重新启动,索引中的文档计数也会加倍。 好像logstash重新读取数据,ES也在复制文档。 有

  • 问题内容: 我正在建立一个新的由AJAX驱动的网站,其中包含不同的部分。每个部分都需要一组新的Javascript函数才能运行。我宁愿不要一开始就加载每个脚本,因为可能会有很多脚本。 有没有一种方法可以使用AJAX加载新脚本并删除旧脚本(以确保类似的变量名或函数签名不存在兼容性问题)。 谢谢 编辑 -jQuery很好,它不必是老式的Javascript 问题答案: 三件事: 1)是,您可以加载新脚

  • 我已经创建了一个按钮数组(),我想为数组中的所有按钮添加相同的操作侦听器,而不是逐个添加它们。 想象一下,每次我点击屏幕上显示的一个按钮,它就会在数组中打印出该按钮的索引。