3

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);}
}
Dzeri96
  • 196
  • 2
  • 13
  • Can you show your `cellValueFactory`, and the `Track` class? – James_D Feb 19 '16 at 19:31
  • @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 : http://stackoverflow.com/questions/16360323/javafx-table-how-to-add-components – Dzeri96 Feb 19 '16 at 19:40
  • 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 Feb 19 '16 at 19:42
  • @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. – Dzeri96 Feb 19 '16 at 19:47
  • 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 Feb 19 '16 at 19:49

2 Answers2

3

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);
        }
    }
});
James_D
  • 201,275
  • 16
  • 291
  • 322
  • Gives me an error, it's requesting two Boolean items to update : Wrong 1st argument type. Found: 'com.jri.Track', required: 'java.lang.Boolean' less... – Dzeri96 Feb 19 '16 at 19:53
  • It is working, can you tell me what you changed to fix the error, and if there is a resource on these classes that can help me not to get stuck asking questions and googling every time I need to make a new Column. As I said, they don't make any sense to me and I'm always getting weird errors, like before asking this question when I attempted to define the CellFactory just like you did – Dzeri96 Feb 19 '16 at 20:03
  • 1
    [This tutorial](http://code.makery.ch/library/javafx-8-tutorial/) seems to be fairly popular. The fix was just to change the type of the first parameter to `updateItem` from `Track` to `Boolean` (and I changed the name correspondingly to make it make more sense). – James_D Feb 19 '16 at 20:21
  • thank you, I'll make sure to read it. See you on my next JavaFX question probably tomorrow :D – Dzeri96 Feb 19 '16 at 20:24
1

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.

purring pigeon
  • 4,141
  • 5
  • 35
  • 68
  • The cellValueFactory is what is used to put the data into the field. If you place an object in there, it will render with the toString() method of the object. If you want to do something fancy with it, that's when you need the cellFactory. – purring pigeon Feb 19 '16 at 19:46
  • Confirmed working, now I just need a way to format the cellValueFactory, since it's displaying the image not centered. – Dzeri96 Feb 19 '16 at 19:59