Java 如何为CheckBoxTreeItem手动调用TreeCell#updateItem以便应用CSS?

Java 如何为CheckBoxTreeItem手动调用TreeCell#updateItem以便应用CSS?,java,javafx,Java,Javafx,我在JavaFX中有一个标准的TreeView,里面有CheckBoxTreeItem。我安装了一个侦听器,以查看某人何时选中/取消选中复选框。但我希望当有人选中/取消选中某个复选框时,我会触发该复选框项的parent updateItem方法并更改其CSS(例如,如果为父项选择了3个或更多子项,则将其颜色更改为红色,否则为绿色) 我该怎么做 rootItem.addEventHandler(CheckBoxTreeItem.checkBoxSelectionChangedEvent(), e

我在JavaFX中有一个标准的TreeView,里面有CheckBoxTreeItem。我安装了一个侦听器,以查看某人何时选中/取消选中复选框。但我希望当有人选中/取消选中某个复选框时,我会触发该复选框项的parent 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
    }
});
rootItem.addEventHandler(CheckBoxTreeItem.checkBoxSelectionChangedEvent(),e->{
if(例如getTreeItem().isLeaf()){
TreeItem TreeItem=(TreeItem)e.getTreeItem();
CheckBoxTreeItem parentItem=(CheckBoxTreeItem)treeItem.getParent();
//如何为parentItem调用重新绘制????
}
});
setCellFactory(p->new CheckBoxTreeCell(){
@凌驾
public void updateItem(字符串项,布尔值为空){
super.updateItem(项,空);
//在此处切换父级的CSS
}
});

您不需要手动更改,您可以使用:

CheckBoxTreeCell似乎没有内置的“checked”伪类,您可以添加一个“checked”伪类,并在检查树单元时应用它。然后你可以这样称呼它:

.check-box-tree-cell:three-children:checked {
    -fx-background-color: green;
}
我同意使用。但是,您不应该试图手动调用
updateItem
。相反,只需添加一个
EventHandler
,以侦听“复选框选择已更改”事件。当直接子级中发生事件时,父级应根据(使用您的示例)是否选择了3+个子级来更新伪类

下面的示例还包括一个“分支”
pseudo类
,因此您可以在CSS文件中区分分支和叶:

.check-box-tree-cell:three-children {
    -fx-background-color: red;
}
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.

}

在评论中回答问题:

  • 如果我们注意取消订阅,为什么要把每个听众都包在一个弱听众里面呢

    以减少内存泄漏的机会。例如,如果您扔掉
    TreeView
    (没有清除
    root
    属性),但在某个地方保留对
    树项的引用,那么非弱处理程序/侦听器将在内存中保留
    树项
    树项

  • 为什么要监听叶子的变化,什么时候调用它

    处理动态添加和/或删除
    TreeItem
    s的情况。
    TreeItem
    是叶当且仅当其
    子项列表为空时。如果添加了一个项,并且叶现在变成了一个分支,那么我们需要更新
    分支
    伪类,以便应用正确的CSS。如果项目被删除,分支变为叶,则相同

    这可能与您的用例有关,也可能与您的用例无关。如果没有,请随意删除实现的这一部分

  • 您可以选中
    getTreeItem()!=事件。getTreeItem())
    在复选框中选中处理程序。为什么?选中/取消选中复选框时将调用此选项

    当您(取消)选中一个
    CheckBoxTreeItem
    时,它会触发一个事件。此事件从(未)选中的
    CheckBoxTreeItem
    开始。从那里,它向上(即气泡)项层次结构,一直到根。在每个项目上,都将调用任何已注册的处理程序。虽然如果事件已被消耗,它不会继续到下一个父项

    我们添加处理程序的原因是要侦听(未)检查的所有子项,但只侦听直接子项。我们不关心任意深度子体中的更改,也不关心处理程序注册到的项

    由于我们只关心直接子女的变化,我们需要确保我们只对所述子女引发的事件作出反应。由于事件首先由(未)检查的项处理,因此我们不需要在第一个处理程序中执行任何操作。我们可以通过测试包含
    TreeCell
    TreeItem
    是否与触发事件的相同,并且该方法返回导致触发事件的项(即(未)选中的项)。如果它们是同一个实例,则不执行任何操作,让事件冒泡到父级

    现在父项的处理程序正在处理事件。这意味着
    getTreeItem()!=event.getTreeItem()
    将返回
    true
    ,我们输入
    if
    块。这将导致在必要时更新伪类的状态。然后我们消费事件,这样它就不会冒泡到下一个父级;这有效地使处理程序只侦听来自直接子级的事件

    请注意,如果父项当前未显示在树中,则它将不是单元格的一部分。如果它不是单元格的一部分,则不会向其添加处理程序。因此,任何未显示的项目都不会受到任何影响。这没关系,因为我们正在更新的所有内容都是纯视觉的;如果某个项目没有显示,则没有可更新的视觉效果


  • 我记得,如果我没有弄错的话:选中复选框时不会调用updateItem。这就是为什么我想从支票处理程序那里做点什么。有什么想法吗?代码是否适合您?@adragomir为什么不将您的选择更改事件处理程序添加到单元格的项中?单元格将接收已更改子项的事件,并可以相应地作出反应。别忘了根据需要删除处理程序(可能使用弱处理程序)。@Slaw:如果我这样做,那么所有创建的单元格都将收到更改事件并造成严重破坏。我的意思是,我将在单元格的c-tor中传输该属性。@adragomir在任何给定的时间,只有~10-50个单元格显示;这意味着您一次只能注册10-50个事件处理程序。此外,您可以使用该事件来阻止它一直冒泡到根项。Slaw I's in country side atm,我将在明天测试并接受它
    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.
    
    }