有没有一种有效的方法可以在 JavaFX 中跨多个阶段刷新多个表视图?
Is there an effecient way to refresh multiple TableViews across multiple Stages in JavaFX?
具体来说,我有一个 Primary Stage 和在选择菜单项时从 Primary Stage 初始化的另外两个 Stage。
所有三个阶段都包含 TableView,它们显示我的数据的不同视图,并允许进行相关操作。当用户在这些阶段之一工作并执行更改数据的操作时,我希望更改反映在所有三个 TableView 中。
每个 TableView 都由 ObservableArrayList 支持。它们会在添加或删除元素时自动更新,但我必须在数据以任何其他方式发生变化且我希望它显示时随时调用 TableView.refresh() 方法。
从阅读其他帖子来看,似乎可以将父控制器对象的引用传递给子控制器,但这不是好的做法。我突然想到,也许我可以创建一个新的 class 来负责刷新所有 3 个阶段中的表,但是这需要以某种方式获取对每个控制器对象的引用。
我被卡住了,如果有任何建议,我将不胜感激!
在尝试创建一个最小的可重现示例时,我发现我做错了什么:
在我的原始代码中,我将简单双精度属性转换为简单字符串属性,然后再将它们显示在 table 中,以控制它们的显示方式。转换是在 Column.setCellValueFactory() 的覆盖 Call() 方法中执行的。这种转换不知何故导致 table 无法立即响应数据更改。
下面是一些代码来说明我在说什么:
public class Controller {
@FXML
public TableView<Person> mainTable;
@FXML
public Button editButton;
@FXML
public BorderPane mainBorderPane;
public Button openSecondButton;
public Button refreshButton;
public void initialize(){
DataModel.getInstance().addPerson(new Person("Frank", 1, 20));
DataModel.getInstance().addPerson(new Person("Cindy", 2, 20));
DataModel.getInstance().addPerson(new Person("Eric", 3, 67));
mainTable.setItems(DataModel.getInstance().getPeople());
TableColumn<Person, String> nameColumn = new TableColumn<>("Name");
nameColumn.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<Person, String>, ObservableValue<String>>(){
@Override
public ObservableValue<String> call(TableColumn.CellDataFeatures<Person, String> c){
return c.getValue().nameProperty();
}
});
TableColumn<Person, Integer> idColumn = new TableColumn<>("Id");
idColumn.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<Person, Integer>, ObservableValue<Integer>>() {
@Override
public ObservableValue<Integer> call(TableColumn.CellDataFeatures<Person, Integer> person) {
return person.getValue().idProperty().asObject();
}
});
TableColumn<Person, Integer> ageColumn = new TableColumn<>("Age");
ageColumn.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<Person, Integer>, ObservableValue<Integer>>() {
@Override
public ObservableValue<Integer> call(TableColumn.CellDataFeatures<Person, Integer> person) {
return person.getValue().ageProperty().asObject();
}
});
TableColumn<Person, String> ageStringColumn = new TableColumn<>("Age String");
ageStringColumn.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<Person, String>, ObservableValue<String>>() {
@Override
public ObservableValue<String> call(TableColumn.CellDataFeatures<Person, String> person) {
return new SimpleStringProperty(String.valueOf(person.getValue().getAge()));
}
});
mainTable.getColumns().addAll(nameColumn, idColumn, ageColumn, ageStringColumn);
}
@FXML
private void showSecondStage(ActionEvent actionEvent) throws IOException {
Stage secondStage = new Stage();
secondStage.setTitle("Secondary Stage");
secondStage.initModality(Modality.NONE);
secondStage.initStyle(StageStyle.UTILITY);
Parent parent = FXMLLoader.load(getClass().getResource("secondary.fxml"));
secondStage.setScene(new Scene(parent));
secondStage.initOwner(mainBorderPane.getScene().getWindow());
secondStage.show();
}
public boolean handleEditPersonRequest() {
Dialog<ButtonType> dialog = new Dialog<>();
dialog.initOwner(mainBorderPane.getScene().getWindow());
dialog.setTitle("Edit Person");
FXMLLoader fxmlLoader = new FXMLLoader();
fxmlLoader.setLocation(Controller.class.getResource("dialog.fxml"));
try {
dialog.getDialogPane().setContent(fxmlLoader.load());
} catch (IOException e) {
e.printStackTrace();
}
DialogController controller = fxmlLoader.getController();
controller.setFields(mainTable.getSelectionModel().getSelectedItem());
dialog.getDialogPane().getButtonTypes().add(ButtonType.OK);
dialog.getDialogPane().getButtonTypes().add(ButtonType.CANCEL);
Button okButton = (Button) dialog.getDialogPane().lookupButton(ButtonType.OK);
okButton.addEventFilter(ActionEvent.ACTION, event -> {
if (!controller.validateAndProcess()) {
event.consume();
System.out.println("Invalid entry, try again");
}});
Optional<ButtonType> result = dialog.showAndWait();
return result.isPresent() && result.get() == ButtonType.OK;
}
public void refreshTable(ActionEvent actionEvent) {
mainTable.refresh();
}
}
和 .fxml 文件
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.VBox?>
<BorderPane fx:id="mainBorderPane" fx:controller="sample.Controller"
xmlns:fx="http://javafx.com/fxml" >
<left>
<VBox>
<Button text="Edit Person" fx:id="editButton" onAction="#handleEditPersonRequest"/>
<Button text = "Open Second Window" fx:id="openSecondButton" onAction="#showSecondStage"/>
<Button text="Refresh table" fx:id="refreshButton" onAction="#refreshTable"/>
</VBox>
</left>
<center>
<TableView fx:id="mainTable" />
</center>
</BorderPane>
这是对话框控制器:
public class DialogController {
public TextField nameField;
public TextField idField;
public TextField ageField;
public Person person;
public void setFields(Person selectedPerson) {
person = selectedPerson;
nameField.setText(person.getName());
idField.setText(String.valueOf(person.getId()));
ageField.setText(String.valueOf(person.getAge()));
}
public boolean validateAndProcess(){
try{
String name = nameField.getText();
int id = Integer.parseInt(idField.getText());
int age = Integer.parseInt(ageField.getText());
person.setName(name);
person.setId(id);
person.setAge(age);
return true;
}catch (NumberFormatException | NullPointerException e){
e.printStackTrace();
return false;
}
}
}
而且是 .fxml 文件
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<VBox xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="sample.DialogController"
prefHeight="400.0" prefWidth="600.0">
<Label text="Name"/>
<TextField fx:id="nameField"/>
<Label text="Id"/>
<TextField fx:id="idField"/>
<Label text="Age"/>
<TextField fx:id="ageField"/>
</VBox>
我不打算包含第二个 window 的代码,因为不需要它来查看问题。
具体来说,我有一个 Primary Stage 和在选择菜单项时从 Primary Stage 初始化的另外两个 Stage。
所有三个阶段都包含 TableView,它们显示我的数据的不同视图,并允许进行相关操作。当用户在这些阶段之一工作并执行更改数据的操作时,我希望更改反映在所有三个 TableView 中。
每个 TableView 都由 ObservableArrayList 支持。它们会在添加或删除元素时自动更新,但我必须在数据以任何其他方式发生变化且我希望它显示时随时调用 TableView.refresh() 方法。
从阅读其他帖子来看,似乎可以将父控制器对象的引用传递给子控制器,但这不是好的做法。我突然想到,也许我可以创建一个新的 class 来负责刷新所有 3 个阶段中的表,但是这需要以某种方式获取对每个控制器对象的引用。
我被卡住了,如果有任何建议,我将不胜感激!
在尝试创建一个最小的可重现示例时,我发现我做错了什么:
在我的原始代码中,我将简单双精度属性转换为简单字符串属性,然后再将它们显示在 table 中,以控制它们的显示方式。转换是在 Column.setCellValueFactory() 的覆盖 Call() 方法中执行的。这种转换不知何故导致 table 无法立即响应数据更改。 下面是一些代码来说明我在说什么:
public class Controller {
@FXML
public TableView<Person> mainTable;
@FXML
public Button editButton;
@FXML
public BorderPane mainBorderPane;
public Button openSecondButton;
public Button refreshButton;
public void initialize(){
DataModel.getInstance().addPerson(new Person("Frank", 1, 20));
DataModel.getInstance().addPerson(new Person("Cindy", 2, 20));
DataModel.getInstance().addPerson(new Person("Eric", 3, 67));
mainTable.setItems(DataModel.getInstance().getPeople());
TableColumn<Person, String> nameColumn = new TableColumn<>("Name");
nameColumn.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<Person, String>, ObservableValue<String>>(){
@Override
public ObservableValue<String> call(TableColumn.CellDataFeatures<Person, String> c){
return c.getValue().nameProperty();
}
});
TableColumn<Person, Integer> idColumn = new TableColumn<>("Id");
idColumn.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<Person, Integer>, ObservableValue<Integer>>() {
@Override
public ObservableValue<Integer> call(TableColumn.CellDataFeatures<Person, Integer> person) {
return person.getValue().idProperty().asObject();
}
});
TableColumn<Person, Integer> ageColumn = new TableColumn<>("Age");
ageColumn.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<Person, Integer>, ObservableValue<Integer>>() {
@Override
public ObservableValue<Integer> call(TableColumn.CellDataFeatures<Person, Integer> person) {
return person.getValue().ageProperty().asObject();
}
});
TableColumn<Person, String> ageStringColumn = new TableColumn<>("Age String");
ageStringColumn.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<Person, String>, ObservableValue<String>>() {
@Override
public ObservableValue<String> call(TableColumn.CellDataFeatures<Person, String> person) {
return new SimpleStringProperty(String.valueOf(person.getValue().getAge()));
}
});
mainTable.getColumns().addAll(nameColumn, idColumn, ageColumn, ageStringColumn);
}
@FXML
private void showSecondStage(ActionEvent actionEvent) throws IOException {
Stage secondStage = new Stage();
secondStage.setTitle("Secondary Stage");
secondStage.initModality(Modality.NONE);
secondStage.initStyle(StageStyle.UTILITY);
Parent parent = FXMLLoader.load(getClass().getResource("secondary.fxml"));
secondStage.setScene(new Scene(parent));
secondStage.initOwner(mainBorderPane.getScene().getWindow());
secondStage.show();
}
public boolean handleEditPersonRequest() {
Dialog<ButtonType> dialog = new Dialog<>();
dialog.initOwner(mainBorderPane.getScene().getWindow());
dialog.setTitle("Edit Person");
FXMLLoader fxmlLoader = new FXMLLoader();
fxmlLoader.setLocation(Controller.class.getResource("dialog.fxml"));
try {
dialog.getDialogPane().setContent(fxmlLoader.load());
} catch (IOException e) {
e.printStackTrace();
}
DialogController controller = fxmlLoader.getController();
controller.setFields(mainTable.getSelectionModel().getSelectedItem());
dialog.getDialogPane().getButtonTypes().add(ButtonType.OK);
dialog.getDialogPane().getButtonTypes().add(ButtonType.CANCEL);
Button okButton = (Button) dialog.getDialogPane().lookupButton(ButtonType.OK);
okButton.addEventFilter(ActionEvent.ACTION, event -> {
if (!controller.validateAndProcess()) {
event.consume();
System.out.println("Invalid entry, try again");
}});
Optional<ButtonType> result = dialog.showAndWait();
return result.isPresent() && result.get() == ButtonType.OK;
}
public void refreshTable(ActionEvent actionEvent) {
mainTable.refresh();
}
}
和 .fxml 文件
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.VBox?>
<BorderPane fx:id="mainBorderPane" fx:controller="sample.Controller"
xmlns:fx="http://javafx.com/fxml" >
<left>
<VBox>
<Button text="Edit Person" fx:id="editButton" onAction="#handleEditPersonRequest"/>
<Button text = "Open Second Window" fx:id="openSecondButton" onAction="#showSecondStage"/>
<Button text="Refresh table" fx:id="refreshButton" onAction="#refreshTable"/>
</VBox>
</left>
<center>
<TableView fx:id="mainTable" />
</center>
</BorderPane>
这是对话框控制器:
public class DialogController {
public TextField nameField;
public TextField idField;
public TextField ageField;
public Person person;
public void setFields(Person selectedPerson) {
person = selectedPerson;
nameField.setText(person.getName());
idField.setText(String.valueOf(person.getId()));
ageField.setText(String.valueOf(person.getAge()));
}
public boolean validateAndProcess(){
try{
String name = nameField.getText();
int id = Integer.parseInt(idField.getText());
int age = Integer.parseInt(ageField.getText());
person.setName(name);
person.setId(id);
person.setAge(age);
return true;
}catch (NumberFormatException | NullPointerException e){
e.printStackTrace();
return false;
}
}
}
而且是 .fxml 文件
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<VBox xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="sample.DialogController"
prefHeight="400.0" prefWidth="600.0">
<Label text="Name"/>
<TextField fx:id="nameField"/>
<Label text="Id"/>
<TextField fx:id="idField"/>
<Label text="Age"/>
<TextField fx:id="ageField"/>
</VBox>
我不打算包含第二个 window 的代码,因为不需要它来查看问题。