JavaFX页面切换时如何保留数组与对象中的已保存数据?
问题原因
- 控制器实例重复创建:每次切换页面后返回,FXMLLoader会重新加载FXML文件并创建新的
ListProdukScreen控制器对象。之前添加的数据(如datalist、daftarKategori)属于旧控制器实例,新实例的所有数据会回到初始状态,导致数据看起来被重置。 - 数据存储在控制器内部:商品列表和分类数据都是
ListProdukScreen的成员变量,每个控制器实例都有独立的副本,没有全局共享的数据源。
解决方案
方案一:全局单例数据源(推荐小型应用使用)
创建全局数据管理类,用单例模式确保整个应用只有一份数据源,所有页面共享这套数据。
1. 创建DataManager类
package com.finalalp; import javafx.collections.FXCollections; import javafx.collections.ObservableList; public class DataManager { // 全局唯一实例 private static final DataManager INSTANCE = new DataManager(); // 共享的商品列表 private ObservableList<Produk> produkList = FXCollections.observableArrayList(); // 共享的分类数据 private Kategori daftarKategori = new Kategori(null); // 私有构造,禁止外部实例化 private DataManager() { // 初始化默认分类 daftarKategori.makanan = new String[]{ "1. Nama kategori", "2. Nama kategori", "3. Nama kategori", "4. Nama kategori", "5. Nama kategori" }; } // 获取单例实例 public static DataManager getInstance() { return INSTANCE; } // 获取商品列表 public ObservableList<Produk> getProdukList() { return produkList; } // 获取分类数据 public Kategori getDaftarKategori() { return daftarKategori; } }
2. 修改ListProdukScreen控制器
package com.finalalp; import java.io.IOException; import java.net.URL; import java.util.ResourceBundle; import javafx.beans.Observable; import javafx.collections.transformation.FilteredList; import javafx.collections.transformation.SortedList; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.scene.control.ChoiceBox; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.control.TextField; import javafx.scene.control.cell.PropertyValueFactory; import javafx.scene.input.MouseEvent; public class ListProdukScreen implements Initializable { // 从全局数据管理器获取数据源,不再自行创建 private Kategori daftarKategori = DataManager.getInstance().getDaftarKategori(); private ObservableList<Produk> datalist = DataManager.getInstance().getProdukList(); @FXML void switchToHomeScreen(ActionEvent event) throws IOException { App.setRoot("homeScreen"); } @FXML private ChoiceBox<String> addListKategori; @FXML private ChoiceBox<String> listKategori; @FXML private ChoiceBox<String> listPilihKategori; @FXML private TableView<Produk> tableListProduk; @FXML private TableColumn<Produk, String> deskripsi; @FXML private TableColumn<Produk, Number> harga; @FXML private TableColumn<Produk, String> nama; @FXML private TableColumn<Produk, String> kategoriTable; @FXML private TextField namaKategoriBaru; @FXML private TextField namaProduk; @FXML private TextField deskripsiProduk; @FXML private TextField hargaProduk; @FXML private TextField searchBarProduk; @FXML void tambahkanProduk(ActionEvent event) { // 构造方法已完成赋值,无需重复调用set Produk produkAdded = new Produk( namaProduk.getText(), deskripsiProduk.getText(), listPilihKategori.getValue(), Integer.parseInt(hargaProduk.getText()) ); datalist.add(produkAdded); // 表格已绑定到过滤后的列表,无需手动添加 namaProduk.clear(); deskripsiProduk.clear(); hargaProduk.clear(); } @FXML void pilihListKategori(MouseEvent event) { searchBarProduk.setText(listKategori.getValue()); } @FXML void filterPencarian(MouseEvent event) { FilteredList<Produk> filteredListdata = new FilteredList<>(datalist, b -> true); searchBarProduk.textProperty().addListener((Observable, oldValue, newValue) -> { filteredListdata.setPredicate(produk -> { if (newValue == null || newValue.isBlank()) { return true; } String lowerCaseFilter = newValue.toLowerCase(); return produk.getKategori().toLowerCase().contains(lowerCaseFilter) || produk.getNama().toLowerCase().contains(lowerCaseFilter); }); }); SortedList<Produk> sortedData = new SortedList<>(filteredListdata); sortedData.comparatorProperty().bind(tableListProduk.comparatorProperty()); tableListProduk.setItems(sortedData); } @Override public void initialize(URL arg0, ResourceBundle arg1) { // 初始化所有下拉框的分类数据 addListKategori.getItems().setAll(daftarKategori.makanan); listKategori.getItems().setAll(daftarKategori.makanan); listPilihKategori.getItems().setAll(daftarKategori.makanan); // 表格列绑定 nama.setCellValueFactory(new PropertyValueFactory<>("nama")); harga.setCellValueFactory(new PropertyValueFactory<>("harga")); deskripsi.setCellValueFactory(new PropertyValueFactory<>("deskripsi")); kategoriTable.setCellValueFactory(new PropertyValueFactory<>("kategori")); // 初始化表格数据 filterPencarian(null); } public void submitKategoriBaru(ActionEvent event) { String namaKategoString = addListKategori.getValue(); switch (namaKategoString) { case "1. Nama kategori": daftarKategori.makanan[0] = "1. " + namaKategoriBaru.getText(); break; case "2. Nama kategori": daftarKategori.makanan[1] = "2. " + namaKategoriBaru.getText(); break; case "3. Nama kategori": daftarKategori.makanan[2] = "3. " + namaKategoriBaru.getText(); break; case "4. Nama kategori": daftarKategori.makanan[3] = "4. " + namaKategoriBaru.getText(); break; case "5. Nama kategori": daftarKategori.makanan[4] = "5. " + namaKategoriBaru.getText(); break; default: break; } // 更新所有下拉框的分类内容 addListKategori.getItems().setAll(daftarKategori.makanan); listKategori.getItems().setAll(daftarKategori.makanan); listPilihKategori.getItems().setAll(daftarKategori.makanan); namaKategoriBaru.clear(); } }
方案二:复用控制器实例
如果不想用单例,可以在App类中缓存已加载的FXML和控制器实例,切换页面时直接复用,避免重复创建:
// 修改App类的setRoot方法 private static Map<String, Parent> cachedRoots = new HashMap<>(); public static void setRoot(String fxml) throws IOException { if (cachedRoots.containsKey(fxml)) { root = cachedRoots.get(fxml); } else { FXMLLoader loader = new FXMLLoader(App.class.getResource(fxml + ".fxml")); root = loader.load(); cachedRoots.put(fxml, root); } scene.setRoot(root); }
注意:这种方式适合页面较少的场景,若页面依赖外部状态变化,需要额外处理数据同步。
方案三:持久化数据(可选)
如果需要关闭应用后仍保留数据,可以将数据序列化到文件(如JSON、XML)或存入数据库:
- 用Jackson库将
ObservableList<Produk>和Kategori序列化为JSON文件 - 在应用启动时读取文件加载数据,数据变更时写入文件
内容的提问来源于stack exchange,提问作者Alizaenal Abidin
相关产品推荐
相关产品推荐

