当前位置: 首页 > 知识库问答 >
问题:

是否可以重新加载相同的fxml/controller实例?

楚丰羽
2023-03-14

我有一个用于总体设置窗口的主窗口,其中包含一个listview和所有设置类别。窗口的右侧有一个Anchorpane,用于在从列表中选择每个类别时为每个类别加载单独的FXML文件。

当一个用户选择一个类别,我需要他们能够编辑右边的设置,切换到另一个类别,并作出更多的改变。然而,如果他们回到第一类,那里所做的改变会持续下去。

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

研究:

我找到的唯一解决这个问题的答案是如何在不重新加载FXML文件的情况下使用javafx应用程序控制器?。它提到了对FXML控制器使用单例,但没有解决每次都重新加载FXML文件本身的问题。

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

共有1个答案

怀德馨
2023-03-14

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

  1. 定义一个表示数据的模型(设置),并创建它的单个实例。每次重新加载FXML文件,并将单个实例传递给控制器。将UI中的数据与模型中的数据绑定。这样,当您重新加载FXML时,它将使用相同的数据进行更新。(这是我的首选选项。)
  2. 只创建一次控制器。每次重新加载FXML文件,每次设置相同的控制器。让initialize()方法从本地存储的字段或模型更新UI。重新加载FXML文件时,@FXML-注释字段将被替换,并调用initialize()方法,用现有数据更新新控件。(这感觉有点做作。从道义上讲,任何名为initialize()的方法只应该执行一次。但是,这是完全可行的。)
  3. 加载每个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 ;
    }
}

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...
}

(对于首选项外观等类似)

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());
    }
}
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);
        }
    }
}
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文件重新加载时,您不创建新的控制器,控制器中的initialize方法使用已经存在的数据更新控件。(请注意调用bindbiredicion方法的方式。)

第三个选项可以在主控制器中实现,也可以在主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);
            }
        }
    }
}
<!-- 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-injection规则是,用指定的fx:id(例如UserInfo)注入所包含的FMXL文件的根(例如UserInfo),当“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);
            }
        }
    }
}
 类似资料:
  • 问题内容: 目标:实施标准的“设置” GUI窗口。类别位于 ListView左侧,而相应选项位于Pane右侧。 (请忽略具有重复类别的明显错误;仍在处理) 在此处输入图片说明 我有一个用于整体“设置”窗口的主窗口,其中包含 ListView带有所有类别的设置。窗口的右侧 具有AnchorPane, 当从列表中选择一个类别时,用于为每个类别加载单独的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)是,您可以加载新脚

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