2
votes

I'm really having trouble understanding CellFactory and CellValueFactory in JavaFX TableColumn. I have to copy and modify most of the code from other people's questions. I'm trying to add a column that displays one image for every item that has a certain boolean value set to negative, and one image for the positive ones. The code is here (copied and modified):

 TableColumn <Track,Track> coverCol = new TableColumn<>("Artwork");
        coverCol.setCellFactory(new Callback<TableColumn<Track, Track>, TableCell<Track, Track>>() {
            @Override
            public TableCell<Track, Track> call(TableColumn<Track, Track> coverCol) {
                return new TableCell<Track, Track>(){
                    final ImageView hasArtworkView = new ImageView();
                    @Override public void updateItem(final Track artworkTrack,boolean empty){
                        super.updateItem(artworkTrack,empty);
                        if(artworkTrack != null){
                            if(artworkTrack.hasCover()){
                                hasArtworkView.setImage(hasArtworkIcon);
                            }else{
                                hasArtworkView.setImage(noArtworkIcon);
                            }
                            setGraphic(hasArtworkView);
                        }else {
                            setGraphic(null);
                        }
                    }
                };

            }
        });

The updateItem class can never look at an object, and it always defaults to null. Is there something I'm missing or should I implement this differently?

Track class:

public class Track {
    public String fileName;
    public String trackName;
    public String artistName;
    public String albumName;
    public String filePath;
    public SimpleBooleanProperty selected;
    public BufferedImage cover;
    Tag tag;



    public Track(File f){

        selected = new SimpleBooleanProperty(true);

        AudioFile af = null;
        try {
            af = AudioFileIO.read(f);
        } catch (CannotReadException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TagException e) {
            e.printStackTrace();
        } catch (ReadOnlyFileException e) {
            e.printStackTrace();
        } catch (InvalidAudioFrameException e) {
            e.printStackTrace();
        }

        tag = af.getTag();
        fileName = f.getName();
        filePath = f.getPath();
        trackName = tag.getFirst(FieldKey.TITLE);
        artistName = tag.getFirst(FieldKey.ARTIST);
        albumName = tag.getFirst(FieldKey.ALBUM);
        Artwork artwork = tag.getFirstArtwork();

        if(hasCover()){
            try {
                cover = Images.getImage(artwork);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }


    }


    public String getTrackName(){
        return trackName;
    }
    public String getArtistName(){
        return artistName;
    }
    public String getAlbumName(){
        return albumName;
    }
    public boolean isSelected() {return selected.getValue();}
    public ObservableValue selectedProperty(){return selected;}
    public BufferedImage getCover (){return cover;}
    public String getFileName() {
        return fileName;
    }
    public String getFilePath(){return filePath;}


    public void setTrackName(String s){trackName = s;}
    public void setArtistName(String s){artistName = s;}
    public void setAlbumName (String s){albumName = s;}
    public void setSelected (boolean b){selected.set(b);}
    public void setFileName(String fileName) {
        this.fileName = fileName;
    }

    public boolean hasCover(){
        return tag.hasField(FieldKey.COVER_ART);
    }
    public boolean hasTrackName(){return tag.hasField(FieldKey.TITLE);}
    public boolean hasArtistName(){return tag.hasField(FieldKey.ARTIST);}
}
2
Can you show your cellValueFactory, and the Track class? - James_D
@James_D Added the Track class, but there is no cellValueFactory. I guess that is my problem, but I have no idea how to use it. All of this just makes absolutely no sense to me. For reference, I'm copying code from this thread : stackoverflow.com/questions/16360323/… - DzeriMNE
The cell value factory maps the row into a value to be displayed in a cell - i.e. it determines the data the cell is displaying. Without a cell value factory, there is no data, so the item the cell is displaying will always be null. - James_D
@James_D The data it's displaying is an ImageView type, but the data it should be looking at is a boolean value. That is what makes me confused, and when displaying strings, you don't pass it the actual object in memory, rather just the name, that has to follow Java naming conventions, which would never give you an error and would make every beginner scratch his head. I might not know too much about all of this, but in my opinion, CellFactory and CellValueFactory are terribly designed classes. - DzeriMNE
No, ImageView is not data. It's a means of displaying data. This is a very standard UI pattern called model-view-controller. The cell value is the model, the cell is the view. Ignore PropertyValueFactory: it's just a convenience implementation of the Callback you need, which is left over from pre-Java 8 days when you needed an anonymous inner class to implement the callback. Now you can use a lambda expression, which is no more verbose than the PropertyValueFactory, and is typesafe, can be checked by the compiler, doesn't rely on naming conventions, etc etc. - James_D

2 Answers

2
votes

The item passed to updateItem is always going to be null, because you have no cellValueFactory defined. The cell value factory determines the data the cell displays.

Since your column is really interested in the result of calling hasCover() on the item represented by the row, it makes more sense to make this a TableColumn<Track, Boolean>. (The data represented in the cell is either true or false, i.e. it is a Boolean.)

So I would define:

TableColumn<Track, Boolean> coverCol = new TableColumn<>("Artwork");
coverCol.setCellValueFactory(cellData -> new SimpleBooleanProperty(cellData.getValue().hasCover()));
coverCol.setCellFactory(tc -> new TableCell<Track, Boolean>() {

    final ImageView hasArtworkView = new ImageView();

    @Override public void updateItem(Boolean hasCover ,boolean empty){
        super.updateItem(hasCover, empty);
        if(hasCover != null){
            if(hasCover){
                hasArtworkView.setImage(hasArtworkIcon);
            }else{
                hasArtworkView.setImage(noArtworkIcon);
            }
            setGraphic(hasArtworkView);
        }else {
            setGraphic(null);
        }
    }
});
1
votes

You can do something this :

coverCol.setCellValueFactory(data -> new ReadOnlyObjectWrapper<Track>(data.getValue()));

This will allow you to put the data into the column, then your cell factory will work.