Ответ 1
Я написал подкласс QIdentityProxyModel
, в котором хранится список цепочек QSortFilterProxyModel
. Он предоставляет интерфейс, похожий на QSortFilterProxyModel
, и принимает логический параметр narrowedDown
, который указывает, сужается ли фильтр. Так что:
- Когда фильтр сужается, к цепочке добавляется новый
QSortFilterProxyModel
, аQIdentityProxyModel
переключается на прокси-сервер нового фильтра в конце цепочки. - В противном случае It удаляет все фильтры в цепочке, создает новую цепочку с одним фильтром, который соответствует текущим критериям фильтрации. После этого
QIdentityProxyModel
переключается на прокси-сервер нового фильтра в цепочке.
Вот программа, которая сравнивает класс с обычным подклассом QSortFilterProxyModel
:
#include <QtWidgets>
class FilterProxyModel : public QSortFilterProxyModel{
public:
explicit FilterProxyModel(QObject* parent= nullptr):QSortFilterProxyModel(parent){}
~FilterProxyModel(){}
//you can override filterAcceptsRow here if you want
};
//the class stores a list of chained FilterProxyModel and proxies the filter model
class NarrowableFilterProxyModel : public QIdentityProxyModel{
Q_OBJECT
//filtering properties of QSortFilterProxyModel
Q_PROPERTY(QRegExp filterRegExp READ filterRegExp WRITE setFilterRegExp)
Q_PROPERTY(int filterKeyColumn READ filterKeyColumn WRITE setFilterKeyColumn)
Q_PROPERTY(Qt::CaseSensitivity filterCaseSensitivity READ filterCaseSensitivity WRITE setFilterCaseSensitivity)
Q_PROPERTY(int filterRole READ filterRole WRITE setFilterRole)
public:
explicit NarrowableFilterProxyModel(QObject* parent= nullptr):QIdentityProxyModel(parent), m_filterKeyColumn(0),
m_filterCaseSensitivity(Qt::CaseSensitive), m_filterRole(Qt::DisplayRole), m_source(nullptr){
}
void setSourceModel(QAbstractItemModel* sourceModel){
m_source= sourceModel;
QIdentityProxyModel::setSourceModel(sourceModel);
for(FilterProxyModel* proxyNode : m_filterProxyChain) delete proxyNode;
m_filterProxyChain.clear();
applyCurrentFilter();
}
QRegExp filterRegExp()const{return m_filterRegExp;}
int filterKeyColumn()const{return m_filterKeyColumn;}
Qt::CaseSensitivity filterCaseSensitivity()const{return m_filterCaseSensitivity;}
int filterRole()const{return m_filterRole;}
void setFilterKeyColumn(int filterKeyColumn, bool narrowedDown= false){
m_filterKeyColumn= filterKeyColumn;
applyCurrentFilter(narrowedDown);
}
void setFilterCaseSensitivity(Qt::CaseSensitivity filterCaseSensitivity, bool narrowedDown= false){
m_filterCaseSensitivity= filterCaseSensitivity;
applyCurrentFilter(narrowedDown);
}
void setFilterRole(int filterRole, bool narrowedDown= false){
m_filterRole= filterRole;
applyCurrentFilter(narrowedDown);
}
void setFilterRegExp(const QRegExp& filterRegExp, bool narrowedDown= false){
m_filterRegExp= filterRegExp;
applyCurrentFilter(narrowedDown);
}
void setFilterRegExp(const QString& filterRegExp, bool narrowedDown= false){
m_filterRegExp.setPatternSyntax(QRegExp::RegExp);
m_filterRegExp.setPattern(filterRegExp);
applyCurrentFilter(narrowedDown);
}
void setFilterWildcard(const QString &pattern, bool narrowedDown= false){
m_filterRegExp.setPatternSyntax(QRegExp::Wildcard);
m_filterRegExp.setPattern(pattern);
applyCurrentFilter(narrowedDown);
}
void setFilterFixedString(const QString &pattern, bool narrowedDown= false){
m_filterRegExp.setPatternSyntax(QRegExp::FixedString);
m_filterRegExp.setPattern(pattern);
applyCurrentFilter(narrowedDown);
}
private:
void applyCurrentFilter(bool narrowDown= false){
if(!m_source) return;
if(narrowDown){ //if the filter is being narrowed down
//instantiate a new filter proxy model and add it to the end of the chain
QAbstractItemModel* proxyNodeSource= m_filterProxyChain.empty()?
m_source : m_filterProxyChain.last();
FilterProxyModel* proxyNode= newProxyNode();
proxyNode->setSourceModel(proxyNodeSource);
QIdentityProxyModel::setSourceModel(proxyNode);
m_filterProxyChain.append(proxyNode);
} else { //otherwise
//delete all filters from the current chain
//and construct a new chain with the new filter in it
FilterProxyModel* proxyNode= newProxyNode();
proxyNode->setSourceModel(m_source);
QIdentityProxyModel::setSourceModel(proxyNode);
for(FilterProxyModel* node : m_filterProxyChain) delete node;
m_filterProxyChain.clear();
m_filterProxyChain.append(proxyNode);
}
}
FilterProxyModel* newProxyNode(){
//return a new child FilterModel with the current properties
FilterProxyModel* proxyNode= new FilterProxyModel(this);
proxyNode->setFilterRegExp(filterRegExp());
proxyNode->setFilterKeyColumn(filterKeyColumn());
proxyNode->setFilterCaseSensitivity(filterCaseSensitivity());
proxyNode->setFilterRole(filterRole());
return proxyNode;
}
//filtering parameters for QSortFilterProxyModel
QRegExp m_filterRegExp;
int m_filterKeyColumn;
Qt::CaseSensitivity m_filterCaseSensitivity;
int m_filterRole;
QAbstractItemModel* m_source;
QList<FilterProxyModel*> m_filterProxyChain;
};
//Demo program that uses the class
//used to fill the table with dummy data
std::string nextString(std::string str){
int length= str.length();
for(int i=length-1; i>=0; i--){
if(str[i] < 'z'){
str[i]++; return str;
} else str[i]= 'a';
}
return std::string();
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//set up GUI
QWidget w;
QGridLayout layout(&w);
QLineEdit lineEditFilter;
lineEditFilter.setPlaceholderText("filter");
QLabel titleTable1("NarrowableFilterProxyModel:");
QTableView tableView1;
QLabel labelTable1;
QLabel titleTable2("FilterProxyModel:");
QTableView tableView2;
QLabel labelTable2;
layout.addWidget(&lineEditFilter,0,0,1,2);
layout.addWidget(&titleTable1,1,0);
layout.addWidget(&tableView1,2,0);
layout.addWidget(&labelTable1,3,0);
layout.addWidget(&titleTable2,1,1);
layout.addWidget(&tableView2,2,1);
layout.addWidget(&labelTable2,3,1);
//set up models
QStandardItemModel sourceModel;
NarrowableFilterProxyModel filterModel1;;
tableView1.setModel(&filterModel1);
FilterProxyModel filterModel2;
tableView2.setModel(&filterModel2);
QObject::connect(&lineEditFilter, &QLineEdit::textChanged, [&](QString newFilter){
QTime stopWatch;
newFilter.prepend("^"); //match from the beginning of the name
bool narrowedDown= newFilter.startsWith(filterModel1.filterRegExp().pattern());
stopWatch.start();
filterModel1.setFilterRegExp(newFilter, narrowedDown);
labelTable1.setText(QString("took: %1 msecs").arg(stopWatch.elapsed()));
stopWatch.start();
filterModel2.setFilterRegExp(newFilter);
labelTable2.setText(QString("took: %1 msecs").arg(stopWatch.elapsed()));
});
//fill model with strings from "aaa" to "zzz" (17576 rows)
std::string str("aaa");
while(!str.empty()){
QList<QStandardItem*> row;
row.append(new QStandardItem(QString::fromStdString(str)));
sourceModel.appendRow(row);
str= nextString(str);
}
filterModel1.setSourceModel(&sourceModel);
filterModel2.setSourceModel(&sourceModel);
w.show();
return a.exec();
}
#include "main.moc"
Примечания:
- Класс предоставляет некоторую оптимизацию только тогда, когда фильтр сужается, поскольку новый сконфигурированный фильтр в конце цепочки не нуждается в поиске по всем исходным образцовым строкам.
- Класс зависит от пользователя, чтобы определить, сужается ли фильтр. То есть, когда пользователь передает
true
для аргументаnarrowedDown
, считается, что фильтр является особым случаем текущего фильтра (даже если это не так). В противном случае он ведет себя точно так же, как и нормальныйQSortFilterProxyModel
, и, возможно, с некоторыми дополнительными накладными расходами (результатом очистки старой цепи фильтра). - Класс может быть дополнительно оптимизирован, когда фильтр не сужается, так что он смотрит в текущей цепочке фильтров на фильтр, похожий на текущий фильтр, и сразу переключается на него (вместо удаления цепочки и начиная новый). Это может быть особенно полезно, когда пользователь удаляет некоторые символы в концевом фильтре
QLineEdit
(т.е. Когда фильтр изменяется с"abcd"
на"abc"
, так как у вас уже должен быть фильтр в цепочке с"abc"
). Но в настоящее время это не реализовано, так как я хочу, чтобы ответ был как можно более минимальным и ясным.