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.

30 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 ?

  7. Why does every TreeItem assume that all subitems have the same type parameter? As soon as you use Folders to group Employees or smth, there will be differences. Just remove the type parameter from everything regarding the children, and it still works the same!

  8. The reflection hack doesn’t seem to be neccessary anymore, at least when I look at TreeItem, the “children” field is now package-protected.

    • Have you tried this? I haven’t had a close look at Java9 in this regard. In Java8 you do not have access to the “children” field from outside of the package, so reflection is necessary.

      • Oh wait, I wasn’t aware that package-protected means less permissions than protected, so no, it is still needed. But the issue is that Java 9 warns when the hack is done:

        WARNING: An illegal reflective access operation has occurred
        WARNING: Illegal reflective access by xerus.util.javafx.FilterableTreeItem to field javafx.scene.control.TreeItem.children
        WARNING: Please consider reporting this to the maintainers of xerus.util.javafx.FilterableTreeItem
        WARNING: Use –illegal-access=warn to enable warnings of further illegal reflective access operations
        WARNING: All illegal access operations will be denied in a future release

  9. Hi, it’s a great implementation , Thanks!
    The only thins that’s missing for me is the ability to keep track of changes in item in the list – when an item’s property updated I would like the filter to update as well.

  10. Pingback: Java desktop links of the week, May 18 – Jonathan Giles

  11. Hi.. I used this code and executed. But I am getting
    Caused by: java.lang.ClassNotFoundException: org.eclipse.fx.core.ReflectionUtil.
    Exception. Can you please explain why i am getting this exception and how can i overcome it.

    To import org.eclipse…….. import statements i am using org.eclipse.fx.ui.controls-2.2.0.jar and org.eclipse.fx.core.fxml-1.0.0.jar.

  12. Hey,

    i want to thank you many times for your awesome work – it works like a charm. I really like that you could get around without reflection. As i am studying TornadoFX i did your example with small modifications as a demo application. For anybody interested, you can find it here https://github.com/deggers/fuzzy-umbrella for reference.

    Greetings,
    Dustyn

      • A small issue i experienced: In the GUI i will have the filteredItems , right? When i change those, the changes are not reflected in the sourceList – i think the problem is, that there is no bidirectional binding? It would be nice if changes in the Items in the filter would be reflected in the source list – but i do not see a solution to that? You might have an idea?

        • We also want to be able to clear the filter and then see all the elements again. If we would also remove the items from the backing list, this would not be possible. So you will always need two lists: A backing list with all the elements and a filtered list for the view.

          • Thanks Christoph for the clarifcation. I am sorry when I did explain it not correctly. My problem was a implementation problem on my side – i just checked it with a more simple tree and it works. To the background: I want to render the treeCells based on their Datatype, using Kotlin a sealed class – which makes sure all cases are handled. Each element which can be rendered will inherit from a parent “TreeElement” which has a “keywords” field which is used to filter the tree. I just realised that i missed to update the keywords in the parent when a child changed its value. More concret: I am working on a TODO App with a Tree. It will have Category and Task and different datatypes which are displayed in the tree differently. During instantitation i said take whatever somebody wrote in task as description as keyword for the search. But i forgot that i need to make the binding dynamically – whenever Task is chaning its description, it must tell it to the “keywords”.. I know this is very implementation specific – just wanted to say : All cool, your idea works like one should expect 🙂

  13. Hi, I’m using your wonderful code, but I have a small question.

    Is it possible to not filter on leaf/specific elements?

    Eg:
    ROOT
    -CATEGORY1
    -name1
    -Index1
    -Index2
    -Index3
    -name2
    -Index1
    -Index2
    -Index3

    I just want to filter on names and categories but not on indexes. Indexes can only be removed when name objects are filtered away.

    My treeItem object can give me true when item is index. I tried to use it, but I was unsuccessful.

Please leave a comment