c++qtqt5qtreeviewqsortfilterproxymodel

Expand specific items in a treeview during filtering


Let's consider this tree data :

Root
|-v A1
| |-- textA
|
|-v B1
| |-- textB

When searching "A1" I want the A1 item NOT expanded (but expandable to see the children):

Root
|-> A1

When searching "textA" I want the A1 item expanded (to see the matched child):

Root
|-v A1
| |-- textA

The (standard) filtering provided by QSortFilterProxyModel works fine, but I don't find how to implement the "expand when needed" thing.

I tried some stupid code, and it didn't work:

bool MySortFilterProxyModel::filterAcceptsRow(
  int sourceRow,
  const QModelIndex &sourceParent
) const {
  bool result = QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent);

  if (m_tree) {
  //   if (result)
       m_tree->expand(sourceParent);
  }
 return result;
}

Solution

  • I think, that the following solution might help you a bit.

    I consider it a sane way to expand the items, if they are just few children having the name being searched for. An unresolved issue might be the folding of children, if one changes the search field. Type for example C-0-0-0 in the search field to understand the behavior of my solution.

    #include <QApplication>
    #include <QTreeView>
    #include <QDebug>
    #include <QVBoxLayout>
    #include <QLineEdit>
    #include <QPushButton>
    #include <QStandardItemModel>
    #include <QSortFilterProxyModel>
    
    
    int main(int argc, char **args)
    {
        QApplication app(argc, args);
        auto frame = new QFrame;
        frame->setLayout(new QVBoxLayout);
        auto model = new QStandardItemModel;
        for (auto i=0; i<10; i++)
        {
            auto item = new QStandardItem(QString("A-%1").arg(i));
            model->appendRow(item);
            for (auto j=0; j<10; j++)
            {
                auto item2 = new QStandardItem(QString("B-%1-%2").arg(i).arg(j));
                item->appendRow(item2);
                for (auto k=0; k<10; k++)
                {
                    auto item3 = new QStandardItem(QString("C-%1-%2-%3").arg(i).arg(j).arg(k));
                    item2->appendRow(item3);
                }
            }
        }
    
        auto proxyModel = new QSortFilterProxyModel;
        proxyModel->setSourceModel(model);
        proxyModel->setRecursiveFilteringEnabled(true);
        auto view = new QTreeView;
        view->setModel(proxyModel);
        frame->layout()->addWidget(view);
        auto edit = new QLineEdit;
        frame->layout()->addWidget(edit);
        frame->show();
        QObject::connect(edit, &QLineEdit::textChanged, [&](auto test) {
            proxyModel->setFilterFixedString(test);
            Qt::MatchFlags flags;
            flags.setFlag(Qt::MatchFlag::MatchStartsWith, true);
            flags.setFlag(Qt::MatchFlag::MatchWrap, true);
            flags.setFlag(Qt::MatchFlag::MatchRecursive, true);
            auto indexList=proxyModel->match(proxyModel->index(0,0), Qt::ItemDataRole::DisplayRole, test, -1, flags);
            for (auto index : indexList)
            {
                auto expanderIndex = index.parent();
                while (expanderIndex.isValid())
                {
                    view->expand(expanderIndex);
                    expanderIndex = expanderIndex.parent();
                }
    
            }
            qDebug() << indexList.size();
        });
        app.exec();
    }
    

    Improved Solution

    The following improved solution uses just the found indices by the model without the possible different searching for indices provided by match.

    main.cpp

    #include <QApplication>
    #include <QTreeView>
    #include <QDebug>
    #include <QVBoxLayout>
    #include <QLineEdit>
    #include <QPushButton>
    #include <QStandardItemModel>
    #include <QSortFilterProxyModel>
    #include "MyProxyModel.h"
    
    int main(int argc, char **args)
    {
        QApplication app(argc, args);
        auto frame = new QFrame;
        frame->setLayout(new QVBoxLayout);
        auto model = new QStandardItemModel;
        for (auto i=0; i<10; i++)
        {
            auto item = new QStandardItem(QString("A-%1").arg(i));
            model->appendRow(item);
            for (auto j=0; j<10; j++)
            {
                auto item2 = new QStandardItem(QString("B-%1-%2").arg(i).arg(j));
                item->appendRow(item2);
                for (auto k=0; k<10; k++)
                {
                    auto item3 = new QStandardItem(QString("C-%1-%2-%3").arg(i).arg(j).arg(k));
                    item2->appendRow(item3);
                }
            }
        }
    
        auto proxyModel = new MyProxyModel;
        proxyModel->setSourceModel(model);
        proxyModel->setRecursiveFilteringEnabled(true);
        auto view = new QTreeView;
        view->setModel(proxyModel);
        frame->layout()->addWidget(view);
        auto edit = new QLineEdit;
        frame->layout()->addWidget(edit);
        frame->show();
        QObject::connect(edit, &QLineEdit::textChanged, [&](auto test) {
            proxyModel->setFilterFixedString(test);
            if (test == "") return;
            view->collapseAll();
            QList<QModelIndex> acceptedIndices = proxyModel->findIndices();
            for (auto index : acceptedIndices)
            {
                auto expanderIndex = index.parent();
                while (expanderIndex.isValid())
                {
                    view->expand(expanderIndex);
                    expanderIndex = expanderIndex.parent();
                }
    
            }
            qDebug() << acceptedIndices.size();
        });
        app.exec();
    }
    

    MyProxyModel.h

    #pragma once
    
    #include <QSortFilterProxyModel>
    
    class MyProxyModel : public QSortFilterProxyModel
    {
        Q_OBJECT
    public:
        QList<QModelIndex> findIndices() const
        {
            QList<QModelIndex> ret;
            for (auto iter=0; iter < rowCount(); iter++)
            {
                auto childIndex = index(iter, 0, QModelIndex());
                ret << recursivelyFindIndices(childIndex);
            }
            return ret;
        }
    
    
        bool rowAccepted(int source_row, const QModelIndex& source_parent) const
        {
            return filterAcceptsRow(source_row, source_parent);
        }
    private:
        QList<QModelIndex> recursivelyFindIndices(const QModelIndex& ind) const
        {
            QList<QModelIndex> ret;
            if (rowAccepted(ind.row(), ind.parent()))
            {
                ret << ind;
            }
            for (auto iter=0; iter<rowCount(ind); iter++)
            {
                ret << recursivelyFindIndices(index(iter, 0, ind));
            }
            return ret;
        }
    };