我想创建一个树视图,其项目在左侧有一个标签和一个复选框。 我尝试写如下,但是当我单击按钮时,它打印 null。如果我可以得到不为 null 的图形,我可以得到其中的复选框。 我的目的是知道父项的复选框是否被选中。
package com.qy.tth.fxgui;
import javafx.application.Application;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class TreeCheck extends Application{
public static void main(String[] args) {
launch(null);
}
private TreeItem<String> item11;
@Override
public void start(Stage stage) throws Exception {
TreeItem<String> item1=new TreeItem<>("1");
TreeItem<String> item2=new TreeItem<>("2");
item11=new TreeItem<>("1-1");
TreeItem<String> itemRoot=new TreeItem<>("root");
item1.getChildren().add(item11);
itemRoot.getChildren().addAll(item1,item2);
TreeView<String> tv=new TreeView<>();
tv.setRoot(itemRoot);
tv.setCellFactory(tv1 -> new TreeCell<String>() {
private HBox hb;
{
Label lable = new Label("icon");
CheckBox cb=new CheckBox();
hb=new HBox();
hb.getChildren().addAll(lable,cb);
setGraphic(hb);
}
protected void updateItem(String value, boolean empty) {
super.updateItem(value, empty);
if (empty || value == null) {
setText("");
hb.setVisible(false);
}else{
setText(value);
hb.setVisible(true);
}
};
});
Button btn=new Button("show parent");
btn.setOnAction(e->showParent());
VBox vb=new VBox();
vb.getChildren().addAll(btn,tv);
Scene scene=new Scene(vb);
stage.setScene(scene);
stage.show();
}
private void showParent() {
TreeItem<String> item1 = item11.getParent();
Node graph = item1.getGraphic();
System.out.println(graph);
}
}
我不确定这样写是不是最好的方法,或者你可以完全给出自己的代码。 我的意图只是创建一棵带有标签和复选框的树,然后检测其父级是否被选中
我相信您的代码打印
null
的原因是因为TreeItem
上没有设置图形。您在 HBox
上设置图形(TreeCell
),然后尝试从 TreeItem
检索该图形。
话虽这么说,如果您同意
Label
位于 CheckBox
的 right,则无需创建自己的
TreeCell
实现。相反,您可以使用内置类来实现此目的:CheckBoxTreeCell
和 CheckBoxTreeItem
。
要设置
TreeView
,您可以使用以下代码:
TreeView<String> tree = new TreeView<>();
tree.setCellFactory(CheckBoxTreeCell.forTreeView());
静态方法
CheckBoxTreeCell.forTreeView()
假设根TreeItem
和所有后代都是CheckBoxTreeItem
的实例。默认情况下,CheckBoxTreeCell
的行为是:
independent
属性设置为 true
。该属性声明:
用于表示此独立状态的布尔属性 CheckBoxTreeItem。独立状态用于表示是否 对单个 CheckBoxTreeItem 的更改应该影响其状态 家长和孩子。
默认情况下,独立属性为 false,这意味着当 CheckBoxTreeItem 的状态更改为选定或不确定 属性,相关 CheckBoxTreeItems 的状态可能是 改变了。如果独立属性设置为 true,则状态 相关的 CheckBoxTreeItems 永远不会改变。
以这种方式设置
TreeView
可以轻松确定是否选择了父级,因为 CheckBoxTreeItem
有一个名为 BooleanProperty
的 selected
。
CheckBoxTreeItem<?> parent = (CheckBoxTreeItem<?>) item.getParent();
System.out.println(parent.isSelected());
在这种情况下,可以进行强制转换,因为我们知道所有
TreeItem
都将是 CheckBoxTreeItem
的实例。
由于您还希望将
Label
作为 TreeCell
的一部分,您只需将 CheckBoxTreeItem
的图形设置为带有所需文本的 Label
即可。 但请注意,这会将 Label
置于实际复选框的right 处。如果,如代码中所示,您希望将 Label
置于 CheckBox
的 left上,这会涉及更多一点。
在内部,
CheckBoxTreeCell
获取TreeItem
的图形并将其设置在内部CheckBox
上;然后它将 CheckBox
设置为其自身的图形。由于 CheckBox
的设计方式,将 CheckBox
的图形/文本放在实际复选框(带有复选标记的可视框)的另一侧并不简单(甚至可能不可能)。如果您想让 TreeItem
的图形位于 CheckBox
的左侧,则必须使用 HBox
之类的东西(就像您在代码中所做的那样)。如果您扩展 CheckBoxTreeCell
而不是直接扩展 TreeCell
,这可以比您的尝试更简单。
import javafx.beans.InvalidationListener;
import javafx.beans.WeakInvalidationListener;
import javafx.geometry.Pos;
import javafx.scene.control.CheckBox;
import javafx.scene.control.CheckBoxTreeItem;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeView;
import javafx.scene.control.cell.CheckBoxTreeCell;
import javafx.scene.layout.HBox;
import javafx.util.Callback;
public class CustomCheckBoxTreeCell<T> extends CheckBoxTreeCell<T> {
public static <T> Callback<TreeView<T>, TreeCell<T>> forTreeView() {
return treeView -> new CustomCheckBoxTreeCell<>();
}
private final InvalidationListener graphicListener = (obs) -> updateItem(getItem(), isEmpty());
private final WeakInvalidationListener weakGraphicListener = new WeakInvalidationListener(graphicListener);
public CustomCheckBoxTreeCell() {
// This again assumes that all TreeItems will actually be
// instances of CheckBoxTreeItem
super(item -> ((CheckBoxTreeItem<?>) item).selectedProperty());
treeItemProperty().addListener((observable, oldItem, newItem) -> {
if (oldItem != null) {
oldItem.graphicProperty().removeListener(weakGraphicListener);
}
if (newItem != null) {
newItem.graphicProperty().addListener(weakGraphicListener);
}
});
}
@Override
public void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
if (!empty && getTreeItem().getGraphic() != null) {
CheckBox cBox = (CheckBox) getGraphic();
cBox.setGraphic(null);
HBox hBox = new HBox(getTreeItem().getGraphic(), cBox);
hBox.setAlignment(Pos.CENTER_LEFT);
setGraphic(hBox);
}
}
}
然后您可以像这样设置单元工厂:
tree.setCellFactory(CustomCheckBoxTreeItem.forTreeView());
一些注意事项:
这取决于
CheckBoxTreeCell
的内部实现方式(特别是 Java 10,但也可能适用于 Java 8)。如果他们将来改变实现,这可能会失败。这需要您将
Label
设置为TreeItem
的图形,而不是仅在TreeCell
中使用它。如果您不想这样做,请删除使用 TreeItem
图形的行为并将其替换为内部 Label
。这将允许您删除 graphicListener
和 weakGraphicListener
的使用。如果您还想显示 TreeItem
的图形,那么您还需要删除 cBox.setGraphic(null)
。我不会尝试缓存
HBox
(或者如果您根据注释 #2 进行更改,则可能会缓存 Label
)。相反,我每次都会创建一个新的HBox
。更改代码应该很简单,这样它就可以缓存 HBox
,但是,如果您这样选择。如果您不想使用
CheckBoxTreeItem
但更愿意外部化是否选择了某个项目,则应该查看以下方法:
如果您仍然想要 CheckBox
的
left上的图形,您还必须在此处扩展
CheckBoxTreeCell
。与上面如何实现 CustomCheckBoxTreeCell
的唯一真正区别是,您必须提供一种方法来将与上述两种方法一起使用的 Callback
设置为 CustomCheckBoxTreeCell
。您可以通过公开采用 Callback
的构造函数或通过在单元工厂内设置 selectedStateCallback
属性来实现此目的。