Solved QTreeView::expand/collapse in loop, update view on each iteration
-
Hi,
I am using my own treeView, which is derived from
QTreeView
. I have customizedkeyPressEvent
such that when the user pressesKey_Left
orKey_Right
on an expanded/collapsed item, all its children (only one level lower) are being collapsed or expanded. For example, when the children are collapsed the following code is used to expand:if ( event->key() == Qt::Key_Right && currentIndex.isValid() ) { if (this->isExpanded(currentIndex)) { for (int i=0; i<model->rowCount(currentIndex.parent()); ++i) { this->expand(model->index(i, 0, currentIndex.parent())); } } else { this->expand(currentIndex); } }
This works well, but there is a significant lag in the app between triggering the
for
-loop and updating the view. I couldn't find a way to force-update the view after each iteration, e.g., using anupdate
orrepaint
call.
Ideally, I'd like the view to update after each iteration such that it "rolls" open from the top when expanding or closes from below when collapsing, so that the user sees the tree expanding/collapsing like an animation instead of having the app frozen for a second while the loop iterates.
How could I do this? -
@MScholli
QTreeView
inherits fromQWidget
, and allQWidget
s have a number ofupdate()
andrepaint()
methods, all on page https://doc.qt.io/qt-5/qwidget.html.I don't know whether these would make it behave the way you want, you'd have to try.
Or, it's not great, but if you put a https://doc.qt.io/qt-5/qcoreapplication.html#processEvents call into your
for
loop that may make the redraw show up.If these don't work, or don't show as you'd like, you should be able to implement it explicitly yourself. When user presses key, start a repeating
QTimer
with a small-ish millisecond interval. On each tick when it calls your slot, expand/collapse one node, next one on next timer click. You will have to maintain state information in the class instance as to how far you got last time through the children so you continue from there --- this is usual with timer code. This approach has the event loop running, so should be no need for anyupdate
/repaint
/processEvents()
. -
@JonB
Thanks a lot for your help and suggestions.update()
orrepaint()
didn't work, butqApp->processEvents()
did guide me in the right direction.I should have mentioned that my app uses
QTreeView->setAnimated(true)
, which complicated things quite a bit. AFAIK, the animation used inQTreeView
uses only private properties, therefore I could not figure out if and how I can connect to afinished()
signal.Instead, I've changed the code above to call a
expandIndex
, which starts expanding the top row, runsprocessEvents()
in an infinite loop until thestate()
of theQTreeView
is no longer inQAbstractItemView::State::AnimatingState
and then calls itself to expand the next row until all rows have been expanded.
Collapsing the tree works the same, but starts at the bottom.void treeView::expandIndex() { if (_expandedRowCount < model()->rowCount(_parentIndex)) { auto index = model()->index(_expandedRowCount, 0, _parentIndex); this->expand(index); _expandedRowCount += 1; forever { qApp->processEvents(); if (state() != QAbstractItemView::State::AnimatingState) break; } expandIndex(); } } void treeView::keyPressEvent(QKeyEvent *event) { auto currentIndex = this->currentIndex(); if ( event->key() == Qt::Key_Right && currentIndex.isValid() ) { if (this->isExpanded(currentIndex)) { _expandedRowCount = 0; _parentIndex = currentIndex.parent(); expandIndex(); } else { this->expand(currentIndex); } } }
This is probably not the most elegant solution and wasteful due to the
forever
loop.
If anybody has suggestions to simplify this and/or get rid of theforever
loop I'm all ears!