JavaFX2 – Add a favorite image click event to TableCell

septembre 15th, 2014

Usually when you have to make a table view with a checked field, you’ll choose to add a checkbox inside a table cell. But this time, my customer ask me to replace the checkbox by a small checked image.

Adding a graphical element to a TableView is not much harder. The tableColumn’s CellFactory handles cell’s content rendering. So if you want to place an image instead of textual content in the cell, you’ll need to also set the TableColumn’s CellFactory to one of your custom object and override the updateItem() method. Let’s have a look at this quick example.

Here is an example of what a table view with checked image might look like :

JavaFX2 - Table view Sample

Our TableView example have only two columns. One for the user name and one for the favorite image field (checked or not ).

Contact object

The user name’s text is simply a StringProperty of the Contact object, but the favorite image’s come from the FavoriteImage object associated to an ObjectProperty object. The property favoriteProperty assign a state favorite to the selected name.


import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

public class Contact {
    private final StringProperty nameProperty = new SimpleStringProperty();
    private final ObjectProperty<FavoriteImage> favoriteImageProperty
= new SimpleObjectProperty();
   
    private final BooleanProperty favoriteProperty;
        private final String BOOKMARK_IMG = "/images/valid.png";

    public Contact(String name, boolean favorite) {
        setName(name);
        this.favoriteProperty = new SimpleBooleanProperty(this, "favorite", favorite);
        hasFavoriteImage(getFavorite());
    }            
           
    public Object getFavoriteImage() {
        return this.favoriteImageProperty.get();
    }

    public ObjectProperty<FavoriteImage> favoriteImageProperty() {
        return favoriteImageProperty;
    }

    public void setFavoriteImage(FavoriteImage favoriteImage) {
        this.favoriteImageProperty.set(favoriteImage);
    }

    public void hasFavoriteImage(boolean image) {
        if (image) {
            setFavoriteImage(new FavoriteImage(BOOKMARK_IMG));
        } else {
            setFavoriteImage(null);
        }
    }        
   
    public Boolean getFavorite() {
        return this.favoriteProperty.get();
    }

    public BooleanProperty favoriteProperty() {
        return favoriteProperty;
    }

    public void setFavorite(Boolean favorite) {
        this.favoriteProperty.set(favorite);
    }    
       
    public final String getName() {
        return nameProperty.get();
    }

    public final void setName(String name) {
        this.nameProperty.set(name);
    }

    public StringProperty textProperty() {
        return nameProperty;
    }    
}

Favorite Image object

In order to bundle the favorite image information we create a FavoriteImage Object.


import javafx.beans.property.SimpleStringProperty;

public class FavoriteImage {

    private final SimpleStringProperty favoriteImage = new SimpleStringProperty();

    public FavoriteImage(String imagePath) {
        setFavoriteImage(imagePath);
    }

    public void setFavoriteImage(String favoriteImageFile) {
        favoriteImage.set(favoriteImageFile);
    }

    public String getFavoriteImage() {
        return favoriteImage.get();
    }
}

Image cell factory with a simple click object

We use an object ImageClickCellFactory to reimplement the table cell in charge of the image object. This object will update the image’s cell and handle the mouse click event to set the contact name favorite. For each cell in that TableColumn, updateItem() will update the item’s state associated with this cell.


import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.VBox;
import javafx.util.Callback;

public class ImageClickCellFactory implements Callback<TableColumn, TableCell> {

    private final EventHandler<Event> click;

    public ImageClickCellFactory(EventHandler click) {
        this.click = click;
    }

    @Override
    public TableCell call(TableColumn p) {
        TableCell<Object, FavoriteImage> cell
                = new TableCell<Object, FavoriteImage>() {

                    private final VBox vbox;
                    private final ImageView imageview;

                    // Constructor
                    {
                        vbox = new VBox();
                        vbox.setAlignment(Pos.CENTER);
                        imageview = new ImageView();
                        imageview.setFitHeight(16);
                        imageview.setFitWidth(16);
                        imageview.setVisible(true);
                        imageview.setCache(true);
                        vbox.getChildren().addAll(imageview);
                        setGraphic(vbox);
                    }

                    @Override
                    protected void updateItem(FavoriteImage item,
                            boolean empty) {

                        // calling super here is very important - don't skip this!
                        super.updateItem(item, empty);

                        if (empty) {
                            setText(null);
                            setGraphic(null);

                        } else {
                            Image image = new Image(
                                    getClass().getResourceAsStream(
                                            item.getFavoriteImage()));

                            imageview.setImage(image);
                            setGraphic(vbox);
                        }
                    }
                };

        // Simple click
        if (click != null) {
            cell.setOnMouseClicked(click);
        }

        return cell;
    }
}

The main class

And finally we create the TableView, columns, data list, property binding and Mouse event in the main application class.


import com.physalix.jfx.model.Contact;
import com.physalix.jfx.model.FavoriteImage;
import com.physalix.jfx.model.ImageClickCellFactory;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class Main extends Application {

    private ObservableList<Contact> dataList;

    @Override
    public void start(Stage stage) {
        Scene scene = new Scene(new Group());
        stage.setTitle("Table View Favorite Sample");
        stage.setWidth(300);
        stage.setHeight(500);

        dataList = FXCollections.observableArrayList(
                new Contact("Jacob", true),
                new Contact("Isabella", true),
                new Contact("Ethan", true),
                new Contact("Emma", true),
                new Contact("Michael", true));

        TableView table = new TableView();
        table.setEditable(true);

        TableColumn nameCol = new TableColumn("Name");
        nameCol.setPrefWidth(90);
        nameCol.setCellValueFactory(
                new PropertyValueFactory<Contact, String>("name"));

        TableColumn favoriteCol = new TableColumn("Favorite");
        favoriteCol.setPrefWidth(90);
        favoriteCol.setCellValueFactory(
                new PropertyValueFactory<Contact, FavoriteImage>("favoriteImage"));

        ImageClickCellFactory cellFactory
                = new ImageClickCellFactory(
                        new CreateFavoriteClickMouseEventHandler());

        favoriteCol.setCellFactory(cellFactory);
       
        table.getColumns().addAll(nameCol, favoriteCol);

        final VBox vbox = new VBox();
        vbox.setSpacing(5);
        vbox.setPadding(new Insets(10, 0, 0, 10));
        vbox.getChildren().add(table);

        ((Group) scene.getRoot()).getChildren().addAll(vbox);

        table.setItems(dataList);

        stage.setScene(scene);
        stage.show();
    }
   
    // This class implement the mouse event handler
    private class CreateFavoriteClickMouseEventHandler
            implements EventHandler<MouseEvent> {

        @Override
        public void handle(MouseEvent event) {
            if (event.getClickCount() == 1) {

                try {
                    Contact contact = dataList.get(
                            ((TableCell) event.getSource()).getIndex());

                    if (contact.getFavorite()) {
                        contact.setFavorite(Boolean.FALSE);
                        contact.hasFavoriteImage(Boolean.FALSE);

                    } else {
                        contact.setFavorite(Boolean.TRUE);
                        contact.hasFavoriteImage(Boolean.TRUE);
                    }

                    // do something here...
                   
                } catch (IndexOutOfBoundsException e) {
                }
            }
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}

The source code of this sample is available on GitHub.

  • https://www.facebook.com/Born2Program Nitish Kumar Singh

    Thanks for this awesome tutorial