JavaFX TableView Objects with Maps
java, javafx, tableview
Solution
You don't have to use the default PropertyValueFactory, you can write your own callback.
import java.util.HashMap;
import java.util.LinkedHashMap;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Callback;
public class AssTable extends Application {
@Override
public void start(Stage primaryStage) {
ObservableList<Student> students = FXCollections.observableArrayList(
new Student("jack"),new Student("john"),new Student("jill"),new Student("jane"));
TableView<Student> studentTable = new TableView(students);
TableColumn<Student, String> firstNameColumn = new TableColumn("name");
firstNameColumn.setCellValueFactory(new PropertyValueFactory("firstName"));
studentTable.getColumns().add(firstNameColumn);
int maxAss = 0;
for (Student student : students)
maxAss = Math.max(maxAss, student.map.size());
Callback<TableColumn.CellDataFeatures<Student, String>, ObservableValue<String>> callBack =
new Callback<TableColumn.CellDataFeatures<Student, String>, ObservableValue<String>>() {
@Override
public ObservableValue<String> call(TableColumn.CellDataFeatures<Student, String> param) {
return param.getValue().map.containsKey(
"ass"+Integer.toString((int)param.getTableColumn().getUserData()))
? new SimpleStringProperty(String.format("%.1f",100d*param.getValue().map.get(
"ass"+Integer.toString((int)param.getTableColumn().getUserData()))))
:new SimpleStringProperty("");
}
};
ObservableList<TableColumn<Student, String>> assCols = FXCollections.observableArrayList();
for (int i = 1; i<=maxAss; i++){
TableColumn<Student, String> tmpCol = new TableColumn("ass"+Integer.toString(i));
tmpCol.setUserData(i);
tmpCol.setCellValueFactory(callBack);
assCols.add(tmpCol);
}
studentTable.getColumns().addAll(assCols);
VBox root = new VBox(studentTable);
Scene scene = new Scene(root, 500, 250);
primaryStage.setTitle("Table with map");
primaryStage.setScene(scene);
primaryStage.show();
}
public class Student {
private final StringProperty firstName = new SimpleStringProperty();
public StringProperty firstNameProperty(){return firstName;}
public final HashMap<String, Double> map;
public Student(String fn) {
firstName.set(fn);
map = new LinkedHashMap<>();
for (int i = 1; i <= 10; i++) {
double grade = Math.random();
if (grade > .5) {
map.put("ass" + Integer.toString(i), grade);
}
}
}
}
}
You can see it adds columns depending on how many assignments there are. Also nobody has done ass4 in this random sample. With this code and example you can't add an assignment like #8 without also adding a new column, or it won't show up.
Problem
So I have done some digging around on the JavaFx TableView and I have found some nice solutions to simple situations. This article provides a nice explanation of how to create a Table with a Person object and also shows how to create a Table using a Map. Here is my problem, let's say I have an Object Person that contains some simple member data and also a Map of Assignments to Grades. Example: ``` public class Person { String firstName; String lastName; String age; Map<Assignment, Grade> map; } ``` I want to display a table like the following ``` firstName | lastName | age | Assignment1 | Assignment 2 | Assignment 3 | ... ``` If I define a Table like so: ``` private TableView<Student> table; ``` It is easy to define simple columns like: ``` private TableColumn<Student, String> firstNameColumn = firstNameColumn.setCellValueFactory(new PropertyValueFactory<Person, String>("firstName")); ``` But how should I go about defining columns for each of the keys in the map? Is this even possible or should I just create another table that handles the map?