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