HowTo restore QTreeView last expanded state?

Viewed 19252

What I have:

  1. QTreeView class with table data
  2. And connected QAbstractTableModel model

Question: how to save expanded state of items? Is some one have finished solutions?

PS: I know, that I can do this code by myself, but I don't have much time, and this is not the major problem of our project, but still we need it, because app contain a lot of such tables, and every time expanding tree items is annoyed process...

8 Answers

Thanks to Razi and mosg I was able to get this working. I made it restore the expanded state recursively so I thought I would share that part.

void applyExpandState_sub(QStringList& expandedItems,
                          QTreeView* treeView,
                          QAbstractItemModel* model,
                          QModelIndex startIndex)
{
    foreach (QString item, expandedItems) 
    {
        QModelIndexList matches = model->match( startIndex, Qt::UserRole, item );
        foreach (QModelIndex index, matches) 
        {
            treeView->setExpanded( index, true );
            applyExpandState_sub(expandedItems, 
                                 treeView,
                                 model,
                                 model->index( 0, 0, index ) );
        }
    }
}

Then use like:

void myclass::applyExpandState() 
{
    m_treeView->setUpdatesEnabled(false);

    applyExpandState_sub( m_expandedItems,
                          m_treeView,
                          m_model,
                          m_model->index( 0, 0, QModelIndex() ) );

    m_treeView->setUpdatesEnabled(true);
}

I am using the Qt::UserRole here because multiple items in my model can have the same display name which would mess up the expand state restoration, so the UserRole provides a unique identifier for each item to avoid that problem.

My approach was to save the list of expanded items (as pointers) and when restoring, only set as expanded only the items in this list. In order to use the code below, you may need to replace TreeItem * to a constant pointer to your object (that doesn't change after a refresh).

.h

protected slots:
    void restoreTreeViewState();
    void saveTreeViewState();
protected:
    QList<TargetObject*> expandedTreeViewItems;

.cpp

connect(view->model(), SIGNAL(modelAboutToBeReset()), this, SLOT(saveTreeViewState()));
connect(view->model(), SIGNAL(modelReset()), this, SLOT(restoreTreeViewState()));

...

void iterateTreeView(const QModelIndex & index, const QAbstractItemModel * model,
             const std::function<void(const QModelIndex&, int)> & fun,
             int depth=0)
{
    if (index.isValid())
        fun(index, depth);
    if (!model->hasChildren(index) || (index.flags() & Qt::ItemNeverHasChildren)) return;
    auto rows = model->rowCount(index);
    auto cols = model->columnCount(index);
    for (int i = 0; i < rows; ++i)
        for (int j = 0; j < cols; ++j)
            iterateTreeView(model->index(i, j, index), model, fun, depth+1);
}

void MainWindow::saveTreeViewState()
{
    expandedTreeViewItems.clear();

    iterateTreeView(view->rootIndex(), view->model(), [&](const QModelIndex& index, int depth){
        if (!view->isExpanded(index))
        {
            TreeItem *item = static_cast<TreeItem*>(index.internalPointer());
            if(item && item->getTarget())
                expandedTreeViewItems.append(item->getTarget());
        }
    });
}

void MainWindow::restoreTreeViewState()
{
    iterateTreeView(view->rootIndex(), view->model(), [&](const QModelIndex& index, int depth){
        TreeItem *item = static_cast<TreeItem*>(index.internalPointer());
        if(item && item->getTarget())
            view->setExpanded(index, expandedTreeViewItems.contains(item->getTarget()));
    });
}

I think this implementation gives extra flexibility compared to some of the others here. At least, I could not make it work with my custom model.

If you want to keep new items expanded, change the code to save the collapsed items instead.

Related