有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

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

目标:实现标准的“设置”GUI窗口。左侧ListView中的类别和右侧Pane中的相应选项。 enter image description here (请忽略重复分类的明显错误;仍在处理中)

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

当用户选择一个类别时,我需要他们能够编辑右侧的设置,切换到另一个类别并进行更多更改。然而,如果他们回到第一类,那里发生的变化就会持续下去

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

那么,是否有可能重用已经加载和修改过的FXML文件呢

研究:

我发现唯一能解决这个问题的答案是How to swich javafx application controller without reloading FXML file?。这提到了对FXML控制器使用单例,但没有解决每次重新加载FXML文件本身的问题

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


共 (1) 个答案

  1. # 1 楼答案

    这里是一个完全不同的方法来创建一个“导航窗格”,就像你展示的那个,部分灵感来自Hypnic Jerk的答案。这里的关键观察是,您想要的功能基本上与TabPane相同:您有一系列节点,一次显示一个,并有一个机制来选择显示哪一个(通常是选项卡,但这里有一个ListView)。因此,这种方法只是让选项卡窗格使用ListView而不是通常的选项卡来显示“选择器”。它通过为选项卡窗格创建一个新的Skin来实现这一点

    以下是基本应用程序:

    <?xml version="1.0" encoding="UTF-8"?>
    
    <?import javafx.scene.layout.BorderPane?>
    <?import javafx.scene.control.TabPane?>
    <?import javafx.scene.control.Tab?>
    
    <BorderPane xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.MainController">
        <center>
            <TabPane  fx:id="navigationPane" id="navigationPane">
                <tabs>
                    <Tab text="User Information">
                        <content>
                            <fx:include fx:id="userInfo" source="UserInfo.fxml"/>
                        </content>
                    </Tab>
                    <Tab text="Preferences">
                        <content>
                            <fx:include fx:id="prefs" source="Preferences.fxml"/>
                        </content>
                    </Tab>
                    <Tab text="Appearance">
                        <content>
                            <fx:include fx:id="appearance" source="Appearance.fxml"/>
                        </content>
                    </Tab>
                </tabs>
            </TabPane>
        </center>
    </BorderPane>
    

    此测试的控制器不执行任何操作:

    package application;
    
    import javafx.fxml.FXML;
    import javafx.scene.control.TabPane;
    
    public class MainController {
    
        @FXML
        private TabPane navigationPane ;
    
        public void initialize() {
    
        }
    }
    

    各个窗格只是占位符:

    <?xml version="1.0" encoding="UTF-8"?>
    
    <?import javafx.scene.layout.VBox?>
    <?import javafx.scene.control.Label?>
    <?import javafx.scene.control.TextField?>
    
    <VBox xmlns:fx="http://javafx.com/fxml/1" minWidth="600" minHeight="400" alignment="CENTER">
        <Label text="User Info Pane"/>
        <TextField  />
    </VBox>
    

    application类只加载FXML,关键是设置一个样式表:

    package application;
    
    import java.io.IOException;
    
    import javafx.application.Application;
    import javafx.fxml.FXMLLoader;
    import javafx.scene.Parent;
    import javafx.scene.Scene;
    import javafx.stage.Stage;
    
    public class NavTabPaneTest extends Application {
    
        @Override
        public void start(Stage primaryStage) throws IOException {
    
            Parent root = FXMLLoader.load(getClass().getResource("NavPaneTest.fxml"));
    
            Scene scene = new Scene(root);
            scene.getStylesheets().add(getClass().getResource("style.css").toExternalForm());
            primaryStage.setScene(scene);
            primaryStage.show();
        }
    
        public static void main(String[] args) {
            launch(args);
        }
    }
    

    样式表指定了皮肤:

    #navigationPane {
        -fx-skin: "application.skin.NavigationSkin" ;
    }
    

    最后是起作用的部分:皮肤:

    package application.skin;
    
    import java.util.function.ToDoubleFunction;
    
    import javafx.beans.binding.Bindings;
    import javafx.collections.ListChangeListener.Change;
    import javafx.scene.Node;
    import javafx.scene.control.ListCell;
    import javafx.scene.control.ListView;
    import javafx.scene.control.SkinBase;
    import javafx.scene.control.Tab;
    import javafx.scene.control.TabPane;
    
    public class NavigationSkin extends SkinBase<TabPane> {
    
    
        private final ListView<Tab> navigator ;
    
        public NavigationSkin(TabPane control) {
            super(control);
    
            navigator = new ListView<Tab>();
    
            navigator.setCellFactory(lv -> {
                ListCell<Tab> cell = new ListCell<>();
                cell.itemProperty().addListener((obs, oldTab, newTab) -> {
                    cell.textProperty().unbind();
                    cell.graphicProperty().unbind();
                    if (newTab == null) {
                        cell.setText(null);
                        cell.setGraphic(null);
                    } else {
                        cell.textProperty().bind(newTab.textProperty());
                        cell.graphicProperty().bind(newTab.graphicProperty());
                    }
                });
                return cell ;
            });
    
            navigator.setItems(control.getTabs());  
    
            navigator.getSelectionModel().selectedItemProperty().addListener(
                    (obs, oldItem, newItem) -> control.getSelectionModel().select(newItem));
    
            navigator.getSelectionModel().select(control.getSelectionModel().getSelectedItem());
    
            control.getSelectionModel().selectedItemProperty().addListener((obs, oldItem, newItem) -> {
                for (Tab t : control.getTabs()) {
                    t.getContent().setVisible(t == control.getSelectionModel().getSelectedItem());
                }
                navigator.getSelectionModel().select(newItem);
            });
    
            getChildren().add(navigator);
            for (Tab t : control.getTabs()) {
                getChildren().add(t.getContent());
                t.getContent().setVisible(t == control.getSelectionModel().getSelectedItem());
            }
    
    
            control.getTabs().addListener((Change<? extends Tab> c) -> {
                while (c.next()) {
                    if (c.wasRemoved()) {
                        getChildren().subList(c.getFrom()+1, c.getFrom()+c.getRemovedSize()+1).clear();
                    }
                    if (c.wasAdded()) {
                        for (int i = 0 ; i < c.getAddedSize() ; i++) {
                            getChildren().add(c.getFrom() + i + 1, c.getAddedSubList().get(i).getContent());
                        }
                    }
                }
                getSkinnable().requestLayout();
            });
        }
    
    
        @Override
        protected void layoutChildren(double contentX, double contentY, double contentWidth, double contentHeight) {
            double navPrefWidth = navigator.prefWidth(-1);
            navigator.resizeRelocate(contentX, contentY, navPrefWidth, contentHeight);
            for (Tab t : getSkinnable().getTabs()) {
                t.getContent().resizeRelocate(navPrefWidth, 0, contentWidth - navPrefWidth, contentHeight);
            }
        }
    
        @Override
        protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
            return computeHeight(n -> n.maxHeight(width - leftInset - rightInset));
        }
    
        @Override
        protected double computeMaxWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
            return computeWidth(n -> n.maxWidth(height - topInset - bottomInset)) ;
        }
    
        @Override
        protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
            return computeHeight(n -> n.minHeight(-1));
        }
    
        @Override
        protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
            return computeWidth(n -> n.minWidth(-1)) ;
        }   
    
        @Override
        protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
            return computeHeight(n -> n.prefHeight(-1));
        }
    
        @Override
        protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
            return computeWidth(n -> n.prefWidth(height - topInset - bottomInset)) ;
        }
    
    
        private double computeWidth(ToDoubleFunction<Node> width) {
            double navWidth = width.applyAsDouble(navigator);
            double max = 0 ;
            for (Tab tab : getSkinnable().getTabs()) {
                double tabWidth = width.applyAsDouble(tab.getContent());
                max = Math.max(max, tabWidth);
            }
            return navWidth + max ;
        }
    
        private double computeHeight(ToDoubleFunction<Node> height) {
            double max = height.applyAsDouble(navigator) ;
            for (Tab tab : getSkinnable().getTabs()) {
                max = Math.max(max, height.applyAsDouble(tab.getContent()));
            }
            return max ;
        }
    }
    

    这将创建一个ListView,并对侦听器和绑定进行一些处理,以确保它始终与选项卡窗格中的选项卡列表具有相同的内容,并且列表视图中的选定项就是选定选项卡。(如果以编程方式更改了选定选项卡,则需要确保列表视图会更新,如果用户更改了列表视图中的选定项,则需要确保选定选项卡会更改。)其余的只覆盖layoutChildren()方法和计算最小/最大/预大小的各种方法

    结果是一个传统的“导航窗格”:

    enter image description here

    当然,由于所有选项卡内容都只加载一次,并且只是在视图中切换和切换(通过更改其可见性),因此恢复到上一个视图时丢失数据的问题消失了