ChangeListener в TreeTableView CheckBoxTreeTableCell срабатывает много раз при каждом нажатии - PullRequest
0 голосов
/ 24 сентября 2018

У меня есть простой класс модели Person, и я использую его для заполнения TreeTableView.Я хочу иметь один столбец с флажком и хочу заполнить другой TreeTableView данными, которые я проверяю в первой таблице.Кажется разумным, но моя проблема в том, что ChangeListener, добавленный в BooleanProperty через SetCellValueFactory, запускается много раз, от 4 до 8 раз случайно (или, как мне кажется, случайно, я не проверял это на самом деле).

Основной класс:

import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.TreeTableView;
import javafx.scene.control.cell.CheckBoxTreeTableCell;
import javafx.scene.control.cell.TreeItemPropertyValueFactory;
import javafx.stage.Stage;

public class ChangeListenerBug extends Application {

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

@Override
public void start(Stage stage) throws Exception {

    // create the treeTableView and colums
    TreeTableView<Person> ttv = new TreeTableView<Person>();
    TreeTableColumn<Person, String> colName = new TreeTableColumn<>("Name");
    TreeTableColumn<Person, Boolean> colSelected = new TreeTableColumn<>("Selected");
    ttv.getColumns().add(colName);
    ttv.getColumns().add(colSelected);
    ttv.setShowRoot(false);
    ttv.setEditable(true);
    colSelected.setEditable(true);

    // set the columns
    colName.setCellValueFactory(new TreeItemPropertyValueFactory<>("name"));
    colSelected.setCellFactory(CheckBoxTreeTableCell.forTreeTableColumn(colSelected));
    colSelected.setCellValueFactory(cellData -> {
        // binding the cell property with the model
        BooleanProperty selected = cellData.getValue().getValue().selectedProperty();
        // listening for a change in the property
        selected.addListener((obs, oldVal, newVal) -> {
            System.out.println(newVal);// WHY IS THIS GETTING CALLED MULTIPLE TIMES
        });
        return selected;
    });

    // creating treeItems to populate the treetableview
    TreeItem<Person> rootTreeItem = new TreeItem<Person>();
    rootTreeItem.getChildren().add(new TreeItem<Person>(new Person("Name 1")));
    rootTreeItem.getChildren().add(new TreeItem<Person>(new Person("Name 2")));
    ttv.setRoot(rootTreeItem);

    // build and show the window
    Group root = new Group();
    root.getChildren().add(ttv);
    stage.setScene(new Scene(root, 300, 300));
    stage.show();
}
}

Персона Класс:

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

public class Person {
private StringProperty name;
private BooleanProperty selected;

public Person(String name) {
    this.name = new SimpleStringProperty(name);
    selected = new SimpleBooleanProperty(false);
}

public StringProperty nameProperty() {return name;}

public BooleanProperty selectedProperty() {return selected;}

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

public void setSelected(boolean selected){this.selected.set(selected);}
}

Вывод, когда я отмечаю флажок (то же самое, но ложь, когда я отменяю выбор):

true
true
true
true
true

Ответы [ 2 ]

0 голосов
/ 25 сентября 2018

Делаем шаг назад, чтобы удовлетворить ваши основные требования:

Я хочу иметь один столбец с флажком и хочу заполнить другой TreeTableView данными, которые я проверяю в первой таблице

Альтернативой ручному прослушиванию (как в вашей попытке и хорошем ответе, предоставленном VGR ) является косвенное связывание содержимого другого treeTable с отфильтрованной версией первого, как указано в James_D .

Применимо к вашему контексту (только для одного уровня):

public class TreeTableDriveTreeTableWithSelected extends Application {

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

