JavaFx: выбор ListView CheckBoxListCell - PullRequest
2 голосов
/ 27 июня 2019

У меня проблема с ListView при использовании CheckBoxListCell. Когда я отмечаю / снимаю отметку с элемента, элемент не выделяется / не фокусируется, что и следовало ожидать, поскольку CheckBox также является частью элемента, а не только текстовой частью.

Вот простой код, который вы можете проверить.

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>

Как вы можете видеть, есть решение, которое я нашел, или лучше сказать, грязный обходной путь, но мне не очень нравится, потому что оно вызывается при каждом updateItem и добавляет слушателя n раз, что не так здорово.

Любые другие идеи / решения, как мне добиться, чтобы, когда я отмечал / снимал флажок со списком, весь элемент был выбран / сфокусирован.

1 Ответ

2 голосов
/ 27 июня 2019

Этот ответ охватывает только часть вопроса: как убедиться, что слушатель свойства графической ячейки регистрируется только один раз (если у нас нет контроля над тем, когда установлена ​​графическая часть).

Шаги:

  1. определить InvalidationListener, который устанавливает «настоящий» слушатель (примечание: должно быть полем, которое можно будет удалить позже)
  2. зарегистрировать слушателя на графическом свойстве ячейки во время создания
  3. Реализация регистрации "реального" метода для удаления исходного графического прослушивателя в дополнение к установке всего необходимого

Фрагмент кода (будьте осторожны: прослушивание выбранного свойства не является хорошей идеей, потому что оно срабатывает при изменении данных, а не только когда пользователь нажимает флажок!):

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;
});
...