New in Qt 5.10: recursive filtering for QSortFilterProxyModel
When using Qt’s model/view framework, filtering has always been very easy with QSortFilterProxyModel. To filter a list using text typed by the user in a line-edit, you have certainly been applying the standard recipe:
- instantiate the proxy;
- insert it between view and source model;
- connect the lineedit’s
textChanged
signal to the proxy’ssetFilterFixedString
slot; done!
But what if the model is a tree rather than a flat list?
Let’s use the tree below as an example. As you can see by looking at the highlighted row, there is a toplevel item “Form Editing Mode” containing the item “Layouts”, which itself contains an item with the word “Vertical”.
Now if we use the same code for filtering as for a flat model, we get a rather unpleasant surprise: typing “Vertical” shows… an empty view.
This is because the toplevel item “Form Editing Mode” doesn’t match the filter and therefore it has been filtered out, with no consideration for its children.
This is logical from an implementation point of view (filterAcceptsRow()
returns false, the whole subtree is filtered out), but this isn’t what we wanted to achieve.
Introducing recursive filtering
We need the parents of matching items to be considered as matching the filter too. Until now this wasn’t easily possible.
This is the reason why I implemented (with the help of my colleague Filipe Azevedo) a “recursive filtering” feature in QSortFilterProxyModel. It has just been merged into Qt’s dev branch (commit), which means it will be in Qt 5.10. You will need to be a bit patient, but at some point you will be able to make filtering work for tree models by adding one simple line of code:
proxy->setRecursiveFilteringEnabled(true);
And here’s the result:
Pretty neat, isn’t it?
About KDAB
KDAB is a consulting company offering a wide variety of expert services in Qt, C++ and 3D/OpenGL and providing training courses in:
KDAB believes that it is critical for our business to contribute to the Qt framework and C++ thinking, to keep pushing these technologies forward to ensure they remain competitive.
This is very nice indeed, I’m just wondering how will this handle models where the tree is infinite levels deep? (For example because there is recursion in it.) Is there a depth limit that can be set?
Right now this would recursive infinitely (well, until the stack is filled, then it would crash), indeed. I wasn’t aware that there was really a use case for an infinitely-deep tree due to a recursion, although now that you mention it, I can imagine a folder tree model with a symlink pointing up…
I’m not sure that a depth limit is the right solution though. What limit should the developer set in the example above ? If he sets 10, and a user is looking for a subfolder at depth 11 it won’t be found, even though the model isn’t infinitely deep. On the other hand detecting recursion is almost impossible since it doesn’t appear that way at the model level.
This being said, if you have a use case for a depth limit setting, please create a JIRA entry on bugreports.qt.io (or feel free to contribute the feature, of course).
I was just asking since we have such a model, and have experimented with using QSFPM, but it’s not likely that it will be included in a release, so nothing critical yet from my part.
Anyways, I have created an Issue on JIRA: https://bugreports.qt.io/browse/QTBUG-62050 with a suggestion.
Great feature, Bravo.
By the way, an infinite tree produced by a loop would no more be a tree. It is a cyclic graph. Such a structure imho doesn’t fullfills the preconditions of treemodels. So, it isn’t a requirement.
However, a very big tree, optimized with canFetchMore, that is progressively filled when opened, could be a problem.
Great job anyway, this was a long time expected feature.
Laurent
Looping was just an example, an infinite tree can be constructed easily without it.
And AFAIK the tree model/view in Qt works just fine even in that case so far (using canFetchMore and such), this would be an exception.
Nice, I have found myself implementing this manually more than once. I do have an additional request though, and that may require an API change for this one:
Sometimes, you do not only want to show parents for a match (so the match is visible), you may (also) want to show children for a match. I guess a getter and setter taking an enum to set a recursive matching mode instead of a simple bool would be needed to do that API wise.
This has now been implemented as well, by Giulio from KDAB. It will be in Qt 6.
https://codereview.qt-project.org/c/qt/qtbase/+/301180
After about 5 days of hunting down a crash in the QSPFM, we’ve found that it’s _something_ to do with proxy->setRecursiveFiltering(true);
We raised a bug with a repeatable test case. https://bugreports.qt.io/browse/QTBUG-67281
This turned out to be a missing call to invalidateFilter().