0

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

Cbrady
  • 93
  • 1
  • 2
  • 7
  • 1
    See if https://stackoverflow.com/questions/56462624/read-excel-and-show-it-on-tableview-populate-tableview-using-2d-array-or-list-o/56528141#56528141 can help. – SedJ601 Feb 09 '23 at 15:37
  • I think James noticed that issue in your code and closed it based on that. The link I shared should give you some ideas on how to solve your issue. Also, it looks like you are not using a library to parse your `CSV` file. I think you should. – SedJ601 Feb 09 '23 at 16:31
  • 1
    [mcve] please .. make sure to read and understand that help page, then act accordingly .. and mind the __M__! :) – kleopatra Feb 09 '23 at 16:59
  • In reply to @SedJ601, I followed that link and my columns are setup differently now. However, there is a new problem where my rows are populated for the correct amount of rows that are in the CSV file but the problem is that the rows are populated with the column name values. i.e student number appears in the column then 4 times in the rows below. – Cbrady Feb 09 '23 at 17:22
  • 2
    How is it not the fix? You need to provide a callback that takes each row model instance (your `StudentCsv` object), and returns an `ObservableValue` wrapping the value to be displayed in the column. Obviously you cannot have a property called "student number", as that's not even a valid property name. – James_D Feb 09 '23 at 17:31
  • 1
    Probable duplicate [populate TableView from student CSV](https://stackoverflow.com/questions/75277201/tableview-doesnt-populate-from-an-arraylist/75277852#75277852). – jewelsea Feb 09 '23 at 23:06
  • I have provided a minimal reproducible example as requested, this was done on another post but since was removed, so can i request that the question be re-reopened? – Cbrady Feb 09 '23 at 23:56
  • 2
    You wrote in your [comment](https://stackoverflow.com/questions/75400736/dynamically-add-rows-to-a-table-in-javafx#comment133051513_75400736): _I have provided a minimal reproducible example as requested_ I don't think you have. I need to be able to copy your code as is (from your question), compile it and run it without changing anything and it will display the behavior that you describe in your question. You have not posted the code of the class that extends class [Application](https://openjfx.io/javadoc/17/javafx.graphics/javafx/application/Application.html). – Abra Feb 10 '23 at 06:56
  • 1
    Continuing from my previous [comment](https://stackoverflow.com/questions/75400736/dynamically-add-rows-to-a-table-in-javafx#comment133054894_75400736): It appears class `tableViewTest` is your FXML controller class but you did not post the FXML. – Abra Feb 10 '23 at 06:59
  • 2
    Your problem is the parameter you are passing to `setCellValueFactory`. You are returning the `StudentCsv` object and not one of its attributes. And the result you are seeing is probably because class `StudentCsv` does not override method [toString](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Object.html#toString()). – Abra Feb 10 '23 at 07:03
  • the fxml file is just a table with an fxid of tableView I didn’t think this needed provided. Is there any recommended fix for this problem? – Cbrady Feb 10 '23 at 11:05
  • 1
    repeating the previous comment by @Abra _You are returning the StudentCsv object and not one of its attributes_ - that __is__ the answer to the string representation in the cell. Please work through a tutorial on how to use TableView. And regarding: _the fxml file is just a table with an fxid of tableView I didn’t think this needed provided_ well, then it's not a [mcve] but just a code snippet, useless for reproducing the problem without change (as it has to be, and already mentioned in several comments ;) – kleopatra Feb 10 '23 at 11:15
  • In a previous post I was told by a member of stack overflow that fxml wouldnt be needed. I have now edited it so that everything is in. – Cbrady Feb 10 '23 at 11:18
  • .. and start fixing those naming violations - they hurt the eye ;) – kleopatra Feb 10 '23 at 11:18
  • fine, thanks for the edit :) but then, @Abra was kind enough to wade through your code and spotted the error with a keen eye, fix it and be happy. Make sure to read and understand the javadoc of CellDataFeatures (== type of param given to the factory) – kleopatra Feb 10 '23 at 11:20
  • I am happy @Abra has spotted where I'm going wrong, but not sure why you have to be constantly commenting condescending comments on my posts lol. I'm happy for someone to tell me where I'm going wrong or where my post lacks depth. Just no need for the condescending remarks. Thanks. – Cbrady Feb 10 '23 at 11:23

1 Answers1

2

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.

James_D
  • 201,275
  • 16
  • 291
  • 322
  • Thanks James, I think that would work as well. I managed to fix the problem also but in a different way. Earlier it wouldn't allow me to post an answer so I will post mine also. I appreciate you taking the time to help out :) – Cbrady Feb 10 '23 at 12:28