How to add listener to the checkbox inside a listview that uses CheckBoxListCell


Question

I have a listview that uses a CheckBoxListCell to display a list with checkboxes next to the items. How do I add a listener to this checkbox to know when an item as been selected or unselected? enter image description here

1
5
8/17/2014 8:10:07 AM

Accepted Answer

Solution

You don't add a listener to the checkbox. You add a listener to the observable property of the object which was associated with the checkbox by the CheckBoxListCell.forListView routine.

Setting up the association:

ListView<Task> checklist = new ListView<>(tasks);
checklist.setCellFactory(CheckBoxListCell.forListView(Task::selectedProperty));

Adding a listener for all items:

tasks.forEach(task -> task.selectedProperty().addListener((observable, wasSelected, isSelected) -> {
    if (isSelected) {
        // . . .
    } else {
        // . . .
    }
}));

Documentation

The process is described in the CheckBoxListCell.forListView javadoc like so:

getSelectedProperty - A Callback that, given an object of type T (which is a value taken out of the ListView.items list), will return an ObservableValue that represents whether the given item is selected or not. This ObservableValue will be bound bidirectionally (meaning that the CheckBox in the cell will set/unset this property based on user interactions, and the CheckBox will reflect the state of the ObservableValue, if it changes externally).

Sample Program

A sample program which demonstrated some of the patterns which could be used with CheckBoxListCell:

import javafx.application.Application;
import javafx.beans.property.*;
import javafx.collections.*;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.scene.control.cell.CheckBoxListCell;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.StringConverter;

import java.util.*;
import java.util.stream.Collectors;

public class CheckList extends Application {
    @Override
    public void start(Stage stage) throws Exception{
        ObservableList<Task> tasks = FXCollections.observableArrayList(
                Arrays.stream(taskNames).map(Task::new).collect(Collectors.toList())
        );

        ListView<String> reactionLog = new ListView<>();
        tasks.forEach(task -> task.selectedProperty().addListener((observable, wasSelected, isSelected) -> {
            if (isSelected) {
                reactionLog.getItems().add(reactionStrings.get(task.getName()));
                reactionLog.scrollTo(reactionLog.getItems().size() - 1);
            }
        }));

        ListView<Task> checklist = new ListView<>(tasks);
        checklist.setCellFactory(CheckBoxListCell.forListView(Task::selectedProperty, new StringConverter<Task>() {
            @Override
            public String toString(Task object) {
                return object.getName();
            }

            @Override
            public Task fromString(String string) {
                return null;
            }
        }));

        HBox layout = new HBox(10, checklist, reactionLog);
        layout.setPrefSize(350, 150);
        layout.setPadding(new Insets(10));
        Scene scene = new Scene(layout);
        stage.setScene(scene);
        stage.show();
    }

    public static class Task {
        private ReadOnlyStringWrapper name = new ReadOnlyStringWrapper();
        private BooleanProperty selected = new SimpleBooleanProperty(false);

        public Task(String name) {
            this.name.set(name);
        }

        public String getName() {
            return name.get();
        }
        public ReadOnlyStringProperty nameProperty() {
            return name.getReadOnlyProperty();
        }

        public BooleanProperty selectedProperty() {
            return selected;
        }
        public boolean isSelected() {
            return selected.get();
        }
        public void setSelected(boolean selected) {
            this.selected.set(selected);
        }
    }

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

    private static final String[] taskNames = {
            "Walk the dog",
            "Skin the cat",
            "Feed the pig"
    };

    private static final Map<String, String> reactionStrings = new HashMap<>();
    static {
        reactionStrings.put("Walk the dog", "The dog thanks you");
        reactionStrings.put("Skin the cat", "The cat hates you");
        reactionStrings.put("Feed the pig", "The pig wants more");
    }
}

Sample output after selecting the first item once and the third item three times.

piggy

12
8/17/2014 9:12:26 AM

Here is an alternative if the item does not already have a property that indicates if it has been selected or not:

public class CheckedListViewCheckObserver<T> extends SimpleObjectProperty<Pair<T, Boolean>> {

    BooleanProperty getObserverForObject(T object) {
        BooleanProperty value = new SimpleBooleanProperty(false);
        value.addListener((observable, oldValue, newValue) -> {
            CheckedListViewCheckObserver.this.set(new Pair<>(object, newValue));
        });
        return value;
    }
}

Then to use it, you simply do:

CheckedListViewCheckObserver observer = new CheckedListViewCheckObserver<>();
checklist.setCellFactory(CheckBoxListCell.forListView(observer::getObserverForObject));

Now you can set a listener to listen for any changes:

observer.addListener((obs, old, curr) -> {
    if (curr.getValue()) {
        System.out.println("You have checked " + curr.getKey());
    } else {
        System.out.println("You have unchecked " + curr.getKey());
    }
});

The advantage of this method is that it does not depend on the objects being used; Instead, since it is generic, you can simply attach it to an already existing listview and it starts working off the bat.

Hope this helps someone.


Licensed under: CC-BY-SA with attribution
Not affiliated with: Stack Overflow
Icon