Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

QTreeView::expand/collapse in loop, update view on each iteration



  • Hi,

    I am using my own treeView, which is derived from QTreeView. I have customized keyPressEvent such that when the user presses Key_Left or Key_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 an update or repaint 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 from QWidget, and all QWidgets have a number of update() and repaint() 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 any update/repaint/processEvents().



  • @JonB
    Thanks a lot for your help and suggestions. update() or repaint() didn't work, but qApp->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 in QTreeView uses only private properties, therefore I could not figure out if and how I can connect to a finished() signal.

    Instead, I've changed the code above to call a expandIndex, which starts expanding the top row, runs processEvents() in an infinite loop until the state() of the QTreeView is no longer in QAbstractItemView::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 the forever loop I'm all ears!


Log in to reply