Whoever digged a little deeper into Qt’s item views and models in the past knows that they’re quite tricky beasts sometimes. Two problems where driving me nuts recently:
1. How can I auto-expand the root node of a `QTreeView` whenever the view is resetted?
2. How can I restore the expansion / root state of a `QTreeView` when a model filter cleared all the items out before, effectively resetting the view as in 1.?
After a lot of struggling and lots of debugging I think I found a solution for both problems, which are slightly connected.
To get the auto-expansion feature, I connected the `layoutChanged()` signal of the model to a timer in my view, whose `timeout()` signal then again called the following slot:
void MyTreeView::delayedLayoutChanged() { QModelIndex index = model()->index(0, 0, QModelIndex()); expand(index); // or alternatively: setRootIndex(index); }
This simply takes the first element of the model and expands it. Why the trick with the timer? Well, apparently the `layoutChanged()` signal is already processed internally by `QAbstractItemView` or `QTreeView` and its implementation resets the view state completly – which is of course problematic if it gets executed _after_ our own slot. A single-shot timer with an interval of 0 ms is enough here to make Qt use the next event loop cycle and do the right thing for us.
Now, things get more complex if you have a `QSortFilterProxyModel` attached to your tree view – imagine one of these fancy find-as-you-type inputs. If a user enters a search word which clears out all items from your current view and then resets the input again to see the original view, the view eventually gets a `layoutChanged()` signal as well, but because of the clearing you may have lost the expansion level in which the user was before!
`QPersistentModelIndex` is here for the rescue. All you have to do is remember the model index you previously used to expand your view and use that instead of the static one if it is still valid:
QPersistentModelIndex MyTreeView::lastExpansion; void MyTreeView::expandSomething(const QModelIndex & index) { lastExpansion = static_cast(model()) ->mapToSource(index); expand(index); } void MyTreeView::delayedLayoutChanged() { QModelIndex index; // is the item still part of the source model? // (important for dynamic source models) if (lastExpansion.isValid()) { index = static_cast (model()) ->mapFromSource(lastExpansion); } else { index = model()->index(0, 0, QModelIndex()); } expand(index); // or alternatively: setRootIndex(index); }
While `QPersistentModelIndex` and `QModelIndex` are two distinct classes, the Trolls made them easily interchangable without special casting magic.
Note that you always need to save the source index and never the proxy index which might be invalidated if it is removed from the view. Things get of course even more complex if you have not one, but two proxy models which filter your view…