JavaFx: ListView CheckBoxListCell selection

Sunflame :

I have a problem with the ListView using a CheckBoxListCell. When I check/uncheck an item, the item is not selected/focused which would be expected since the CheckBox is also part of the item not only the text part.

Here is a simple code you can verify it.

import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ListView;
import javafx.scene.control.cell.CheckBoxListCell;
import lombok.Getter;

import java.net.URL;
import java.util.ResourceBundle;

public class Controller implements Initializable {
    @FXML private ListView<Model> listView;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
//      without selection
//      listView.setCellFactory(CheckBoxListCell.forListView(Model::getSelected));

        // actual "bad" solution
        listView.setCellFactory(factory -> {
            CheckBoxListCell<Model> cell = new CheckBoxListCell<Model>() {
                @Override
                public void updateItem(Model item, boolean empty) {
                    super.updateItem(item, empty);
                    if (empty) {
                        setText(null);
                        setGraphic(null);
                        return;
                    }
                    ((CheckBox) getGraphic()).selectedProperty().addListener(
                            (observable, oldValue, newValue) -> listView.getSelectionModel().select(getItem()));
                }
            };
            cell.setSelectedStateCallback(Model::getSelected);
            return cell;
        });

        ObservableList<Model> items = FXCollections.observableArrayList();

        items.add(new Model("A", true));
        items.add(new Model("B", true));
        items.add(new Model("C", false));

        listView.setItems(items);
    }

    @Getter
    private class Model {
        String text;
        BooleanProperty selected;

        private Model(String text, Boolean selected) {
            this.text = text;
            this.selected = new SimpleBooleanProperty(selected);
        }

        @Override public String toString() {
            return text;
        }
    }
}

.fxml:

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.control.ListView?>
<AnchorPane xmlns="http://javafx.com/javafx"
            xmlns:fx="http://javafx.com/fxml"
            fx:controller="list.Controller">
<ListView fx:id="listView"/>
</AnchorPane>

As you can see there is a solution I found, or better said a dirty workaround, but I don't really like is since it is called at every updateItem and it adds the listener n times which is not so great.

Any other ideas/solutions how can I achieve that when I check/uncheck the combobox the whole item is selected/focused.

kleopatra :

This answer covers only part of the question: how to make sure that a listener on a property of the graphic of a cell is registered once only (if we don't have control about when the graphic is set).

The steps involved:

  1. define an InvalidationListener that installs the "real" listener (note: has to be a field to be able to remove later on)
  2. register the listener on the cell's graphic property at instantiation time
  3. implement the registration of the "real" method to remove the initial graphics listener in addition to install whatever is needed

The code snippet (beware: listening to the selected property is not a good idea, because it will fire whenever the data changes, not only when the user clicks the checkbox!):

listView.setCellFactory(factory -> {
    CheckBoxListCell<Model> cell = new CheckBoxListCell<Model>() {
        // a listener on the graphicProperty: it installs the "real" listener
        InvalidationListener graphicListener = g -> {
            // installs the "real" listener on the graphic control once it is available 
            registerUIListener();
        };

        {
            // install the graphic listener at instantiation
            graphicProperty().addListener(graphicListener);
        }

        /** method to install a listener on a property of the graphic control
         * and unregisters the initially installed listener
         */
        private void registerUIListener() {
            if (!(getGraphic() instanceof CheckBox)) throw new IllegalStateException("checkBox expected");
            graphicProperty().removeListener(graphicListener);
            ((CheckBox) getGraphic()).selectedProperty().addListener(
                    (observable, oldValue, newValue) -> listView.getSelectionModel().select(getItem()));
        }
    };
    cell.setSelectedStateCallback(Model::selectedProperty);
    return cell;
});

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=130983&siteId=1