Filtering a JavaFX TreeView

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:
2015-05-15_132450

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.