如何恢复QTreeView的最后扩展状态?

问题描述:

我有什么:如何恢复QTreeView的最后扩展状态?

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

问题:如何保存项目的展开状态?有人已经完成了解决方案吗? PS:我知道,我可以自己做这个代码,但我没有太多时间,这不是我们项目的主要问题,但我们仍然需要它,因为应用程序包含很多这样的表,每次扩大树项目是恼火过程...

+0

您可以扩展您的需求吗?位?你的意思是在程序执行过程中保留扩展状态,或许将数据存储在QSettings中?或修改树时保留展开状态? – Casey 2010-07-15 18:14:39

+0

@Casey是的,首先用QSettings存储'QByteArray'。没有修改,第二。在这里:http://www.qtcentre.org/threads/13826-QTreeView-restore-Expanded-node-after-reload-model我发现了一些实现,但没有时间检查... – mosg 2010-07-16 06:20:24

+0

>没有修改 - 意味着,我想恢复最后一个项目时,我的应用程序启动时扩大。 – mosg 2010-07-16 06:27:26

首先感谢拉齐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的代码。

这两个功能使用循环应该为你做的:

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

好吧,我会试试这个... – mosg 2010-07-19 06:26:36

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

然后使用像:

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为每个项目提供一个唯一的标识符以避免该问题。

我已经返工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; 
} 

这样就没有必要匹配的数据。显然 - 为了这种方法的工作,树需要保持相对不变。如果你以某种方式改变树项目的顺序 - 它会扩大错误的节点。

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

希望能帮助别人。

+0

这只会恢复当前选定的项目,但不是整个“扩展”状态,对吧? – Legolas 2015-08-06 14:02:29

下面是应与任何基于QTreeView则插件工作的一般方法,使用某种ID系统以识别元件(我假设的ID是一个i​​nt,其上存储着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())。我只使用了上面的实现,因为无论如何都会调用for循环,并且MyWidget::saveExpandedState()MyWidget::saveExpandedState()在我的SIGNAL和SLOT设计中看起来更干净。

+0

我发现这非常有用!请注意,任何想要多次保存和恢复的应用程序,每次调用saveExpandedState()时都应该重置包含id的类型。另外,如果您要存储已创建的模型,则可以将'QVariant(QModelIndexes)'作为您的ID保存在Qt :: UserRole中。 – mrg95 2016-08-08 09:43:44

+0

另请注意,如果父索引未扩展,则此函数不会存储扩展索引。这可能是一个问题,取决于用例。 – mrg95 2016-08-09 21:08:21

这里是一个不依赖于节点具有唯一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); 
};