【问题标题】:HowTo restore QTreeView last expanded state?如何恢复 TreeView 上次展开状态?
【发布时间】:2010-07-15 07:21:35
【问题描述】:

我有什么:

  1. QTreeView 带表数据的类
  2. 并连接QAbstractTableModel模型

问题:如何保存物品的展开状态?有人已经完成了解决方案吗?

PS:我知道,我可以自己做这个代码,但我没有太多时间,这不是我们项目的主要问题,但我们仍然需要它,因为应用程序包含很多这样的表,每次扩展树项都是烦人的过程......

【问题讨论】:

  • 你能扩展一下你的要求吗?您的意思是通过将数据存储在 QSettings 中来保留跨程序执行的扩展状态吗?还是在修改树时保留展开状态?
  • @Casey 是的,首先使用 QSettings 存储 QByteArray。没有修改,第二。在这里:qtcentre.org/threads/…我找到了一些实现,但是没时间检查...
  • > 没有修改 - 意思是,我想在我的应用程序启动时恢复上次展开的项目。

标签: qt qt4 qtreeview


【解决方案1】:

首先,感谢 Razi 提供persistentIndexListisExpanded 方式。

其次,这里的代码对我来说很好:-)

dialog.h 文件:

class Dialog : public QDialog
{
    Q_OBJECT;

    TreeModel *model;
    TreeView *view;

public:
    Dialog(QWidget *parent = 0);
    ~Dialog(void);

    void reload(void);

protected:
    void createGUI(void);
    void closeEvent(QCloseEvent *);
    void saveState(void);
    void restoreState(void);
};

dialog.cpp 文件:

Dialog::Dialog(QWidget *parent)
{
    createGUI();
    reload();
}

Dialog::~Dialog(void) {};

void Dialog::reload(void)
{
    restoreState();
}

void Dialog::createGUI(void)
{
    QFile file(":/Resources/default.txt");
    file.open(QIODevice::ReadOnly);
    model = new TreeModel(file.readAll());
    file.close();

    view = new TreeView(this);
    view->setModel(model);

    QVBoxLayout *mainVLayout = new QVBoxLayout;
    mainVLayout->addWidget(view);

    setLayout(mainVLayout);
}

void Dialog::closeEvent(QCloseEvent *event_)
{
    saveState();
}

void Dialog::saveState(void)
{
    QStringList List;

    // prepare list
    // PS: getPersistentIndexList() function is a simple `return this->persistentIndexList()` from TreeModel model class
    foreach (QModelIndex index, model->getPersistentIndexList())
    {
        if (view->isExpanded(index))
        {
            List << index.data(Qt::DisplayRole).toString();
        }
    }

    // save list
    QSettings settings("settings.ini", QSettings::IniFormat);
    settings.beginGroup("MainWindow");
    settings.setValue("ExpandedItems", QVariant::fromValue(List));
    settings.endGroup();
}

void Dialog::restoreState(void)
{
    QStringList List;

    // get list
    QSettings settings("settings.ini", QSettings::IniFormat);
    settings.beginGroup("MainWindow");
    List = settings.value("ExpandedItems").toStringList();
    settings.endGroup();

    foreach (QString item, List)
    {
        // search `item` text in model
        QModelIndexList Items = model->match(model->index(0, 0), Qt::DisplayRole, QVariant::fromValue(item));
        if (!Items.isEmpty())
        {
            // Information: with this code, expands ONLY first level in QTreeView
            view->setExpanded(Items.first(), true);
        }
    }
}

祝你有美好的一天!)


PS:本例基于C:\Qt\4.6.3\examples\itemviews\simpletreemodel代码。

