Cbrady
Cbrady

Reputation: 93

Dynamically add Rows to a table in JavaFX

I am trying to read from a CSV file in one class, which loops through the contents of the CSV file which takes the form of Student Number in column 1 and Question 1 to Question xAmount in the columns and creates an object from the rows in the CSV file. In another class, I am creating a table and trying to dynamically populate the table using the contents of the CSV file.

So far I have set it up so that the columns dynamically populate based on the headers in the CSV file, however whenever it comes to the rows it throws an error: "WARNING: Can not retrieve property 'student number' in PropertyValueFactory: javafx.scene.control.cell.PropertyValueFactory@76afc271 with provided class type: class java.lang.String" and does this for each column in the CSV file. Is there anyway I can dynamically populate the rows?

Here is the code for the StudentCsv Class:

public class StudentCsv {
    private int[] marks;
    private String studentNumber;
    public StudentCsv(String studentNumber, int[] marks) {
        this.studentNumber=studentNumber;
        this.marks = marks;
    }
}

And here is the code for the tableView class:

public class tableViewTest implements Initializable {
    @FXML
    private TableView tableView;
    @Override
    public void initialize(URL url, ResourceBundle resourceBundle) {
        ArrayList<StudentCsv> testStudents = new ArrayList<>();

        StudentCsv studentCsv1 = new StudentCsv("40236387", new int[]{2, 3, 4, 5, 6});
        StudentCsv studentCsv2 = new StudentCsv("40236388", new int[]{7, 8, 9, 10, 11});
        StudentCsv studentCsv3 = new StudentCsv("40236388", new int[]{12, 13, 14, 15, 16});

        testStudents.add(studentCsv1);
        testStudents.add(studentCsv2);
        testStudents.add(studentCsv3);

        String[] columnNames = {"Student number", "q1", "q2", "q3", "q4", "q5"};

        ObservableList<StudentCsv> studentData = FXCollections.observableArrayList();
        for (int i = 0; i < testStudents.size(); i++) {
            studentData.add(testStudents.get(i));
        }
        tableView.setItems(studentData);
        for (int i = 0; i < columnNames.length; i++) {
            TableColumn<StudentCsv, String> column = new TableColumn<>(columnNames[i]);
            column.setCellValueFactory(param ->
                    new ReadOnlyObjectWrapper<>(param.getValue()).asString());
            column.setPrefWidth(75);
            tableView.getColumns().add(column);
        }
    }
}
public class Main extends Application {
    @Override
    public void start(Stage stage) throws IOException {
        try{
            Parent root = FXMLLoader.load(getClass().getResource("tableViewTest.fxml"));
            Scene scene = new Scene(root);
            stage.setScene(scene);
            stage.show();
        }catch (Exception e){
            e.printStackTrace();
        }

    }
    public static void main(String[] args) {
        launch();
    }
}
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.TableView?>
<?import javafx.scene.layout.AnchorPane?>

<AnchorPane prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/19" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.example.testfx.tableViewTest">
   <children>
      <TableView fx:id="tableView" layoutX="43.0" layoutY="76.0" prefHeight="286.0" prefWidth="505.0" />
   </children>
</AnchorPane>

When the table populates it populates like this

Upvotes: 0

Views: 215

Answers (1)

James_D
James_D

Reputation: 209684

The cell value factory for a column is a function which takes the object representing a row (a StudentCsv, which is a terrible name for the class) and returns an ObservableValue containing the value to be displayed in that column for that row (i.e. the value in each cell in the column). The function is applied to each visible row in the table to determine what to display in the cells in the column.

So your cell value factory should be a function that takes a Student and returns an ObservableValue wrapping the value for that column. The first column's cell value factory should return an ObservableValue containing the studentNumber. The remaining columns cell value factories should return an ObservableNumber containing the respective mark.

So you need to start by having accessor methods for studentNumber and marks. At a minimum:

public class Student {
    private int[] marks;
    private String studentNumber;
    public Student(String studentNumber, int[] marks) {
        this.studentNumber=studentNumber;
        this.marks = marks;
    }

    public String getStudentNumber() {
        return studentNumber;
    }

    public int[] getMarks() {
        return marks;
    }
}

Now your cell value factories can return appropriate values for each column. I fixed some of the ugly code here. I'm not sure why you have a field called studentNumber which is a String (and not a numeric type of some kind), but I left it in case there is some good reason for it.

public class TableViewTest implements Initializable {
    @FXML
    private TableView<Student> tableView;
    @Override
    public void initialize(URL url, ResourceBundle resourceBundle) {
        ArrayList<Student> testStudents = new ArrayList<>();

        Student student1 = new Student("40236387", new int[]{2, 3, 4, 5, 6});
        Student student2 = new Student("40236388", new int[]{7, 8, 9, 10, 11});
        Student student3 = new Student("40236388", new int[]{12, 13, 14, 15, 16});

        testStudents.add(student1);
        testStudents.add(student2);
        testStudents.add(student3);

        String[] columnNames = {"Student number", "q1", "q2", "q3", "q4", "q5"};

        ObservableList<Student> studentData = FXCollections.observableArrayList();
        studentData.addAll(testStudents);
        tableView.setItems(studentData);

        TableColumn<Student, String> studentNumberColumn = new TableColumn<>("Student number");
        studentNumberColumn.setCellValueFactory(cellData -> new SimpleStringProperty(cellData.getValue().getStudentNumber()));
        tableView.getColumns().add(studentNumberColumn);

        for (int i = 1; i < columnNames.length; i++) {
            TableColumn<Student, Number> column = new TableColumn<>(columnNames[i]);
            final int marksIndex = i-1 ;
            column.setCellValueFactory(cellData ->
                    new SimpleIntegerProperty<>(cellData.getValue().getMarks()[marksIndex]));
            column.setPrefWidth(75);
            tableView.getColumns().add(column);
        }
    }
}

This is not tested, so there may be typos.

Upvotes: 2

Related Questions