In this article I will demonstrate how to filter data in a JavaFX TreeView
using a FilteredList
.
Introduction
To populate a TreeView
with data it is necessary to create a hierarchy of tree items and set the root TreeItem
for the TreeView
. To filter our data we could directly manipulate TreeItem#getChildren
but that would be cumbersome. Especially since JavaFX already provides classes that can help: To filter an OberservableList
you can use a FilteredList
and set a Predicate
on the list to control the filter. So our goal is to use this class together with TreeItem
.
We also want to be able to set the predicate only on the root tree item, so that we can perform the filtering in a single place. Usually we also don’t want to hide folders that still have children even if they wouldn’t match the predicate. Ideally we would like the tree item to automatically check whether it has any children and only apply the predicate when it is a leaf.
Implementation
The complete implementation can be viewed here.
The Filterable Tree Item
To start lets marry TreeItem
and FilteredList
in a new subclass: FilterableTreeItem
. In the constructor we will create a FilteredList
using a new observable array list as its source. Since overriding getChildren
will not work properly in Java 8 (see JDK-8089158), we need to use a hack to set the private field TreeItem#children
to this filtered list instead of the original source. Since a FilteredList
is unmodifiable we will provide a new method to access the source list directly: getInternalChildren()
.
public class FilterableTreeItem<T> extends TreeItem<T> { final private ObservableList<TreeItem<T>> sourceList; private FilteredList<TreeItem<T>> filteredList; public FilterableTreeItem(T value) { super(value); this.sourceList = FXCollections.observableArrayList(); this.filteredList = new FilteredList<>(this.sourceList); setHiddenFieldChildren(this.filteredList); } protected void setHiddenFieldChildren(ObservableList<TreeItem<T>> list) { try { Field childrenField = TreeItem.class.getDeclaredField("children"); //$NON-NLS-1$ childrenField.setAccessible(true); childrenField.set(this, list); Field declaredField = TreeItem.class.getDeclaredField("childrenListener"); //$NON-NLS-1$ declaredField.setAccessible(true); list.addListener((ListChangeListener<? super TreeItem<T>>) declaredField.get(this)); } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { throw new RuntimeException("Could not set TreeItem.children", e); //$NON-NLS-1$ } } public ObservableList<TreeItem<T>> getInternalChildren() { return this.sourceList; } }
Edit: The implementation details have changed over time. We don’t use reflection anymore but Bindings.bindContent instead. I recommend you don’t use the code above but to use the current version that is included in efxclipse. The full name of the class is org.eclipse.fx.ui.controls.tree.FilterableTreeItem
The Predicate
Sometimes you might want to adjust the filter depending on the hierarchy you are in. In this case it would be nice to have the current TreeItem
available when evaluating Predicate#test
. So let’s define a new interface and add a convenience method to convert a classic Predicate
:
@FunctionalInterface public interface TreeItemPredicate<T> { boolean test(TreeItem<T> parent, T value); static <T> TreeItemPredicate<T> create(Predicate<T> predicate) { return (parent, value) -> predicate.test(value); } }
To use this interface in FilterableTreeItem
we define an ObjectPropery
to set the TreeItemPredicate
. We then need to install a binding that sets the predicate
property of FilteredList
.
public class FilterableTreeItem<T> extends TreeItem<T> { private FilteredList<TreeItem<T>> filteredList; private ObjectProperty<TreeItemPredicate<T>> predicate = new SimpleObjectProperty<>(); public FilterableTreeItem(T value) { super(value); this.sourceList = FXCollections.observableArrayList(); this.filteredList = new FilteredList<>(this.sourceList); this.filteredList.predicateProperty().bind(Bindings.createObjectBinding(() -> { return child -> { // Set the predicate of child items to force filtering if (child instanceof FilterableTreeItem) { FilterableTreeItem<T> filterableChild = (FilterableTreeItem<T>) child; filterableChild.setPredicate(this.predicate.get()); } // If there is no predicate, keep this tree item if (this.predicate.get() == null) return true; // If there are children, keep this tree item if (child.getChildren().size() > 0) return true; // Otherwise ask the TreeItemPredicate return this.predicate.get().test(this, child.getValue()); }; }, this.predicate)); setHiddenFieldChildren(this.filteredList); } ... }
Example
The usage of FilterableTreeItem
is pretty straight forward. Let’s assume we are using the data type Actor
as a base type for the TreeView
.
class Actor { public String firstname; public String lastname; public Actor(String string) { this.lastname = string; } public Actor(String firstname, String lastname) { this.firstname = firstname; this.lastname = lastname; } @Override public String toString() { return firstname == null ? lastname : firstname + " " + lastname; } }
We want to filter the displayed actor using a TextField
as input.
FilterableTreeItem<Actor> root = getTreeModel(); root.predicateProperty().bind(Bindings.createObjectBinding(() -> { if (filterField.getText() == null || filterField.getText().isEmpty()) return null; return TreeItemPredicate.create(actor -> actor.toString().contains(filterField.getText())); }, filterField.textProperty())); TreeView<Actor> treeView = new TreeView<>(root);
The complete sample Application
can be viewed here and looks like this:
Conclusion
A marriage between TreeItem
and FilteredList
could be implemented fairly nicely if it would be possible to customize the list implementation used in TreeItem
without resorting the the evil reflective hack described above (thanks to Jeanette and J. Duke for pointing me to this solution). I hope that a possibility to customize the list implementation will be introduced in the future as I have suggested in RT-40790/JDK-8091687.
These classes were contributed to the e(fx)clipse open source framework in the bundle org.eclipse.fx.ui.controls
so you can use this feature, the very similar SortableTreeItem
and other cool stuff provided there.