Как восстановить последнее расширенное состояние QTreeView? - PullRequest
15 голосов
/ 15 июля 2010

Что у меня есть:

  1. QTreeView класс с табличными данными
  2. И подключен QAbstractTableModel модель

Вопрос : как сохранить расширенное состояние элементов?У кого-то есть готовые решения?

PS: я знаю, что я могу сделать этот код самостоятельно, но у меня не так много времени, и это не главная проблема нашего проекта, но все же нам это нужно, потому что приложение содержиттаких таблиц много, и каждый раз расширение элементов дерева раздражает процесс ...

Ответы [ 8 ]

11 голосов
/ 19 июля 2010

Во-первых, спасибо Рази за persistentIndexList и isExpanded способ.

Во-вторых, вот код, который работает для меня просто отлично: -)

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 коде.

8 голосов
/ 04 марта 2012

Благодаря Рази и Мосгу я смог заставить это работать.Я сделал это, чтобы восстановить расширенное состояние рекурсивно, поэтому я решил поделиться этой частью.

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

Затем используйте:элементы в моей модели могут иметь одно и то же отображаемое имя, что может испортить восстановление состояния расширения, поэтому UserRole предоставляет уникальный идентификатор для каждого элемента, чтобы избежать этой проблемы.

5 голосов
/ 18 июля 2010

Эти две функции с помощью цикла должны сделать это для вас:

QModelIndexList QAbstractItemModel::persistentIndexList () const
bool isExpanded ( const QModelIndex & index ) const
4 голосов
/ 09 сентября 2015

Вот общий подход, который должен работать с любым виджетом на основе QTreeView, который использует какую-то систему идентификаторов для идентификации элементов (я предполагаю, что идентификатор представляет собой 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()).Я использовал только вышеприведенную реализацию, потому что цикл for будет вызываться в любом случае, а MyWidget::saveExpandedState() и MyWidget::saveExpandedState() выглядели чище с моими проектами SIGNAL и SLOT.

2 голосов
/ 23 декабря 2012

Я переделал решение 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;
}

Таким образом, нет необходимости сопоставлять данные. Очевидно - для такого подхода к работе дерево должно оставаться относительно неизменным. Если вы как-то измените порядок элементов дерева - это расширит неправильные узлы.

0 голосов
/ 26 марта 2019

Мой подход состоял в том, чтобы сохранить список расширенных элементов (в виде указателей) и при восстановлении устанавливать только расширенные элементы только в этом списке.Чтобы использовать приведенный ниже код, вам может потребоваться заменить 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()));
    });
}

Я думаю, что эта реализация дает дополнительную гибкость по сравнению с некоторыми другими здесь.По крайней мере, я не мог заставить его работать с моей пользовательской моделью.

Если вы хотите, чтобы новые элементы были расширены, измените код, чтобы сохранить свернутые элементы.

0 голосов
/ 24 июня 2017

Вот версия, которая не использует узлы, имеющие уникальный 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);
};
0 голосов
/ 22 января 2015

Для 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;
} );

Надеюсь, что это кому-то поможет.

...