I want to create a treeview whose item has a lable and a checkbox on the left. I try to write as below,but when I click the button,it prints null.If I can get the graphic not null,I can get the checkbox in it. My purpose is to konw whether the checkbox of the parent item is checked.
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);
}
}
And I am not sure is it the best way to write like this,or you can give you own code completely. My intent is just create a tree with a label and checkbox,then detect whether its parent is checked
The reason your code is printing null
, I believe, is because there is no graphic set on the TreeItem
. You set the graphic (an HBox
) on the TreeCell
and then later try to retrieve that graphic from the TreeItem
.
That being said, if you are okay with the Label
being to the right of the CheckBox
there is no need to create your own TreeCell
implementation. Instead, you can use the built in classes for this: CheckBoxTreeCell
and CheckBoxTreeItem
.
To set up the TreeView
you'd use the following code:
TreeView<String> tree = new TreeView<>();
tree.setCellFactory(CheckBoxTreeCell.forTreeView());
The static method CheckBoxTreeCell.forTreeView()
assumes that the root TreeItem
and all descendants will be instances of CheckBoxTreeItem
. Be default, the behavior of CheckBoxTreeCell
is:
If you don't want this behavior you should set the independent
property to true
. This property states:
A BooleanProperty used to represent the independent state of this CheckBoxTreeItem. The independent state is used to represent whether changes to a single CheckBoxTreeItem should influence the state of its parent and children.
By default, the independent property is false, which means that when a CheckBoxTreeItem has state changes to the selected or indeterminate properties, the state of related CheckBoxTreeItems will possibly be changed. If the independent property is set to true, the state of related CheckBoxTreeItems will never change.
Setting your TreeView
up this way makes it easy to determine if a parent is selected because CheckBoxTreeItem
has a BooleanProperty
named selected
.
CheckBoxTreeItem<?> parent = (CheckBoxTreeItem<?>) item.getParent();
System.out.println(parent.isSelected());
In this case, casting it okay because we know all the TreeItem
s will be instances of CheckBoxTreeItem
.
Since you also want a Label
as a part of the TreeCell
you can simply set the graphic of the CheckBoxTreeItem
to a Label
with the desired text. Note, however, that this will put the Label
to the right of the actual check box. If, as indicated in your code, you want the Label
to the left of the CheckBox
this gets a little more involved.
Internally, a CheckBoxTreeCell
takes the graphic of the TreeItem
and sets it on the internal CheckBox
; then it sets the CheckBox
as the graphic of itself. Due to the way CheckBox
is designed it is not simple (probably not even possible) to put the graphic/text of the CheckBox
on the other side of the actual check box (the visual box with the check). If you want to have the TreeItem
s graphic be on the left side of the CheckBox
you have to use something like an HBox
(as you were doing in your code). This can be done simpler than your attempt if you extend CheckBoxTreeCell
rather than TreeCell
directly.
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);
}
}
}
You would then set the cell factory like so:
tree.setCellFactory(CustomCheckBoxTreeItem.forTreeView());
A few notes:
This relies on how CheckBoxTreeCell
is implemented internally (specifically Java 10, but probably works with Java 8 as well). If they change the implementation in the future this could break.
This requires you to set the Label
as the TreeItem
s graphic rather than using it in the TreeCell
only. If you don't want to do this, then remove the behavior that uses the TreeItem
s graphic and replace it with an internal Label
. This would allow you to remove the use of graphicListener
and weakGraphicListener
. If you want to display the graphic of the TreeItem
as well then you would also remove cBox.setGraphic(null)
.
I make no attempt to cache the HBox
(or the possible Label
if you change things based on Note #2). Instead, I just create a new HBox
every time. It should be simple to change the code so it does cache the HBox
, however, if you so choose.
If you don't want to use CheckBoxTreeItem
but would rather externalize whether or not an item is selected, you should look at these methods:
If you still want the graphic on the left of the CheckBox
you'll have to extend CheckBoxTreeCell
here as well. The only real difference to how CustomCheckBoxTreeCell
is implemented above is you'd have to provide a way to set the Callback
you would have used with the above two methods to the CustomCheckBoxTreeCell
. You can do this by exposing a constructor that takes the Callback
or by setting the selectedStateCallback
property inside the cell factory.