【讨论】:

    【解决方案2】:

    多亏了 Razi 和 mosg,我才得以完成这项工作。我让它递归地恢复扩展状态,所以我想我会分享那部分。

    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 ) );
            }
        }
    }
    

    然后使用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);
    }
    

    我在这里使用 Qt::UserRole 是因为我的模型中的多个项目可以具有相同的显示名称,这会扰乱展开状态恢复,因此 UserRole 为每个项目提供唯一标识符以避免该问题。

    【讨论】:

      【解决方案3】:

      这两个使用循环的函数应该可以为你做到这一点:

      QModelIndexList QAbstractItemModel::persistentIndexList () const
      bool isExpanded ( const QModelIndex & index ) const
      

      【讨论】:

        【解决方案4】:

        这是一种适用于任何基于 QTreeView 的小部件的通用方法,它使用某种 ID 系统来识别元素(我假设 ID 是一个 int,它存储在 Qt::UserRole 中):

        void MyWidget::saveExpandedState()
        {
            for(int row = 0; row < tree_view_->model()->rowCount(); ++row)
                saveExpandedOnLevel(tree_view_->model()->index(row,0));
        }
        
        void Widget::restoreExpandedState()
        {
            tree_view_->setUpdatesEnabled(false);
        
            for(int row = 0; row < tree_view_->model()->rowCount(); ++row)
                restoreExpandedOnLevel(tree_view_->model()->index(row,0));
        
            tree_view_->setUpdatesEnabled(true);
        }
        
        void MyWidget::saveExpandedOnLevel(const QModelIndex& index)
        {
            if(tree_view_->isExpanded(index)) {
                if(index.isValid())
                    expanded_ids_.insert(index.data(Qt::UserRole).toInt());
                for(int row = 0; row < tree_view_->model()->rowCount(index); ++row)
                    saveExpandedOnLevel(index.child(row,0));
            }
        }
        
        void MyWidget::restoreExpandedOnLevel(const QModelIndex& index)
        {
            if(expanded_ids_.contains(index.data(Qt::UserRole).toInt())) {
                tree_view_->setExpanded(index, true);
                for(int row = 0; row < tree_view_->model()->rowCount(index); ++row)
                    restoreExpandedOnLevel(index.child(row,0));
            }
        }
        

        也可以直接调用MyWidget::saveExpandedOnLevel(tree_view_-&gt;rootIndex())MyWidget::restoreExpandedOnLevel(tree_view_-&gt;rootIndex()),而不是MyWidget::saveExpandedState()MyWidget::saveExpandedState()。我只使用了上述实现,因为无论如何都会调用 for 循环,而 MyWidget::saveExpandedState()MyWidget::saveExpandedState() 在我的 SIGNAL 和 SLOT 设计中看起来更干净。

        【讨论】:

        • 我发现这非常有用!请注意,任何想要多次保存和恢复的人,每次调用 saveExpandedState() 时都应重置包含 id 的类型。此外,如果您要存储您创建的模型,您可以将 QVariant(QModelIndexes) 作为您的 ID 存储在 Qt::UserRole 中。
        • 另请注意,如果父索引也未展开,则此函数不会存储展开的索引。这可能是一个问题,具体取决于用例。
        【解决方案5】:

        我已将 iforce2d 的解决方案改写为:

         void ApplyExpandState(QStringList & nodes,
                               QTreeView * view,
                               QAbstractItemModel * model,
                               const QModelIndex startIndex,
                               QString path)
        {
            path+=QString::number(startIndex.row()) + QString::number(startIndex.column());
            for(int i(0); i < model->rowCount(startIndex); ++i)
            {
                QModelIndex nextIndex = model->index(i, 0, startIndex);
                QString nextPath = path + QString::number(nextIndex.row()) + QString::number(nextIndex.column());
                if(!nodes.contains(nextPath))
                    continue;
                ApplyExpandState(nodes, view, model, model->index(i, 0, startIndex), path);
            }
            if(nodes.contains(path))
                view->setExpanded( startIndex.sibling(startIndex.row(), 0), true );
        }
        
        void StoreExpandState(QStringList & nodes,
                              QTreeView * view,
                              QAbstractItemModel * model,
                              const QModelIndex startIndex,
                              QString path)
        {
            path+=QString::number(startIndex.row()) + QString::number(startIndex.column());
            for(int i(0); i < model->rowCount(startIndex); ++i)
            {
                if(!view->isExpanded(model->index(i, 0, startIndex)))
                    continue;
                StoreExpandState(nodes, view, model, model->index(i, 0, startIndex), path);
            }
        
            if(view->isExpanded(startIndex))
                nodes << path;
        }
        

        这样就不需要匹配数据了。显然 - 要使这种方法起作用,树需要保持相对不变。如果您以某种方式更改树项的顺序 - 它会扩展错误的节点。

        【讨论】:

        • 对于 row=12、col=3 的项目和 row=1 和 col=23 的项目以及模拟组合(我们已经得到“123”),此代码不正确。需要生成带有格式符号的路径块,例如“[12,3]”。
        【解决方案6】:

        这是一个不依赖于具有唯一 Qt::UserRoleQt::DisplayRole 的节点的版本 - 它只是序列化整个 QModelIndex

        标题:

        #pragma once
        #include <QTreeView>
        
        class TreeView : public QTreeView
        {
            Q_OBJECT
        public:
            using QTreeView::QTreeView;
        
            QStringList saveExpandedState(const QModelIndexList&) const;
            void        restoreExpandedState(const QStringList&);
        };
        

        来源:

        #include "tree_view.h"
        #include <QAbstractItemModel>
        
        namespace
        {
            std::string toString(const QModelIndex& index)
            {
                std::string parent = index.parent().isValid() ? toString(index.parent()) : "X";
        
                char buf[512];
                sprintf(buf, "%d:%d[%s]", index.row(), index.column(), parent.c_str());
                return buf;
            }
        
            QModelIndex fromString(const std::string& string, QAbstractItemModel& model)
            {
                int row, column;
                char parent_str[512];
                sscanf(string.c_str(), "%d:%d[%s]", &row, &column, parent_str);
        
                QModelIndex parent = *parent_str == 'X' ? QModelIndex() : fromString(parent_str, model);
        
                return model.index(row, column, parent);
            }
        }
        
        QStringList TreeView::saveExpandedState(const QModelIndexList& indices) const
        {
            QStringList list;
            for (const QModelIndex& index : indices)
            {
                if (isExpanded(index))
                {
                    list << QString::fromStdString(toString(index));
                }
            }
            return list;
        }
        
        void TreeView::restoreExpandedState(const QStringList& list)
        {
            setUpdatesEnabled(false);
        
            for (const QString& string : list)
            {
                QModelIndex index = fromString(string.toStdString(), *model());
                setExpanded(index, true);
            }
        
            setUpdatesEnabled(true);
        };
        

        【讨论】:

        • 这不适用于任何支持以任何方式添加或删除项目的模型,因为 QModelIndexes 可以并且将在下次事件循环时发生变化。 QPersistentModelIndex 将保持状态并且可以在调用之间保持在视图中。
        【解决方案7】:

        对于QFileSystemModel,您不能使用persistentIndexList()

        这是我的工作。它工作得很好,即使我自己这么说。我还没有测试过如果您的文件系统加载缓慢,或者如果您删除了文件或路径会发生什么。

        // scrolling code connection in constructor
        model = new QFileSystemModel();
        
        QObject::connect(ui->treeView, &QTreeView::expanded, [=](const QModelIndex &index)
        {
            ui->treeView->scrollTo(index, QAbstractItemView::PositionAtTop);//PositionAtCenter);
        });
        
        // save state, probably in your closeEvent()
        QSettings s;
        s.setValue("header_state",ui->treeView->header()->saveState());
        s.setValue("header_geometry",ui->treeView->header()->saveGeometry());
        
        if(ui->treeView->currentIndex().isValid())
        {
            QFileInfo info = model->fileInfo(ui->treeView->currentIndex());
            QString filename = info.absoluteFilePath();
            s.setValue("last_directory",filename);
        }
        
        // restore state, probably in your showEvent()
        QSettings s;
        ui->treeView->header()->restoreState(s.value("header_state").toByteArray());
        ui->treeView->header()->restoreGeometry(s.value("header_geometry").toByteArray());
        QTimer::singleShot(1000, [=]() {
            QSettings s;
            QString filename = s.value("last_directory").toString();
            QModelIndex index = model->index(filename);
            if(index.isValid())
            {
                ui->treeView->expand(index);
                ui->treeView->setCurrentIndex(index);
        
                ui->treeView->scrollTo(index, QAbstractItemView::PositionAtCenter);
                qDebug() << "Expanded" << filename;
            }
            else
                qDebug() << "Invalid index" << filename;
        } );
        

        希望对某人有所帮助。

        【讨论】:

        • 这只是恢复当前选定的项目,而不是整个“扩展”状态,对吧?
        【解决方案8】:

        我的方法是保存扩展项目的列表(作为指针),并且在恢复时,仅将列表中的项目设置为扩展。 为了使用下面的代码,您可能需要将 TreeItem * 替换为指向您的对象的常量指针(刷新后不会更改)。

        .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()));
            });
        }
        

        我认为与这里的其他一些实现相比,这种实现提供了额外的灵活性。至少,我无法让它与我的自定义模型一起使用。

        如果您想保持新项目展开,请更改代码以保存折叠的项目。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2013-08-17
          • 1970-01-01
          • 2011-05-10
          • 1970-01-01
          • 2012-05-05
          • 2017-03-05
          • 2022-09-27
          相关资源
          最近更新 更多