代码之家  ›  专栏  ›  技术社区  ›  mosg

如何恢复QTreeView上次展开的状态?

  •  14
  • mosg  · 技术社区  · 14 年前

    我所拥有的:

    1. QTreeView 使用表数据初始化
    2. 并连接 QAbstractTableModel 模型

    问题 :如何保存项目的展开状态?有人已经解决了吗?

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

    7 回复  |  直到 13 年前
        1
  •  14
  •   mosg    14 年前

    首先,感谢 对于 persistentIndexList isExpanded 是的。

    第二,这是一个对我来说很好的代码:-)

    对话框.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);
    };
    

    对话框.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
  •  8
  •   iforce2d    12 年前

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

    然后像这样使用:

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

        3
  •  7
  •   S. Razi    14 年前

    这两个函数通过使用循环来实现:

    QModelIndexList QAbstractItemModel::persistentIndexList () const
    bool isExpanded ( const QModelIndex & index ) const
    
        4
  •  5
  •   Basti Vagabond    9 年前

    这里有一种通用的方法,可以与任何基于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::saveExpandedState() MyWidget::saveExpandedState() 也可以直接打电话 MyWidget::saveExpandedOnLevel(tree_view_->rootIndex()) MyWidget::restoreExpandedOnLevel(tree_view_->rootIndex()) MyWidget::saveExpandedState() MyWidget::saveExpandedState()

        5
  •  2
  •   Zeks    12 年前

    我将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;
    }
    

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

        6
  •  1
  •   Steve Lorimer    7 年前

    这是一个不依赖节点具有唯一 Qt::UserRole Qt::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);
    };
    
        7
  •  0
  •   phyatt    10 年前

    为了一个 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
  •  0
  •   Adriel Jr    5 年前

    为了使用下面的代码,您可能需要将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()));
        });
    }
    

    我认为与这里的其他一些实现相比,这个实现提供了额外的灵活性。至少,我不能使它与我的自定义模型。

    如果要使新项保持展开状态,请更改代码以保存折叠的项。