JavaFX 8 TreeView showing CheckBoxTreeItem with custom CheckBoxTreeCell - Checkbox Selection issue


Question

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();
}

}
1
4
8/22/2014 7:34:33 AM

Accepted Answer

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());
    }
 }
});
7
8/22/2014 2:17:04 PM

Licensed under: CC-BY-SA with attribution
Not affiliated with: Stack Overflow
Icon