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

У меня есть стандартный TreeView в JavaFX с CheckBoxTreeItem в нем. Я установил прослушиватель, чтобы видеть, когда кто-то проверяет/снимает флажок. Но я хочу, чтобы, когда кто-то устанавливает/снимает флажок, я запускал родительский метод updateItem этого элемента checkboxitem и изменял его 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
    }
});
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
2
0
368
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

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

М. С. Насколько я помню, если не ошибаюсь: updateItem не вызывается при установленном флажке. Вот почему я хотел сделать что-то из обработчика проверки. Есть предположения? Код работает для вас?

adragomir 27.05.2019 21:13

@adragomir Почему бы не добавить обработчик события изменения выбора к элементу ячейки? Ячейка будет получать события для измененных дочерних элементов и может реагировать соответствующим образом. Не забудьте удалить соответствующий обработчик (и, возможно, использовать слабые обработчики).

Slaw 27.05.2019 23:58

@Slaw: если я это сделаю, то все созданные ячейки получат событие изменения и вызовут хаос. Я имею в виду, что передам это свойство центру ячейки.

adragomir 28.05.2019 10:42

@adragomir В любой момент времени будет отображаться только ~ 10-50 ячеек; что означает, что у вас будет только ~ 10-50 обработчиков событий, зарегистрированных одновременно. Кроме того, вы можете использовать событие, чтобы остановить его восхождение до корневого элемента.

Slaw 28.05.2019 10:58
Ответ принят как подходящий

Я согласен с ответ М.С. относительно использования 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. Why wrapping every listener inside a weak one if we take care to unsubscribe it?

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

  2. Why are you listening for leaf changes and when does it gets called?

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

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

  3. You check getTreeItem() != event.getTreeItem()) in the checkbox checked handler. Why? This will be called when a checkbox gets checked/ unchecked.

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

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

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

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

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

Slaw Я нахожусь в сельской местности, я проверю это завтра и приму ваш ответ, если все в порядке.

adragomir 28.05.2019 16:02

Отличный ответ, я попытался привести его к правильному способу реализации этой функции.

Miss Chanandler Bong 28.05.2019 17:29

Слав, работает отлично! Я постараюсь полностью понять ваш код!

adragomir 29.05.2019 10:48

@adragomir Рад, что смог помочь. Если в моем коде есть что-то конкретное, что вам нужно понять, дайте мне знать.

Slaw 29.05.2019 11:02

@Slaw Вы сами поняли это из исходного кода javafx или прочитали какую-нибудь хорошую книгу, которую можете порекомендовать?

adragomir 29.05.2019 11:07

@adragomir Давным-давно я прочитал несколько книг, что-то вроде «Pro JavaFX» и «Master JavaFX Controls». Помимо этого, я в основном просто читаю некоторые учебные пособия, документацию (например, Javadoc, Справочное руководство по CSS для JavaFX и Введение в FXML) и много вопросов о переполнении стека. Всякий раз, когда эти ресурсы меня не устраивают или мне просто любопытно, я изучаю исходный код JavaFX.

Slaw 29.05.2019 11:42

@Slaw Несколько вопросов, если вы любезны: 1. Зачем заворачивать каждого слушателя в слабого, если мы позаботимся о том, чтобы отписаться от него? 2. Почему вы прослушиваете изменения листьев и когда они вызываются? 3. Вы проверяете getTreeItem() != event.getTreeItem()) в обработчике Checkbox Checked. Почему? Это будет вызываться, когда флажок установлен/снят.

adragomir 29.05.2019 12:29

@adragomir Обновил мой ответ (внизу)

Slaw 29.05.2019 13:15

Другие вопросы по теме