I have a strange checkbox selection issue with nodes that have Children in a JavaFX 8 TreeView using CheckBoxTreeItems with custom CheckBoxTreeCell.
The Problem is that checkboxes of Nodes with children have to be clicked twice instead of once in order to be selected. Leaves require only a single click.
My CheckBoxTreeItems take Person Objects. I override the updateItem() Method in my CheckBoxTreeCells to set the value displayed to the name of the Person in the TreeCell. If I don't call setText() in my overidden updateItem Method, the TreeCell displays the default toString() Method of my Person object (which is not what I want) and all nodes behave a expected when selecting their checkboxes.
I do not want to change the default toString in class Person, so the only workaround I see is to write a Wrapper class for Person that returns the Persons name in its toString(). But I prefer resolving this issue properly rather than using a workaround!
Any ideas? Help would be much appreciated!
Here is the code I use:
class Person {
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
and
public class TreeUtilTest extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) throws Exception {
VBox vBox = new VBox();
TreeView<Person> treeView = new TreeView<>();
treeView.setCellFactory(p -> new CheckBoxTreeCell<Person>() {
@Override
public void updateItem(Person item, boolean empty) {
super.updateItem(item, empty);
if (item != null) {
setText(item.getName());
}
}
});
vBox.getChildren().add(treeView);
CheckBoxTreeItem<Person> treeRoot = new CheckBoxTreeItem<>();
treeRoot.setValue(new Person("Peter", 10));
treeRoot.setIndependent(true);
treeView.setRoot(treeRoot);
IntStream.range(0, 10).forEach(i -> {
CheckBoxTreeItem<Person> item = new CheckBoxTreeItem<>();
item.setValue(new Person("Friend", i));
item.setIndependent(true);
treeRoot.getChildren().add(item);
});
Scene scene = new Scene(vBox);
primaryStage.setScene(scene);
primaryStage.show();
}
}
If you look at CheckBoxTreeCell
class, you'll see it has the possibity to provide a callback that returns an ObservableValue<Boolean>
with the selection status of the item, and a StringConverter
.
So we can define these in our class:
final Callback<TreeItem<Person>, ObservableValue<Boolean>> getSelectedProperty =
(TreeItem<Person> item) -> {
if (item instanceof CheckBoxTreeItem<?>) {
return ((CheckBoxTreeItem<?>)item).selectedProperty();
}
return null;
};
final StringConverter<TreeItem<Person>> converter =
new StringConverter<TreeItem<Person>>() {
@Override
public String toString(TreeItem<Person> object) {
Person item=object.getValue();
return item.getName();
}
@Override
public TreeItem<Person> fromString(String string) {
throw new UnsupportedOperationException("Not supported yet.");
}
};
And now we just need to define the cell factory with these two parameters:
treeView.setCellFactory(p -> new CheckBoxTreeCell<>(getSelectedProperty,converter));
If you try this, you will see that you can select/unselect the root as well as the children with just one click.
EDIT
There's an easier solution, using the static method forTreeView
, where you don't need to provide the callback nor the converter:
treeView.setCellFactory(CheckBoxTreeCell.<Person>forTreeView());
But for this to work you just need to override toString
in Person
:
private class Person {
...
@Override
public String toString() {
return name;
}
}
And this explains why you had the problem in the first place: if you add the toString
method to your code, it will work also with your CheckBoxTreeCell
:
treeView.setCellFactory(p -> new CheckBoxTreeCell<Person>() {
@Override
public void updateItem(Person item, boolean empty) {
super.updateItem(item, empty);
if (item != null) {
setText(item.getName());
}
}
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With