Как вручную вызвать TreeCell # updateItem для CheckBoxTreeItem, чтобы мы могли применить CSS? - PullRequest
2 голосов
/ 27 мая 2019

У меня есть стандартный TreeView в JavaFX с CheckBoxTreeItem в нем. Я установил прослушиватель, чтобы видеть, когда кто-то проверяет / снимает флажок. Но я хочу, чтобы, когда кто-то устанавливал / снимал флажок, я запускал метод updateItem родительского элемента этого флажка и изменял его CSS (например, если для родительского элемента выбрано 3 или более дочерних элемента, измените его цвет на красный, в противном случае - зеленый).

Как я могу это сделать?

rootItem.addEventHandler(CheckBoxTreeItem.checkBoxSelectionChangedEvent(), e -> {
        if (e.getTreeItem().isLeaf()) {
            TreeItem<String> treeItem = (TreeItem) e.getTreeItem();
            CheckBoxTreeItem<String> parentItem = (CheckBoxTreeItem<String>) treeItem.getParent();
            // how to call repaint for the parentItem????
        }
    });

treeView.setCellFactory(p -> new CheckBoxTreeCell<>() {
    @Override
    public void updateItem(String item, boolean empty) {
        super.updateItem(item, empty);
        // toggle the parent's CSS here
    }
});

Ответы [ 2 ]

2 голосов
/ 28 мая 2019

Я согласен с ответом MS относительно использования PseudoClass.Тем не менее, вы не должны пытаться вручную вызвать updateItem.Вместо этого просто добавьте EventHandler, чтобы прослушивать события «флажок изменен».Когда событие происходит в прямом дочернем элементе, родитель должен обновить псевдокласс на основе (на вашем примере) того, выбраны ли 3+ дочерние элементы.

Вот пример, который также включает в себя «ветвь» PseudoClass, чтобы вы могли различить ветвь и лист в файле CSS:

import javafx.beans.InvalidationListener;
import javafx.beans.WeakInvalidationListener;
import javafx.css.PseudoClass;
import javafx.event.EventHandler;
import javafx.event.WeakEventHandler;
import javafx.scene.control.CheckBoxTreeItem;
import javafx.scene.control.CheckBoxTreeItem.TreeModificationEvent;
import javafx.scene.control.cell.CheckBoxTreeCell;

public class MyCheckBoxTreeCell<T> extends CheckBoxTreeCell<T> {

    private static final PseudoClass BRANCH = PseudoClass.getPseudoClass("branch");
    private static final PseudoClass THREE_CHILDREN_SELECTED = PseudoClass.getPseudoClass("three-children-selected");

    // event handler to listen for selection changes in direct children
    private final EventHandler<TreeModificationEvent<T>> handler = event -> {
        /*
         * Event starts from the source TreeItem and bubbles up the to the root. This means
         * the first time getTreeItem() != event.getTreeItem() will be the source TreeItem's
         * parent. We then consume the event to stop it propagating to the next parent.
         */
        if (getTreeItem() != event.getTreeItem()) {
            event.consume();
            updatePseudoClasses();
        }
    };
    private final WeakEventHandler<TreeModificationEvent<T>> weakHandler = new WeakEventHandler<>(handler);

    // Used to listen for the "leaf" property of the TreeItem and update the BRANCH pseudo-class
    private final InvalidationListener leafListener = observable -> updatePseudoClasses();
    private final WeakInvalidationListener weakLeafListener = new WeakInvalidationListener(leafListener);

    public MyCheckBoxTreeCell() {
        getStyleClass().add("my-check-box-tree-cell");

        // add listener to "treeItem" property to properly register and unregister
        // the "leafListener" and "handler" instances.
        treeItemProperty().addListener((observable, oldValue, newValue) -> {
            if (oldValue != null) {
                oldValue.leafProperty().removeListener(weakLeafListener);
                oldValue.removeEventHandler(CheckBoxTreeItem.checkBoxSelectionChangedEvent(), weakHandler);
            }
            if (newValue != null) {
                newValue.leafProperty().addListener(weakLeafListener);
                newValue.addEventHandler(CheckBoxTreeItem.checkBoxSelectionChangedEvent(), weakHandler);
            }
            updatePseudoClasses();
        });
    }

    private void updatePseudoClasses() {
        /*
         * Assumes the use of CheckBoxTreeItem for each TreeItem in the TreeView.
         *
         * This code is not the most efficient as it will recalculate both the BRANCH and
         * THREE_CHILDREN_SELECTED pseudo-classes each time either possibly changes.
         */
        var item = (CheckBoxTreeItem<T>) getTreeItem();
        if (item == null) {
            pseudoClassStateChanged(BRANCH, false);
            pseudoClassStateChanged(THREE_CHILDREN_SELECTED, false);
        } else {
            pseudoClassStateChanged(BRANCH, !item.isLeaf());

            int selected = 0;
            for (var child : item.getChildren()) {
                // only need to know if *at least* 3 children are selected
                if (((CheckBoxTreeItem<T>) child).isSelected() && ++selected >= 3) {
                    break;
                }
            }
            pseudoClassStateChanged(THREE_CHILDREN_SELECTED, selected >= 3);
        }
    }

    // No need to override "updateItem(T,boolean)" as CheckBoxTreeCell provides
    // the necessary implementation which can be customized via the StringConverter
    // property.

}

И тогда ваш файл CSS может выглядеть следующим образом:

.my-check-box-tree-cell:branch {
    -fx-background-color: green;
    -fx-text-fill: white;
}

.my-check-box-tree-cell:branch:three-children-selected {
    -fx-background-color: red;
    -fx-text-fill: white;
}

Ответы на вопросы в комментариях:

  1. Зачем оборачивать каждого слушателя в слабого, если мы хотим отменить его подписку?

    Чтобы уменьшитьшанс утечки памяти.Например, если вы отбрасываете TreeView (не очистив свойство root), но сохраняете где-то ссылки на TreeItem s, тогда неслабый обработчик / слушатель будет содержать TreeCell s и TreeView в памяти.

  2. Почему вы слушаете листовые изменения и когда он вызывается?

    Для обработки случая, когда TreeItem ы динамически добавляются и / или удаляются.TreeItem является листом тогда и только тогда, когда его список children пуст.Если элемент добавлен, и теперь лист становится веткой, нам нужно обновить псевдокласс BRANCH, чтобы применить правильный CSS.То же самое, если элемент удален, а ветвь становится листом.

    Это может относиться или не относиться к вашему варианту использования.Если нет, то не стесняйтесь удалить эту часть реализации.

  3. Вы отметили getTreeItem() != event.getTreeItem()) в обработчике, отмеченном флажком.Зачем?Это будет вызвано, когда флажок будет установлен / снят.

    Когда вы (не) проверяете CheckBoxTreeItem, это вызывает событие.Это событие начинает свое путешествие с CheckBoxTreeItem, который был (не) отмечен.Оттуда он перемещается вверх (т.е. пузыри) по иерархии предметов вплоть до корня.Для каждого элемента будут вызываться любые зарегистрированные обработчики.Хотя, если событие используется, оно не переходит к следующему родительскому элементу.

    Причина, по которой мы добавляем обработчик, заключается в том, чтобы прослушивать любые (1060 * дочерние ) проверки (не), но только прямые дети .Мы не заботимся ни об изменениях в произвольно глубоких потомках, ни об элементе, к которому был зарегистрирован обработчик.

    Поскольку нас интересуют только изменения в непосредственных дочерних элементах, нам нужно убедиться, что мы реагируем только на события, инициированные указанными дочерними элементами.,Поскольку событие обрабатывается первым элементом, который был (не) проверен, нам не нужно ничего делать в этом первом обработчике.Мы можем сделать это, проверив, является ли TreeItem содержащего TreeCell тем же, который вызвал событие, и метод TreeModificationEvent#getTreeItem() возвращает элемент, вызвавший событие (т.е.элемент, который был (не) проверен).Если это один и тот же экземпляр, ничего не делайте, и пусть событие всплывет к родителю.

    Теперь обработчик родительского элемента обрабатывает событие.Это означает, что getTreeItem() != event.getTreeItem() вернет true, и мы войдем в блок if.Это вызывает обновление, если необходимо, состояния псевдоклассов.Затем мы используем событие, чтобы оно не всплывало до следующего родителя;это эффективно заставляет обработчик слушать только события от direct children .

    Обратите внимание, что если родительский элемент в данный момент не отображается в дереве, он не будет частью ячейки.Если он не является частью ячейки, к нему не будет добавлен обработчик.Таким образом, любые не отображаемые элементы не будут затронуты этим.Это нормально, поскольку все, что мы обновляем, чисто визуально;если элемент не отображается, то нет визуальных элементов для обновления.

0 голосов
/ 27 мая 2019

Вам не нужно менять его вручную, вы можете использовать PseudoClass :

private PseudoClass threeChildrenClass = PseudoClass.getPseudoClass("three-children");

tree.setCellFactory(param -> new CheckBoxTreeCell<String>() {
    @Override
    public void updateItem(String item, boolean empty) {
        super.updateItem(item, empty);
        if (item == null || empty) {
            setText(null);
            setGraphic(null);
        } else {
            setText(item);
            // Change the class based on the number of parent items
            pseudoClassStateChanged(threeChildrenClass, hasThreeChildren(item));
        }
    }
});

В вашем файле CSS:

.check-box-tree-cell:three-children {
    -fx-background-color: red;
}

Похоже, что CheckBoxTreeCell не имеет встроенного «проверенного» псевдокласса, вы можете добавить «проверенный» псевдокласс и применить его при проверке ячейки дерева. Тогда вы можете назвать это так:

.check-box-tree-cell:three-children:checked {
    -fx-background-color: green;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...