I have been working on part of my application that allows a user to input holidays that their school takes off and saves it to a file. The name of the holiday and the date (stored as a LocalDate object) is read from the file and stored in a holiday object and put into an observable arraylist.
This is the main controller:
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.*;
import javafx.scene.layout.BorderPane;
import javafx.stage.FileChooser;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.DataFormatter;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.File;
import java.io.IOException;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Optional;
public class Controller {
@FXML
private BorderPane mainGridPane;
private ArrayList<Job> jobs = new ArrayList<>();
private static XSSFRow row;
private boolean fileClosed = false;
@FXML
private DatePicker employeeStartDate;
@FXML
private Label evaluation40;
@FXML
private Label evaluation80;
@FXML
private Label evaluation120;
@FXML
private DatePicker summerStart;
@FXML
private DatePicker summerEnd;
@FXML
private DatePicker fallStart;
@FXML
private DatePicker fallEnd;
@FXML
private TableView<Holiday> tableView;
private HolidayData data;
public void initialize() throws IOException {
data = new HolidayData();
data.loadHolidays();
tableView.setItems(data.getHolidays());
}
This is the Holiday Class:
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import java.time.LocalDate;
public class Holiday {
private SimpleStringProperty name = new SimpleStringProperty();
private ObjectProperty<LocalDate> date;
public Holiday(String name, LocalDate date) {
this.name.set(name);
this.date = new SimpleObjectProperty<>(date);
}
public String getName() {
return name.get();
}
public SimpleStringProperty nameProperty() {
return name;
}
public LocalDate getDate() {
return date.get();
}
public ObjectProperty<LocalDate> dateProperty() {
return date;
}
This is the HolidayData class that is responsible for reading the data to and from files and producing and observable arraylist:
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Iterator;
public class HolidayData {
private static HolidayData instance = new HolidayData();
private static String fileName = "schoolHolidays.txt";
private DateTimeFormatter formatter;
private ObservableList<Holiday> holidays;
public HolidayData(){
formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy");
}
public static HolidayData getInstance() {
return instance;
}
public static String getFileName() {
return fileName;
}
public ObservableList<Holiday> getHolidays(){
return holidays;
}
public void addHoliday(Holiday holiday){
holidays.add(holiday);
}
public void loadHolidays() throws IOException {
holidays = FXCollections.observableArrayList();
Path path = Paths.get(fileName);
BufferedReader br = Files.newBufferedReader(path);
String input;
try{
while((input = br.readLine()) != null){
String[] holidayPieces = input.split("\t");
String name = holidayPieces[0];
String dateString = holidayPieces[1];
LocalDate date = LocalDate.parse(dateString, formatter);
Holiday holiday = new Holiday(name, date);
holidays.add(holiday);
}
}catch(IOException e) {
e.printStackTrace();
} finally{
if(br != null){
br.close();
}
}
}
public void storeHolidays()throws IOException{
Path path = Paths.get(fileName);
BufferedWriter bw = Files.newBufferedWriter(path);
try {
Iterator<Holiday> iter = holidays.iterator();
while(iter.hasNext()){
Holiday holiday = iter.next();
bw.write(String.format("%s\t%s", holiday.getName(), holiday.getDate().format(formatter)));
bw.newLine();
}
}finally {
if (bw != null){
bw.close();
}
}
}
public void deleteHoliday(Holiday holiday){
holidays.remove(holiday);
}
}
This is the tableView portion of the FXML file:
<TableView fx:id="tableView">
<columnResizePolicy>
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/>
</columnResizePolicy>
<columns>
<TableColumn text="Holiday Name">
<cellFactory>
<PropertyValueFactory property="name"/>
</cellFactory>
</TableColumn>
<TableColumn text="Date">
<cellFactory>
<PropertyValueFactory property="date" />
</cellFactory>
</TableColumn>
</columns>
</TableView>
The problem I am running into is java.lang.ClassCastException
saying the table column cannot be cast to a certain object, which I think is the LocalDate
object. I am using the SimpleObjectProperty to databind it to the FXML file, but I can't seem to get it to work. The only other thing I can think to do is just make the date in Holiday
a SimpleStringProperty
and convert that to a LocalDate
in all the other parts in my code, but that seems unnecessary. I appreciate any insight anyone can give me.
TL;DR version: you are confusing the
cellValueFactory
with thecellFactory
. See, for example, this tutorial for a nice explanation of the difference, which is summarized for this particular example below.A table column's
cellValueFactory
is an object that tells the column which values to display in the cells, or more precisely how to get those values from the objects representing each row. This is represented by aCallback<CellDataFeatures<Holiday, LocalDate>, ObservableProperty<LocalDate>>
, i.e. a function mapping aCellDataFeatures<Holiday, LocalDate>
to anObservableValue<LocalDate>
. So in Java code you would door, if you prefer to use the (somewhat legacy)
PropertyValueFactory
class, you can doThe latter version has the (many disadvantages, but the one) advantage that it can be done in FXML as well. Note, though, that you want the
cellValueFactory
, not thecellFactory
. So your FXML should beThe
cellFactory
, by contrast, is an object that tells the column how to display the data. It is represented by aCallback<TableColumn<Holiday, LocalDate>, TableCell<Holiday, LocalDate>>
, i.e a function mapping aTableColumn<Holiday, LocalDate>
to aTableCell<Holiday, LocalDate>
. TheClassCastException
occurs because the cell factory you set is going to be passed theTableColumn
, but is expecting to receive aCellDataFeatures
, and so when it tries to treat it as such, the cast fails.You may well want a cell factory here, in addition to the cell value factory, so that you can control how the date is displayed (e.g. control the format used for it). If you give the date column an
fx:id
, say<TableColumn fx:id="dateColumn">
, and inject it into the controller withthen in the controller's initialize method, you can do: