Problem
Applying filtering on a JTree
to avoid certain nodes/leaves to show up in the rendered version of the JTree
. Ideally I am looking for a solution which allows to have a dynamic filter, but I would already be glad if I can get a static filter to work.
To make it a bit easier, let us suppose the JTree
only supports rendering, and not editing. Moving, adding, removing of nodes should be possible.
An example is a search field above a JTree
, and on typing the JTree
would only show the subtree with matches.
A few restrictions: it is to be used in a project which has access to JDK and SwingX. I would like to avoid to include other third party libs.
I already thought of a few possible solutions, but neither of those seemed ideal
Approaches
Model based filtering
- decorate the
TreeModel
to filter out some of the values. A quick-and-dirt version is easy to write. Filter out nodes, and on every change of the filter or the delegateTreeModel
the decorator can fire an event that the whole tree has changes (treeStructureChanged
with the root node as node). Combine this with listeners which restore the selection state and the expansion state of theJTree
and you get a version which works more or less, but the events originating from theTreeModel
are messed up. This is more or less the approach used in this question - decorate the
TreeModel
but try fire the correct events. I did not (yet) managed to come up with a working version of this. It seems to require a copy of the delegateTreeModel
in order to be able to fire an event with the correct child indices when nodes are removed from the delegate model. I think with some more time I could get this to work, but it just feels wrong (filtering feels like something the view should do, and not the model) - decorate whatever data structure was used to create the initial
TreeModel
. However, this is completely non-reusable, and probably as hard as to write a decorator for aTreeModel
View based filtering
This seems like the way to go. Filtering should not affect the model but only the view.
I took a look at
RowFilter
class. Although the javadoc seems to suggest you can use it in combination with aJTree
:when associated with a JTree, an entry corresponds to a node.
I could not find any link between
RowFilter
(orRowSorter
) and theJTree
class. The standard implementations ofRowFilter
and the Swing tutorials seems to suggest thatRowFilter
can only be used directly with aJTable
(seeJTable#setRowSorter
). No similar methods are available on aJTree
- I also looked at the
JXTree
javadoc. It has aComponentAdapter
available and the javadoc ofComponentAdapter
indicates aRowFilter
could interact with the target component, but I fail to see how I make the link between theRowFilter
and theJTree
- I did not yet look at how a
JTable
handles the filtering withRowFilter
s, and perhaps the same can be done on a modified version of aJTree
.
So in short: I have no clue on what's the best approach to solve this
Note: this question is a possible duplicate of this question, but that question is still unanswered, the question rather short and the answers seems incomplete, so I thought to post a new question. If this is not done (the FAQ did not provide a clear answer on this) I will update that 3year old question
View-based filtering is definitely the way to go. You can use something like the example I've coded below. Another common practice when filtering trees is to switch to a list view when filtering a tree, since the list won't require you to show hidden nodes whose descendants need to be shown.
This is absolutely horrendous code (I tried to cut every corner possible in whipping it up just now), but it should be enough to get you started. Just type your query in the search box and press Enter, and it'll filter the JTree's default model. (FYI, the first 90 lines are just generated boilerplate and layout code.)
When you implement it for real, you'll probably want to create your own TreeNode and TreeCellRenderer implementations, use a less stupid method for triggering an update, and follow MVC separation. Note that the "hidden" nodes are still rendered, but they're so small that you can't see them. If you use the arrow keys to navigate the tree, though, you'll notice that they're still there. If you just need something that works, this might be good enough.
Edit
Here are screenshots of the unfiltered and filtered version of the tree in Mac OS, showing that the whitespace is visible in Mac OS:
ETable
, a subclass ofJTable
and the parent class ofOutline
, described here, includes "Quick-Filter features allowing to show only certain rows from the model (seesetQuickFilter()
)." While this violates the no "third party libs" requirement, theOutline
JAR has no dependencies other than the JDK.Take a look at this implementation: http://www.java2s.com/Code/Java/Swing-Components/InvisibleNodeTreeExample.htm
It creates subclasses of DefaultMutableNode adding a "isVisible" property rather then actually removing/adding nodes from the TreeModel. Pretty sweet I think, and it solved my filtering problem neatly.
Old Question, I stumbled upon... for all those who want a quick and easy Solution of
JUST FILTERING THE VIEW:
I know it isn't as clean as Filtering the Model and comes with possible backdraws, but if you just want a quick solution for a small Application:
Extend the DefaultTableCellRenderer, override getTreeCellRendererComponent - invoke super.getTreeCellRendererComponent(...) and after that just set the Preferred Height to ZERO for all Nodes you want to hide. When constructing your JTree be sure to set setRowHeight(0); - so it will respect the Preferred Height of each row...
voila - all filtered Rows invisible!
COMPLETE WORKING EXAMPLE
I've finally managed to pull of something the suits my needs perfectly, and thought I'd share in case someone else can use it.
I have been trying to display two JTrees side by side - one that contains a filtered list of the other.
Basically I have made two TreeModels, and use the same root node for both. This seems to work fine so far, so long as I make sure to override EVERY method that gets called from DefaultTreeModel in my code, such as nodeChanged( TreeNode node ) or else there will be pain.
The pain comes from the fact that the only time the nodes themselves are queried for information like childcount, is when nodestructure type methods are called on the DefaultTreeModel. Aside from that, all calls for tree structure information can be intercepted and filtered out as shown below.
This can get nasty as you have to make sure you dig out every time the nodes themselves are queried if you use DefaultTreeModel as the base like I did. This problem might not be there if you implement TreeModel directly instead of being lazy like me. NodesChanged source came straight from the JDK source.
I was lucky in that what I wanted meant there was always a path back to the root node from every item in the filtered list.
This was all I needed to do, even tho I spent the whole day trying wild and chaotic inventions, like recreating a shallow copy of the tree etc, not to mention reading lots on Stack !:
A solution was given http://forums.sun.com/thread.jspa?forumID=57&threadID=5378510
We have implemented it here, adn it works as a charm.
You just have to implement you TreeModel, and on the JTree use a FilterTreeModel with a TreeFilter.
The implementation is quite simple, there is perhaps maybe some stuff to do on listener, since the actual code will called twice, which is not good at all. My idea is to just pass listener to delegate model, I don't see the point to add listener on the filter model... But that is another question.