在过滤期间展开树视图中的特定项目
Expand specific items in a treeview during filtering
让我们考虑这个树数据:
Root
|-v A1
| |-- textA
|
|-v B1
| |-- textB
搜索时 "A1" 我希望 A1 项不展开(但展开后可以看到子项):
Root
|-> A1
搜索时 "textA" 我想展开 A1 项(以查看匹配的子项):
Root
|-v A1
| |-- textA
QSortFilterProxyModel 提供的(标准)过滤工作正常,但我找不到如何实现 "expand when needed" 东西。
我尝试了一些愚蠢的代码,但没有成功:
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;
}
我认为,以下解决方案可能会对您有所帮助。
我认为这是扩展项目的明智方式,如果它们只有少数 children 具有正在搜索的名称。如果更改搜索字段,则未解决的问题可能是 children 的折叠。例如,在搜索字段中键入 C-0-0-0
以了解我的解决方案的行为。
#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();
}
改进的解决方案
以下改进的解决方案仅使用模型找到的索引,而无需对 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;
}
};
@Aleph0 回答的一些建议:
- main.cpp片:
view->collapseAll();
QList<QModelIndex> acceptedIndices = proxyModel->findIndices();
for (auto index : acceptedIndices)
{
auto expanderIndex = index.parent();
while (expanderIndex.isValid())
{
view->expand(expanderIndex);
expanderIndex = expanderIndex.parent();
}
}
为了防止重复展开,最好检查一下 expanderIndex 是否已经展开并停止上升。在最坏的情况下,它将使您免于 O(n^2) 复杂性(应该是线性的)。
- 以类似的方式,
MyProxyModel::findIndices
方法可以在没有递归的情况下实现以加快速度(但是,现代编译器应该足够聪明来展开它)
让我们考虑这个树数据:
Root
|-v A1
| |-- textA
|
|-v B1
| |-- textB
搜索时 "A1" 我希望 A1 项不展开(但展开后可以看到子项):
Root
|-> A1
搜索时 "textA" 我想展开 A1 项(以查看匹配的子项):
Root
|-v A1
| |-- textA
QSortFilterProxyModel 提供的(标准)过滤工作正常,但我找不到如何实现 "expand when needed" 东西。
我尝试了一些愚蠢的代码,但没有成功:
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;
}
我认为,以下解决方案可能会对您有所帮助。
我认为这是扩展项目的明智方式,如果它们只有少数 children 具有正在搜索的名称。如果更改搜索字段,则未解决的问题可能是 children 的折叠。例如,在搜索字段中键入 C-0-0-0
以了解我的解决方案的行为。
#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();
}
改进的解决方案
以下改进的解决方案仅使用模型找到的索引,而无需对 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;
}
};
@Aleph0 回答的一些建议:
- main.cpp片:
view->collapseAll();
QList<QModelIndex> acceptedIndices = proxyModel->findIndices();
for (auto index : acceptedIndices)
{
auto expanderIndex = index.parent();
while (expanderIndex.isValid())
{
view->expand(expanderIndex);
expanderIndex = expanderIndex.parent();
}
}
为了防止重复展开,最好检查一下 expanderIndex 是否已经展开并停止上升。在最坏的情况下,它将使您免于 O(n^2) 复杂性(应该是线性的)。
- 以类似的方式,
MyProxyModel::findIndices
方法可以在没有递归的情况下实现以加快速度(但是,现代编译器应该足够聪明来展开它)