It's not clear what is wrong with your code. There's too much missing. There's also an indication of not enough familiarity with how JavaFX's TableView
works nor how to separate concerns in a non-trivial application. This answer does not directly address your code, but hopefully the example provided here can help you solve your problem(s).
That said, I believe part of the problem is that you're not separating the data from how the data is retrieved enough. I don't quite understand what your TableView
is supposed to be displaying, but the fact your model is named CSV_Objects
would seem to support my belief. You should set up your UI code to display a model regardless of where the instances come from, then later worry about reading the data from a CSV file.
For example, let's say you want a table to display a number of students, where each student has an ID, a name, and a "year" (freshman, sophomore, junior, or senior). You first create a Student
class which encapsulates those properties.
Student.java
:
package sample;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class Student {
private final StringProperty id = new SimpleStringProperty(this, "id");
public final void setId(String id) { this.id.set(id); }
public final String getId() { return id.get(); }
public final StringProperty idProperty() { return id; }
private final StringProperty name = new SimpleStringProperty(this, "name");
public final void setName(String name) { this.name.set(name); }
public final String getName() { return name.get(); }
public final StringProperty nameProperty() { return name; }
private final ObjectProperty<Year> year = new SimpleObjectProperty<>(this, "year");
public final void setYear(Year year) { this.year.set(year); }
public final Year getYear() { return year.get(); }
public final ObjectProperty<Year> yearProperty() { return year; }
public Student() {}
public Student(String id, String name, Year year) {
setId(id);
setName(name);
setYear(year);
}
public enum Year {
FRESHMAN,
SOPHOMORE,
JUNIOR,
SENIOR
}
}
Note: JavaFX properties are used to make interacting with a TableView
easier.
Then you set up TableView
(FXML is used since you are also using FXML).
Controller.java
:
package sample;
import javafx.fxml.FXML;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
public class Controller {
@FXML private TableView<Student> studentTable;
@FXML private TableColumn<Student, String> idColumn;
@FXML private TableColumn<Student, String> nameColumn;
@FXML private TableColumn<Student, Student.Year> yearColumn;
@FXML
private void initialize() {
idColumn.setCellValueFactory(data -> data.getValue().idProperty());
nameColumn.setCellValueFactory(data -> data.getValue().nameProperty());
yearColumn.setCellValueFactory(data -> data.getValue().yearProperty());
// mock data for example
studentTable.getItems().addAll(
new Student("1", "John Smith", Student.Year.FRESHMAN),
new Student("2", "Jane Smith", Student.Year.SOPHOMORE)
);
}
}
Note: Used lambdas instead of PropertyValueFactory
. See Why should I avoid using PropertyValueFactory in JavaFX?.
Main.fxml
:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.layout.StackPane?>
<StackPane xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml"
fx:controller="sample.Controller">
<TableView fx:id="studentTable">
<columnResizePolicy>
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/>
</columnResizePolicy>
<columns>
<TableColumn fx:id="idColumn" text="ID"/>
<TableColumn fx:id="nameColumn" text="Name"/>
<TableColumn fx:id="yearColumn" text="Year"/>
</columns>
</TableView>
</StackPane>
And here is the application class.
Main.java
:
package sample;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
Parent root = FXMLLoader.load(Main.class.getResource("Main.fxml"));
primaryStage.setScene(new Scene(root, 600, 400));
primaryStage.show();
}
}
Note: Expects the Main.fxml
file to be in the same package as the sample.Main
class.
The above example will run and display two students in a table view. But these students are created in code (specifically, the initialize
method of the FXML controller). You want to read the students from a CSV file. For that, we need to write code to parse a CSV file into Student
instances.
For example, StudentCsvReader.java
:
package sample;
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
public class StudentCsvReader {
/*
* This is really basic CSV-reading code. It assumes a perfectly formatted file
* that has no commas in the actual data (i.e., there's no delimiter-escape
* handling). You may want to find an existing library to parse CSV files; they
* are likely to be more robust. Also, there are libraries out there that will
* "directly" parse a CSV file into POJOs. It could look something like:
*
* List<Student> students = CSVParser.parse(csvFile, Student.class);
*
*
* Note this code also assumes there is no header row.
*/
public List<Student> read(Path csvFile) throws IOException {
try (BufferedReader br = Files.newBufferedReader(csvFile)) {
List<Student> students = new ArrayList<>();
String line;
while ((line = br.readLine()) != null) {
if (!line.isBlank()) {
String[] columns = line.split(",");
String id = columns[0];
String name = columns[1];
String year = columns[2];
students.add(new Student(id, name, Student.Year.valueOf(year.toUpperCase())));
}
}
return students;
}
}
}
And then we modify the Controller
class to make use of this StudentCsvReader
. Note we use a background thread and a Task
to avoid doing I/O work on the JavaFX Application Thread.
Modified Controller.java
:
package sample;
import java.nio.file.Path;
import java.util.List;
import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
public class Controller {
@FXML private TableView<Student> studentTable;
@FXML private TableColumn<Student, String> idColumn;
@FXML private TableColumn<Student, String> nameColumn;
@FXML private TableColumn<Student, Student.Year> yearColumn;
@FXML
private void initialize() {
idColumn.setCellValueFactory(data -> data.getValue().idProperty());
nameColumn.setCellValueFactory(data -> data.getValue().nameProperty());
yearColumn.setCellValueFactory(data -> data.getValue().yearProperty());
Path csvFile = Path.of(".", "students.csv");
Thread t = new Thread(new ParseStudentCsvTask(csvFile), "parse-csv-thread");
t.setDaemon(true);
t.start();
}
private class ParseStudentCsvTask extends Task<List<Student>> {
private final Path csvFile;
ParseStudentCsvTask(Path csvFile) {
this.csvFile = csvFile;
}
@Override
protected List<Student> call() throws Exception {
return new StudentCsvReader().read(csvFile);
}
@Override
protected void succeeded() {
studentTable.getItems().setAll(getValue());
}
@Override
protected void failed() {
getException().printStackTrace();
}
}
}
Note: Expects a CSV file named students.csv
to exist in the working directory.
And here's an example CSV file that will work with the above code:
1,Kate Todd,FRESHMAN
2,Leroy Gibbs,SENIOR
3,Tony DiNozzo,JUNIOR
4,Ellie Bishop,FRESHMAN
5,Nick Torres,FRESHMAN
6,Ziva David,SOPHOMORE
Notice the modifications to the code did nothing to Student
or how the TableView
is configured. The only difference was how the Student
instances were created. Basically, the UI should not know where or how the data is created/found.
You should also consider using an application architecture such as Model-View-Controller (MVC) or Model-View-ViewModel (MVVM). It encourages separation of concerns, making it easier to reason about and develop code. Check out Applying MVC With JavaFx.