In this article I will demonstrate how to filter data in a JavaFX TreeView
using a FilteredList
.
Contents
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.
Pingback: JavaFX links of the week, May 18 // JavaFX News, Demos and Insight // FX Experience
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
Thanks! Forgot about that …
Pingback: e(fx)clipse 2.0 – Is released | Tomsondev Blog
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
You can use the efxclipse libraries. I have no experience with maven, so I don’t know about that, but you can check the projects homepage for more information (http://www.eclipse.org/efxclipse/index.html). If you have problems feel free to ask questions on the project forum.
Sorry, but I’m not at my desk for the next two weeks. Best to ask on the forum: https://www.eclipse.org/forums/index.php/f/259/
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.
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.
Hi, I was trying the same thing. Can you please tell me how exactly to do it? Because I can’t make it work. What I’m trying to do is if I search the folder name, I also want to show the children. Please help.
Hi Nico!
I’m not sure I understand what exactly you are trying to achieve. The FilteredListTreeItem will pass the predicate on to its children, if they are also of the type FilteredListeTreeItem. That means that the list of children is also filtered with the same predicate. If you don’t want this, maybe because you only want to filter the first layer and always want to show all of the children then a simple solution would be to use a basic TreeItem for the children.
Here is the current code we are using for our trees:
https://github.com/eclipse-efx/efxclipse-rt/blob/3.x/modules/ui/org.eclipse.fx.ui.controls/src/main/java/org/eclipse/fx/ui/controls/tree/FilterableTreeItem.java
Hope that helps!
Christoph
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!
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
but this is an interesting alternative I found: https://stackoverflow.com/a/34426897/6723250
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.
Pingback: Java desktop links of the week, May 18 – Jonathan Giles
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.
Hi Chaitanya,
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 but to use the current version that is included in efxclipse. The full name of the class isorg.eclipse.fx.ui.controls.tree.FilterableTreeItem
If you want to have a look at the current implementation see https://github.com/eclipse-efx/efxclipse-rt/blob/3.x/modules/ui/org.eclipse.fx.ui.controls/src/main/java/org/eclipse/fx/ui/controls/tree/FilterableTreeItem.java
Greetings,
Christoph
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
Thanks Dustyn! Glad that this has been usefull 🙂
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 🙂
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.