Reputation: 169
I have the following TableView:
Each row represents an Ingredient which have two properties, a name and a ArrayList of type Nutrient with the nutrients (name and weight) that composes the ingredient.
My goal is to update the weight property of each Nutrient in the Array List of each Ingredient by clicking on the corresponding cell and entering the new value (same behavior as the Name column).
I suspect that I need to set for each column a custom CellValueFactory and a CellFactory, but I'm new to JavaFx and I don't know exactly ho do do it.
This is the Controller class where most of the magic (should) happen:
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>
Ingredient 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;
}
}
Nutrient 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() ;
}
}
Main 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);
}
}
Upvotes: 0
Views: 490
Reputation: 209225
You can do
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()));
}
You may prefer to provide a custom StringConverter
in place of the DoubleStringConverter
to provide, e.g., localized parsing etc.
Note that the setup you have won't work correctly, but I assume this is just example code. You can't use the same list of Nutrient
s for different ingredients (or indeed the same Nutrient
instances, even in different lists). You need to create new instances for each Ingredient
.
A fully working example can be made using
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()));
}
}
with the additional change of changing the type of Ingredient.nutrients
to List<Nutrient>
(which is good practice anyway).
Upvotes: 4