I' m trying to create a TableView from a dataset consisting of a doublenested ArrayList with String values. Unfortunately, I cannot display all values in a TableView. The rows are not numbered according to the data set.
The most solutions of the internet demonstrate, that I should use a specific object instance. However, I do not work that way. Instead, I use a matrix of Strings that has an indefinite size. The data is structured as follows:
List<List<String>> values = new ArrayList<List<String>>();
int rows = 10;
int cols = 4;
for(int r=0; r<rows; r++) {
List<String> line = new ArrayList<String>();
for(int c=0; c<cols; c++)
line.add("r: "+r+", c: "+c);
values.add(line);
}
My best (but wrong) result was achieved with the following code:
import java.util.ArrayList;
import java.util.List;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.stage.Stage;
public class TestApplication extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage stage) throws Exception {
// create values
List<List<String>> values = new ArrayList<List<String>>();
int rows = 10;
int cols = 4;
for (int r = 0; r < rows; r++) {
List<String> line = new ArrayList<String>();
for (int c = 0; c < cols; c++)
line.add("r: " + r + ", c: " + c);
values.add(line);
}
// show values in table
TableView<List<StringProperty>> tableView = new TableView<List<StringProperty>>();
ObservableList<List<StringProperty>> data = FXCollections.observableArrayList();
for (int c = 0; c < cols; c++) {
TableColumn<List<StringProperty>, String> col = new TableColumn<>("Col: " + c);
tableView.getColumns().add(col);
}
for (int r = 0; r < values.size(); r++) {
List<StringProperty> row = new ArrayList<StringProperty>();
for (int c = 0; c < values.get(r).size(); c++) {
TableColumn<List<StringProperty>, String> col = (TableColumn<List<StringProperty>, String>) tableView.getColumns().get(c);
String val = values.get(r).get(c);
col.setCellValueFactory(cl -> new SimpleStringProperty(new String(val)));
}
data.add(row);
}
tableView.setItems(data);
stage.setScene(new Scene(tableView, 300, 500));
stage.show();
}
}
It gives the following result:
However, the table does not show the numbered rows. I have already tried many things. But I did not get a satisfactory result. What is my mistake?
The cellValueFactory
for a given column is a function that takes a wrapper containing the data for a row (i.e. the List<StringProperty>
) and generates the ObservableValue
(e.g. a Property
) whose value is to be displayed in that row and column.
In your code you change the cellValueFactory
every time you add a row, so the resulting cellValueFactory
is just the one from the last row you add, and you see only the data from the last row.
You should set the cellValueFactory
just once per column, and it should be a function mapping the row data to the specific value for that column.
For example, the following will give you what you need:
@Override
public void start(Stage stage) throws Exception {
// create values
List<List<String>> values = new ArrayList<List<String>>();
int rows = 10;
int cols = 4;
for (int r = 0; r < rows; r++) {
List<String> line = new ArrayList<String>();
for (int c = 0; c < cols; c++)
line.add("r: " + r + ", c: " + c);
values.add(line);
}
// show values in table
TableView<List<StringProperty>> tableView = new TableView<>();
ObservableList<List<StringProperty>> data = FXCollections.observableArrayList();
for (int c = 0; c < cols; c++) {
TableColumn<List<StringProperty>, String> col = new TableColumn<>("Col: " + c);
final int colIndex = c ;
col.setCellValueFactory(cd -> cd.getValue().get(colIndex));
tableView.getColumns().add(col);
}
for (int r = 0; r < values.size(); r++) {
List<StringProperty> row = new ArrayList<StringProperty>();
for (int c = 0; c < values.get(r).size(); c++) {
row.add(new SimpleStringProperty(values.get(r).get(c)));
}
data.add(row);
}
tableView.setItems(data);
stage.setScene(new Scene(tableView, 300, 500));
stage.show();
}
If the data are not going to change, you might prefer to use something lighter weight than a StringProperty
, though the benefit to this small performance saving is debatable:
@Override
public void start(Stage stage) throws Exception {
// create values
List<List<String>> values = new ArrayList<List<String>>();
int rows = 10;
int cols = 4;
for (int r = 0; r < rows; r++) {
List<String> line = new ArrayList<String>();
for (int c = 0; c < cols; c++)
line.add("r: " + r + ", c: " + c);
values.add(line);
}
// show values in table
TableView<List<String>> tableView = new TableView<>();
for (int c = 0; c < cols; c++) {
TableColumn<List<String>, String> col = new TableColumn<>("Col: " + c);
final int colIndex = c ;
col.setCellValueFactory(cd -> new ObservableValue<String>() {
// If data are immutable, there's nothing for a listener to do, so we ignore them:
@Override
public void addListener(InvalidationListener listener) {}
@Override
public void removeListener(InvalidationListener listener) {}
@Override
public void addListener(ChangeListener<? super String> listener) {}
@Override
public void removeListener(ChangeListener<? super String> listener) {}
@Override
public String getValue() {
return cd.getValue().get(colIndex);
}
});
tableView.getColumns().add(col);
}
tableView.setItems(FXCollections.observableList(values));
stage.setScene(new Scene(tableView, 300, 500));
stage.show();
}