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

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.

13 thoughts on “Filtering a JavaFX TreeView

  1. Pingback: JavaFX links of the week, May 18 // JavaFX News, Demos and Insight // FX Experience

  2. nice idea 🙂

    Just: you are certainly aware that you are playing with fire. TreeItem (and other collaborators) access the children field directly at several places, so there’s probably trouble ahead if getChildren() != children. Another slight uglyness is the unspecific treeModificationEvent: it should carry more information about the change, listeners might get confused. But then, core treeItem fires crap on modification to the children, anyway, so listeners might expect nothing better. And FilteredList fires way too coarse-grained as well, so even a well-implemented selectionModel (f.i.) can’t keep its state half-way reasonbly.

    Without api changes, there’s nothing much that can be done – while my preferred dirty hack is to replace the children field reflectively that comes at the price of a whole tail of even more dirt 😉

    Cheers, Jeanette

    • Yeah! I saw the reflection hack in RT-34943/JDK-8089158 after I wrote this and I agree that this is the “better” hack of the two, especially if you intend to use the FilteredTreeItem with TreeTableView. I haven’t got around to refactoring yet …

    • Hi Jeanette

      I did the refactoring today after we noticed an error trying to set the selection of a TreeView using the FilteredTreeItem. Using the reflective hack works a lot better so I also updated the article. Thanks for pointing this out!

      Smile, Christoph

      • Hi Christoph,

        wondering if you really need the manual notification when changing the predicate? Now that super is listening to the filteredList, the update should be automatic.

        Cheers, Jeanette

  3. Pingback: e(fx)clipse 2.0 – Is released | Tomsondev Blog

  4. Hey,

    how is it possible to use the FilterableTreeItem? Just copiing sources into own project? or is there a maven project?

    The implementation is exactly what i am looking for…

    Thanks and best regards,
    FHU

  5. Thank you for the fast reply, but: Where do i find the efxclipse libraries? I have read (hopefully) all information on you provided link and i´ve installed all plugins for eclipse. But i am not able to include a library (or a maven dependency) to my project to use your javafx features.

  6. Hi Christoph,
    I’m trying to tweek the FilterableTreeItem class in order to be able to return the filtered treeitem and its children. But i’m having a hard time to do so. Have you a tip about the way of doing this ?

    • You can use getChildren() to return all the children of the current tree item. This is backed by the FilteredList that gets set up in the constructor.

Please leave a comment