    FilteredList<TreeItem<Person>>  targetItems;
    @Override
    public void start(Stage stage) throws Exception {

        // create the treeTableView and colums
        TreeTableView<Person> source = createTreeTable(true);
        // creating treeItems to populate the treetableview
        TreeItem<Person> sourceRoot = createRootItem();
        source.setRoot(sourceRoot);

        TreeTableView<Person> target = createTreeTable(false);
        TreeItem<Person> targetRoot = new TreeItem<>();
        target.setRoot(targetRoot);

        // backing list for filteredList, configured to fire updates on change
        // of selected
        ObservableList<TreeItem<Person>> backingTargetItems = FXCollections.observableArrayList(
                item -> new ObservableValue[] {item.getValue().selectedProperty()} 
        );

        // fill backing list with items of source
        // note: treeItems can't be shared across trees, so need to create with same value
        sourceRoot.getChildren()
            .forEach(tp -> backingTargetItems.add(new TreeItem<>(tp.getValue())));
        // filter the backing list by its selected property
        // this must be a strong reference, otherwise the binding is garbage-collected
        targetItems = new FilteredList<>(backingTargetItems, 
                p -> p.getValue().isSelected()
        );
        // bind content of target root to filtered list
        Bindings.bindContent(targetRoot.getChildren(), targetItems);

        // build and show the window
        HBox root = new HBox(10);
        root.getChildren().addAll(source, target);
        stage.setScene(new Scene(root, 300, 300));
        stage.show();
    }

    protected TreeItem<Person> createRootItem() {
        TreeItem<Person> rootTreeItem = new TreeItem<Person>();
        rootTreeItem.getChildren()
                .add(new TreeItem<Person>(new Person("Name 1")));
        rootTreeItem.getChildren()
                .add(new TreeItem<Person>(new Person("Name 2")));
        return rootTreeItem;
    }

    protected TreeTableView<Person> createTreeTable(boolean withSelected) {
        TreeTableView<Person> ttv = new TreeTableView<Person>();
        TreeTableColumn<Person, String> colName = new TreeTableColumn<>("Name");
        colName.setCellValueFactory(new TreeItemPropertyValueFactory<>("name"));
        ttv.getColumns().add(colName);
        ttv.setShowRoot(false);

        if (withSelected) {
            ttv.setEditable(true);
            // column editable is true by default
            //  colSelected.setEditable(true);

            TreeTableColumn<Person, Boolean> colSelected = new TreeTableColumn<>(
                    "Selected");
            ttv.getColumns().add(colSelected);
            // set the columns
            // updating the property
            colSelected.setCellFactory(
                    CheckBoxTreeTableCell.forTreeTableColumn(colSelected));
            colSelected.setCellValueFactory(new TreeItemPropertyValueFactory<>("selected"));
        }
        return ttv;
    }

}
0 голосов
/ 24 сентября 2018

cellData.getValue().getValue().selectedProperty() не создает новый BooleanProperty.Для любого данного объекта Person он снова и снова возвращает один и тот же BooleanProperty, и вы продолжаете добавлять еще одного прослушивателя к тому же BooleanProperty, снова и снова.

Не добавляйте прослушиватель в свой cellValueFactory.Добавьте слушателя один раз к каждому объекту Person.

Поскольку ваше дерево имеет глубину только один уровень (кроме корня), вы можете просто пройти по ним:

rootTreeItem.getChildren().forEach(item -> {
    BooleanProperty selected = item.getValue().selectedProperty();

    selected.addListener((obs, oldVal, newVal) -> {
        System.out.println(newVal);
    });
});

Если бы дерево имело несколько уровней, вам нужен рекурсивный метод:

    listenForSelection(rootTreeItem,
        selected -> System.out.println(selected));

// ...

private void listenForSelection(TreeItem<Person> treeItem,
                                Consumer<Boolean> listener) {

    BooleanProperty selected = treeItem.getValue().selectedProperty();

    selected.addListener(
        (obs, oldVal, newVal) -> listener.accept(newVal));

    treeItem.getChildren().forEach(item -> listenForSelection(item, listener));
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...