Search code examples
uitableviewjavafxtableviewindexoutofboundsexception

JavaFX TableView crashes if scroll to bottom too fast, also custom cell becomes illogical on actions


So first off I am trying to color a cell red, or have the cell outlined in red based on another column's value, same row. For example, there is a "Enrolled" Date column and a "DeadlineToEnrollBy" Date column. This is a generic example.

So If The Deadline date is tomorrow, the cell will not be in red because the student still has time, but if the Deadline was yesterday and the student still has not enrolled, then the "Enrolled" Date cell will be in red, indicating immediate attention to that student. I am actually able to do this but the table acts "funky" at times.

If I scroll to fast to the bottom, I get an error. And at times when scrolling up and down, the red cells will be in other places, or all the cells in the column will be red even though it shouldn't be. I believe even uploading new data, hence refreshing the table too causes the red cells to be off.

The error that I get is

 Exception in thread "JavaFX Application Thread"
 java.lang.IndexOutOfBoundsException

on this line of code

Person student= ClassPanelView.retrieveTable().getItems.get(getIndex());

Here is the flow of code that's relevant

In ClassPanelView

createTable(){
    .
    .
    TableColumn<Person, Date> enrolledBy = new TableColumn<>("Enrolled");
    enrolledBy.setCellValueFactory(new PropertyValueFactory<>("dateEnrolled"));
    enrolledBy.setCellFactory(column -> {
        return new util.EditEnrolledDateCell<Person, Date>();
    });
    table.getColumns().addAll(enrolledBy, ..etc);
}

public static TableView<Person> retrieveTable() {
    return table;
}

The other class/Cell the column calls/returns

public class EditEnrolledDateCell<S,T> extends TextFieldTableCell<Person, Date> {
    private Date now = new Date();
...
...
        @Override
    public void updateItem(Date item, boolean empty) {
      super.updateItem(item, empty);

      if (item == null || empty) {
          if(this.getIndex() > -1) {
            /*int currentIndex = indexProperty().getValue() < 0 ? 0
                    : indexProperty().getValue(); */          

            Person student = ClassPanelView.retrieveTable().getItems().get(getIndex()); //<==== This line is the problem
            if(student.getDeadline != null && student.getDeadline.before(now)) {
                setStyle("-fx-border-color: #f40404;\n"
                        + "-fx-border-width: 1 1 1 1;\n");
            }
          } 
        }
        else { //if there is something here, format it
          setStyle("");
          setText(GuiUtils.monthFirstDateFormat.format(item));
        }
    }

}

Any tips/knowledge would be greatly appreciated! Thank you

Edit: Desired Behavior


Solution

  • You never reset the style of the cell for persons not crossing the deadline. You need to do this kind of update. Furthermore there may be empty cells at the end of the table that have a non-negative index. (You try to retrieve the person only if the cell is empty.)

    Also to make the cell more reuseable I recommend using TableCell.getTableView instead of accessing a static field.

    @Override
    public void updateItem(Date item, boolean empty) {
        super.updateItem(item, empty);
    
        if (empty) {
            setStyle(null);
            setText("");
        } else {
            Person student = getTableView().getItems().get(getIndex());
            setStyle(student.getDeadline != null && !now.after(student.getDeadline)
                             ? null
                             : "-fx-border-color: #f40404; -fx-border-width: 1 1 1 1;");
            // I recommend passing the formatter in a constructor to make the cell type easier to reuse
            setText(item == null ? "" : GuiUtils.monthFirstDateFormat.format(item));
        }
    }
    

    Note: You should also rename the field getDeadline to deadline. getDeadline by convention is the name of the getter method for the deadline property and I cannot think of a reason where naming a field in a way you'd usually use for a getter would be good practice.

    Also I currently don't see a reason to add type parameters to your custom cell type. Those types are never used.

    public class EditEnrolledDateCell extends TextFieldTableCell<Person, Date>
    

    There may be a scenario where you'd want to keep those. If you want to use the cell with a TableView<T> where T is a subtype of Person or use a value type in the column that is a subtype of Date, but this requires a different declaration:

    public class EditEnrolledDateCell<S extends Person, T extends Date> extends TextFieldTableCell<S, T> {
    
         ...
    
        @Override
        public void updateItem(T item, boolean empty) {
    ...
    

    Moreover I recommend using LocalDate instead of Date, if possible, since this type is more modern/easier to use.