0

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:

enter image description here

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?

2

2 Answers 2

2

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();
}
Sign up to request clarification or add additional context in comments.

Comments

0

That's how it seems to work...i have been guided by this.

@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);
    }

    
    List<String> colHeads = new ArrayList<String>();
    for (int c = 0; c < cols; c++) {
        String ch = "Col: " + c; 
        colHeads.add(ch);
    }
    
    
    // show values in table 
    List<TableColumn<Map<String, String>, String>> tcs = new ArrayList<TableColumn<Map<String, String>, String>>();
    for (int c = 0; c < cols; c++) {
        String ch =  colHeads.get(c);
        
        TableColumn<Map<String, String>, String> col = new TableColumn<>(ch);
        col.setCellValueFactory(new MapValueFactory(ch)); 
        
        tcs.add(col); 
    }
    
    TableView<Map<String, String>> tableView = new TableView<>(generateDataInMap(colHeads, values));
    tableView.getColumns().setAll(tcs);
     

    stage.setScene(new Scene(tableView, 300, 500));
    stage.show();

}

private ObservableList<Map<String, String>> generateDataInMap(List<String> colHeads, List<List<String>> values ) {

    ObservableList<Map<String, String>> allData = FXCollections.observableArrayList();
    
    for(int r=0; r<values.size(); r++) {
        Map<String, String> dataRow = new HashMap<String, String>();
        
        for(int c=0; c<values.get(r).size(); c++) {
            String val = values.get(r).get(c);
            dataRow.put(colHeads.get(c), val);
        }
        allData.add(dataRow);
    }
    return allData;
}

2 Comments

Your "guide" is for pre Java 8. Here is the Java 8 version.
What's wrong with just doing final int colIndex = c ; and col.setCellValueFactory(cd -> new SimpleStringProperty(cd.getValue().get(colIndex)));?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.