如何向 TableView 动态添加列并更新其内容
How to dynamically add columns to TableView and update their content
我有以下表格视图:
每一行代表一个成分,它有两个属性,一个名称和一个营养类型的数组列表,其中包含构成该成分的营养素(名称和重量)。
我的目标是通过点击相应的单元格并输入新值(相同Name column).
的行为
我怀疑我需要为每一列设置一个自定义的 CellValueFactory 和一个 CellFactory,但我是 JavaFx 的新手,我不知道该怎么做。
这是 控制器 class 大多数魔术(应该)发生的地方:
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.control.cell.TextFieldTableCell;
import java.net.URL;
import java.util.ArrayList;
import java.util.ResourceBundle;
public class Controller implements Initializable {
@FXML private TableView<Ingredient> tableView;
@FXML private TableColumn<Ingredient, String> nameColumn;
private final ObservableList<Ingredient> ingredientsList = FXCollections.observableArrayList();
private final ArrayList<Nutrient> nutrientsList = new ArrayList<>();
@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
// add ingredients and nutrients
ingredientsList.add(new Ingredient("ingredient 1"));
ingredientsList.add(new Ingredient("ingredient 2"));
ingredientsList.add(new Ingredient("ingredient 3"));
nutrientsList.add(new Nutrient("proteins", 0.0));
nutrientsList.add(new Nutrient("fats", 0.0));
nutrientsList.add(new Nutrient("carbs", 0.0));
// add the list of nutrients to each ingredient
ingredientsList.forEach(nutrient -> nutrient.setNutrients(nutrientsList));
ingredientsList.forEach(System.out::println);
//add ingredients (rows) to the table
tableView.setItems(ingredientsList);
// make name column editable
tableView.setEditable(true);
nameColumn.setCellFactory(TextFieldTableCell.forTableColumn());
nameColumn.setCellValueFactory(new PropertyValueFactory<Ingredient, String>("name"));
for (int i = 0; i < nutrientsList.size(); i++) {
// add a column for each ingredient
TableColumn<Ingredient, Nutrient> column = new TableColumn<>(nutrientsList.get(i).getName());
tableView.getColumns().add(column);
Ingredient selectedIngredient = tableView.getSelectionModel().getSelectedItem();
int currentNutrientIndex = i;
column.setOnEditCommit(new EventHandler<TableColumn.CellEditEvent<Ingredient, Nutrient>>() {
@Override
public void handle(TableColumn.CellEditEvent<Ingredient, Nutrient> ingredientNutrientCellEditEvent) {
selectedIngredient.updateNutrientValue(currentNutrientIndex, ingredientNutrientCellEditEvent.getNewValue().getWeight());
}
});
// column.setCellFactory and column.setCellValueFactory here???
}
}
/**
* This method allows the user to double click on an cell and update the ingredient name.
* It is linked to the UI by setting the attribute onEditCommit of the Column the the name of the method in the fxml file
*/
public void changeIngredientNameCellEvent(TableColumn.CellEditEvent editedCell){
Ingredient selectedIngredient = tableView.getSelectionModel().getSelectedItem();
selectedIngredient.setName(editedCell.getNewValue().toString());
ingredientsList.forEach(System.out::println);
}
}
sample.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<TableView fx:id="tableView" prefHeight="200.0" prefWidth="200.0" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Controller">
<columns>
<TableColumn fx:id="nameColumn" onEditCommit="#changeIngredientNameCellEvent" prefWidth="75.0" text="Name" />
</columns>
</TableView>
成分class:
package sample;
import javafx.beans.property.SimpleStringProperty;
import java.util.ArrayList;
public class Ingredient {
private SimpleStringProperty name;
private ArrayList<Nutrient> nutrients;
public Ingredient(String name) {
this.name = new SimpleStringProperty(name);
}
public void setNutrients(ArrayList<Nutrient> list){
this.nutrients = list;
}
public void updateNutrientValue(int nutrientIndex, double newValue){
nutrients.get(nutrientIndex).setWeight(newValue);
}
public Nutrient getNutrientAt(int index){
return nutrients.get(index);
}
public String getName() {
return name.get();
}
public SimpleStringProperty nameProperty() {
return name;
}
public void setName(String name) {
this.name.set(name);
}
public String toString(){
return "name = '" + name.get() + "' nutrients = " + nutrients;
}
}
营养class:
package sample;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleStringProperty;
public class Nutrient {
private final SimpleStringProperty name;
private final SimpleDoubleProperty weight;
public Nutrient(String name, double weight) {
this.name = new SimpleStringProperty(name);
this.weight = new SimpleDoubleProperty(weight);
}
public String getName() {
return name.get();
}
public SimpleStringProperty nameProperty() {
return name;
}
public void setName(String name) {
this.name.set(name);
}
public double getWeight() {
return weight.get();
}
public SimpleDoubleProperty weightProperty() {
return weight;
}
public void setWeight(double weight) {
this.weight.set(weight);
}
public String toString(){
return name.get() + ", " + weight.get() ;
}
}
主要class:
package sample;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
@Override
public void start(Stage primaryStage) throws Exception{
Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
primaryStage.setScene(new Scene(root, 400, 300));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
你可以做到
for (int i = 0; i < nutrientsList.size(); i++) {
// add a column for each ingredient
TableColumn<Ingredient, Double> column = new TableColumn<>(nutrientsList.get(i).getName());
tableView.getColumns().add(column);
int currentNutrientIndex = i;
// column.setCellFactory and column.setCellValueFactory here???
column.setCellValueFactory(data ->
data.getValue().getNutrientAt(currentNutrientIndex).weightProperty().asObject());
column.setCellFactory(tc -> new TextFieldTableCell<>(new DoubleStringConverter()));
}
您可能更愿意提供自定义 StringConverter
来代替 DoubleStringConverter
以提供例如本地化解析等
请注意,您的设置将无法正常工作,但我认为这只是示例代码。您不能对不同的成分使用相同的 Nutrient
列表(或者实际上是相同的 Nutrient
实例,即使在不同的列表中)。您需要为每个 Ingredient
.
创建新实例
可以使用
制作一个完整的示例
private final ObservableList<Ingredient> ingredientsList = FXCollections.observableArrayList();
@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
// add ingredients and nutrients
ingredientsList.add(new Ingredient("ingredient 1"));
ingredientsList.add(new Ingredient("ingredient 2"));
ingredientsList.add(new Ingredient("ingredient 3"));
List<String> nutrientNames = List.of("proteins", "fats", "carbs");
// add a list of nutrients to each ingredient
ingredientsList.forEach(ingredient -> ingredient.setNutrients(
nutrientNames.stream()
.map(name -> new Nutrient(name, 0.0))
.collect(Collectors.toList())
)
);
ingredientsList.forEach(System.out::println);
//add ingredients (rows) to the table
tableView.setItems(ingredientsList);
// make name column editable
tableView.setEditable(true);
nameColumn.setCellFactory(TextFieldTableCell.forTableColumn());
nameColumn.setCellValueFactory(data -> data.getValue().nameProperty());
for (int i = 0; i < nutrientNames.size(); i++) {
// add a column for each ingredient
TableColumn<Ingredient, Double> column = new TableColumn<>(nutrientNames.get(i));
tableView.getColumns().add(column);
Ingredient selectedIngredient = tableView.getSelectionModel().getSelectedItem();
int currentNutrientIndex = i;
// column.setCellFactory and column.setCellValueFactory here???
column.setCellValueFactory(data ->
data.getValue().getNutrientAt(currentNutrientIndex).weightProperty().asObject());
column.setCellFactory(tc -> new TextFieldTableCell<>(new DoubleStringConverter()));
}
}
将 Ingredient.nutrients
的类型更改为 List<Nutrient>
(无论如何这是一个很好的做法)。
我有以下表格视图:
每一行代表一个成分,它有两个属性,一个名称和一个营养类型的数组列表,其中包含构成该成分的营养素(名称和重量)。
我的目标是通过点击相应的单元格并输入新值(相同Name column).
的行为我怀疑我需要为每一列设置一个自定义的 CellValueFactory 和一个 CellFactory,但我是 JavaFx 的新手,我不知道该怎么做。
这是 控制器 class 大多数魔术(应该)发生的地方:
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.control.cell.TextFieldTableCell;
import java.net.URL;
import java.util.ArrayList;
import java.util.ResourceBundle;
public class Controller implements Initializable {
@FXML private TableView<Ingredient> tableView;
@FXML private TableColumn<Ingredient, String> nameColumn;
private final ObservableList<Ingredient> ingredientsList = FXCollections.observableArrayList();
private final ArrayList<Nutrient> nutrientsList = new ArrayList<>();
@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
// add ingredients and nutrients
ingredientsList.add(new Ingredient("ingredient 1"));
ingredientsList.add(new Ingredient("ingredient 2"));
ingredientsList.add(new Ingredient("ingredient 3"));
nutrientsList.add(new Nutrient("proteins", 0.0));
nutrientsList.add(new Nutrient("fats", 0.0));
nutrientsList.add(new Nutrient("carbs", 0.0));
// add the list of nutrients to each ingredient
ingredientsList.forEach(nutrient -> nutrient.setNutrients(nutrientsList));
ingredientsList.forEach(System.out::println);
//add ingredients (rows) to the table
tableView.setItems(ingredientsList);
// make name column editable
tableView.setEditable(true);
nameColumn.setCellFactory(TextFieldTableCell.forTableColumn());
nameColumn.setCellValueFactory(new PropertyValueFactory<Ingredient, String>("name"));
for (int i = 0; i < nutrientsList.size(); i++) {
// add a column for each ingredient
TableColumn<Ingredient, Nutrient> column = new TableColumn<>(nutrientsList.get(i).getName());
tableView.getColumns().add(column);
Ingredient selectedIngredient = tableView.getSelectionModel().getSelectedItem();
int currentNutrientIndex = i;
column.setOnEditCommit(new EventHandler<TableColumn.CellEditEvent<Ingredient, Nutrient>>() {
@Override
public void handle(TableColumn.CellEditEvent<Ingredient, Nutrient> ingredientNutrientCellEditEvent) {
selectedIngredient.updateNutrientValue(currentNutrientIndex, ingredientNutrientCellEditEvent.getNewValue().getWeight());
}
});
// column.setCellFactory and column.setCellValueFactory here???
}
}
/**
* This method allows the user to double click on an cell and update the ingredient name.
* It is linked to the UI by setting the attribute onEditCommit of the Column the the name of the method in the fxml file
*/
public void changeIngredientNameCellEvent(TableColumn.CellEditEvent editedCell){
Ingredient selectedIngredient = tableView.getSelectionModel().getSelectedItem();
selectedIngredient.setName(editedCell.getNewValue().toString());
ingredientsList.forEach(System.out::println);
}
}
sample.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<TableView fx:id="tableView" prefHeight="200.0" prefWidth="200.0" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Controller">
<columns>
<TableColumn fx:id="nameColumn" onEditCommit="#changeIngredientNameCellEvent" prefWidth="75.0" text="Name" />
</columns>
</TableView>
成分class:
package sample;
import javafx.beans.property.SimpleStringProperty;
import java.util.ArrayList;
public class Ingredient {
private SimpleStringProperty name;
private ArrayList<Nutrient> nutrients;
public Ingredient(String name) {
this.name = new SimpleStringProperty(name);
}
public void setNutrients(ArrayList<Nutrient> list){
this.nutrients = list;
}
public void updateNutrientValue(int nutrientIndex, double newValue){
nutrients.get(nutrientIndex).setWeight(newValue);
}
public Nutrient getNutrientAt(int index){
return nutrients.get(index);
}
public String getName() {
return name.get();
}
public SimpleStringProperty nameProperty() {
return name;
}
public void setName(String name) {
this.name.set(name);
}
public String toString(){
return "name = '" + name.get() + "' nutrients = " + nutrients;
}
}
营养class:
package sample;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleStringProperty;
public class Nutrient {
private final SimpleStringProperty name;
private final SimpleDoubleProperty weight;
public Nutrient(String name, double weight) {
this.name = new SimpleStringProperty(name);
this.weight = new SimpleDoubleProperty(weight);
}
public String getName() {
return name.get();
}
public SimpleStringProperty nameProperty() {
return name;
}
public void setName(String name) {
this.name.set(name);
}
public double getWeight() {
return weight.get();
}
public SimpleDoubleProperty weightProperty() {
return weight;
}
public void setWeight(double weight) {
this.weight.set(weight);
}
public String toString(){
return name.get() + ", " + weight.get() ;
}
}
主要class:
package sample;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
@Override
public void start(Stage primaryStage) throws Exception{
Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
primaryStage.setScene(new Scene(root, 400, 300));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
你可以做到
for (int i = 0; i < nutrientsList.size(); i++) {
// add a column for each ingredient
TableColumn<Ingredient, Double> column = new TableColumn<>(nutrientsList.get(i).getName());
tableView.getColumns().add(column);
int currentNutrientIndex = i;
// column.setCellFactory and column.setCellValueFactory here???
column.setCellValueFactory(data ->
data.getValue().getNutrientAt(currentNutrientIndex).weightProperty().asObject());
column.setCellFactory(tc -> new TextFieldTableCell<>(new DoubleStringConverter()));
}
您可能更愿意提供自定义 StringConverter
来代替 DoubleStringConverter
以提供例如本地化解析等
请注意,您的设置将无法正常工作,但我认为这只是示例代码。您不能对不同的成分使用相同的 Nutrient
列表(或者实际上是相同的 Nutrient
实例,即使在不同的列表中)。您需要为每个 Ingredient
.
可以使用
制作一个完整的示例private final ObservableList<Ingredient> ingredientsList = FXCollections.observableArrayList();
@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
// add ingredients and nutrients
ingredientsList.add(new Ingredient("ingredient 1"));
ingredientsList.add(new Ingredient("ingredient 2"));
ingredientsList.add(new Ingredient("ingredient 3"));
List<String> nutrientNames = List.of("proteins", "fats", "carbs");
// add a list of nutrients to each ingredient
ingredientsList.forEach(ingredient -> ingredient.setNutrients(
nutrientNames.stream()
.map(name -> new Nutrient(name, 0.0))
.collect(Collectors.toList())
)
);
ingredientsList.forEach(System.out::println);
//add ingredients (rows) to the table
tableView.setItems(ingredientsList);
// make name column editable
tableView.setEditable(true);
nameColumn.setCellFactory(TextFieldTableCell.forTableColumn());
nameColumn.setCellValueFactory(data -> data.getValue().nameProperty());
for (int i = 0; i < nutrientNames.size(); i++) {
// add a column for each ingredient
TableColumn<Ingredient, Double> column = new TableColumn<>(nutrientNames.get(i));
tableView.getColumns().add(column);
Ingredient selectedIngredient = tableView.getSelectionModel().getSelectedItem();
int currentNutrientIndex = i;
// column.setCellFactory and column.setCellValueFactory here???
column.setCellValueFactory(data ->
data.getValue().getNutrientAt(currentNutrientIndex).weightProperty().asObject());
column.setCellFactory(tc -> new TextFieldTableCell<>(new DoubleStringConverter()));
}
}
将 Ingredient.nutrients
的类型更改为 List<Nutrient>
(无论如何这是一个很好的做法)。