Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
menu search
person
Welcome To Ask or Share your Answers For Others

Categories

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("");

                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%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.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
469 views
Welcome To Ask or Share your Answers For Others

1 Answer

TL;DR version: you are confusing the cellValueFactory with the cellFactory. 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 a Callback<CellDataFeatures<Holiday, LocalDate>, ObservableProperty<LocalDate>>, i.e. a function mapping a CellDataFeatures<Holiday, LocalDate> to an ObservableValue<LocalDate>. So in Java code you would do

dateColumn.setCellValueFactory(holidayRowData -> holidayRowData.getValue().dateProperty());

or, if you prefer to use the (somewhat legacy) PropertyValueFactory class, you can do

dateColumn.setCellValueFactory(new PropertyValueFactory<>("date"));

The 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 the cellFactory. So your FXML should be

<TableView fx:id="tableView">
    <columnResizePolicy>
        <TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/>
    </columnResizePolicy>
    <columns>
        <TableColumn text="Holiday Name">
            <cellValueFactory>
                <PropertyValueFactory property="name"/>
            </cellValueFactory>
        </TableColumn>
        <TableColumn text="Date">
            <cellValueFactory>
                <PropertyValueFactory property="date" />
            </cellValueFactory>
        </TableColumn>
    </columns>
</TableView>

The cellFactory, by contrast, is an object that tells the column how to display the data. It is represented by a Callback<TableColumn<Holiday, LocalDate>, TableCell<Holiday, LocalDate>>, i.e a function mapping a TableColumn<Holiday, LocalDate> to a TableCell<Holiday, LocalDate>. The ClassCastException occurs because the cell factory you set is going to be passed the TableColumn, but is expecting to receive a CellDataFeatures, 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 with

@FXML
private TableColumn<Holiday, LocalDate> dateColumn ;

then in the controller's initialize method, you can do:

public void initialize() throws IOException {
    data = new HolidayData();
    data.loadHolidays();
    tableView.setItems(data.getHolidays());

    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy");
    dateColumn.setCellFactory(column -> new TableCell<Holiday, LocalDate>() {
        @Override
        protected void updateItem(LocalDate date, boolean empty) {
            super.updateItem(date, empty);
            if (empty) {
                setText("");
            } else {
                setText(formatter.format(date));
            }
        }
    });
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
...