GOXR3PLUS GOXR3PLUS - 2 months ago 20
Java Question

Define custom Comparator for JavaFX TableView Column rendered as SimpleObjectProperty<ImageView>

I have a Table which has different columns , the signature of the one i want to add a custom comporator is this :

@FXML
private TableColumn<Media,SimpleObjectProperty<ImageView>> hasBeenPlayed;


So this column is rendered as an ImageView . It has two possible states , either the
Imageview
will have
null
image or the
ImageView
will have an Image , a specific one i have defined .




So what i want is the sorting to be based on If the
ImageView
has null Image and if the ImageView has Image
, so i made the below Comparator but it reports an error i don't know why...

hasBeenPlayed.setComparator( new Comparator<SimpleObjectProperty<ImageView>>() {
@Override
public int compare(SimpleObjectProperty<ImageView> o1 , SimpleObjectProperty<ImageView> o2) {
if (o1.get().getImage() == o2.get().getImage())
return 0;
return -1;
}
});


I should use lambda expression for the above , i added it in this way to be more obvious what i am trying to achieve.




The error i am getting is ..

Exception in thread "JavaFX Application Thread" java.lang.ClassCastException:
javafx.scene.image.ImageView cannot be cast to javafx.beans.property.SimpleObjectProperty

















As James_D requested on the comments:

Cell Value Factory:

hasBeenPlayed.setCellValueFactory(new PropertyValueFactory<>("hasBeenPlayed"));


Part of the Model
Media
:

public abstract class Media {

.....

/** The has been played. */
private SimpleObjectProperty<ImageView> hasBeenPlayed;

.....

/**
* Checks for been played property.
*
* @return the simple object property
*/
public SimpleObjectProperty<ImageView> hasBeenPlayedProperty() {
return hasBeenPlayed;
}


}

Answer Source

There are multiple issues with your code.

First of all you're putting UI elements in the item class, which is something you should avoid. The name of the column hints at the displayed property to have only 2 states. A boolean property would be more appropriate.

Use a custom cellFactory to display the image.

Furthermore according to the error message the real type of the column should be TableColumn<Media, ImageView>.

Also you're violating the contract of Comparator by making it non-symetric.

You have to ensure the following is fulfilled:

If comparator.compare(a, b) < 0 then comparator.compare(b, a) > 0. In your case this is not fulfilled unless all ImageViews contain the same image (or all contain null).

Modify the code like this in addition to making the property a boolean property:

@FXML
private TableColumn<Media, Boolean> hasBeenPlayed;

hasBeenPlayed.setComparator((v1, v2) -> Boolean.compare(v1, v2));

or alternatively

hasBeenPlayed.setComparator((v1, v2) -> -Boolean.compare(v1, v2));

and add the following to the initialize method of the controller

final Image playedImage = new Image(...);

hasBeenPlayed.setCellFactory(col -> new TableCell<Media, Boolean>() {
    private final ImageView image = new ImageView();

    {
        setGraphic(image);
        image.setFitWidth(playedImage.getWidth());
        image.setFitHeight(playedImage.getHeight());
    }

    @Override
    protected void updateItem(Boolean item, boolean empty) {
         super.updateItem(item, empty);

         // set the image according to the played state
         image.setImage(item != null && item ? playedImage : null);
    }
});