更改 QSortFilterProxyModel 行为以进行多列过滤

问题描述 投票:0回答:5

我们在

QSortFilterProxyModel
上安装了一个
QTableView
和两个(或更多)
QLineEdit
用于过滤视图(基于这些
QLineEdit
的文本)

在我们看来,我们有一个槽,它告诉我们行编辑的字符串和我们想要的当前列。像这样的东西:

void onTextChange(int index, QString ntext) {
    filter.setFilterKeyColumn(index);
    filter.setFilterRegExp(QRegExp(ntext, Qt::CaseInsensitive));
}

第一列有名字,第二列有生日年份。

现在我们在第 2 列中输入年份(例如 1985 年)。到目前为止,过滤还可以,但是当我们切换到第一行编辑并输入名称(例如约翰)时,之前基于年份的过滤将重置。

我们如何改变我们的自定义行为

QSortFilterProxyModel

(实际上,当我们更改过滤器键列时,过滤器模型必须过滤现有视图而不是重置它)

更新...

基于@Mike的回答: 如果您使用

QMap<int, QRegExp>
与未知的列数交互将会对您有所帮助

qt qt5 qtableview qsortfilterproxymodel
5个回答
8
投票

基于@Hayt 的回答和评论。由于您希望在模型上有两个单独的过滤器,因此您可以有两个链接的

QSortFilterProxyModel
(一个根据名称进行过滤,另一个根据年份进行过滤,使用第一个过滤模型作为源模型) .

这是一个完整的示例,说明如何为一个表设置两个单独的过滤器:

#include <QApplication>
#include <QtWidgets>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    //set up GUI
    QWidget w;
    QVBoxLayout layout(&w);
    QHBoxLayout hLayout;
    QLineEdit lineEditName;
    QLineEdit lineEditYear;
    lineEditName.setPlaceholderText("name filter");
    lineEditYear.setPlaceholderText("year filter");
    lineEditYear.setValidator(new QRegExpValidator(QRegExp("[0-9]*")));
    lineEditYear.setMaxLength(4);
    hLayout.addWidget(&lineEditName);
    hLayout.addWidget(&lineEditYear);

    QTableView tableView;
    layout.addLayout(&hLayout);
    layout.addWidget(&tableView);

    //set up models
    QStandardItemModel sourceModel;
    QSortFilterProxyModel yearFilterModel;
    yearFilterModel.setSourceModel(&sourceModel);
    QSortFilterProxyModel nameFilterModel;
    //nameFilterModel uses yearFilterModel as source
    nameFilterModel.setSourceModel(&yearFilterModel);
    //tableView displayes the last model in the chain nameFilterModel
    tableView.setModel(&nameFilterModel);
    nameFilterModel.setFilterKeyColumn(0);
    yearFilterModel.setFilterKeyColumn(1);
    nameFilterModel.setFilterCaseSensitivity(Qt::CaseInsensitive);
    yearFilterModel.setFilterCaseSensitivity(Qt::CaseInsensitive);

    QObject::connect(&lineEditName, &QLineEdit::textChanged, &nameFilterModel,
            static_cast<void (QSortFilterProxyModel::*)(const QString&)>
            (&QSortFilterProxyModel::setFilterRegExp));
    QObject::connect(&lineEditYear, &QLineEdit::textChanged, &yearFilterModel,
            static_cast<void (QSortFilterProxyModel::*)(const QString&)>
            (&QSortFilterProxyModel::setFilterRegExp));

    //fill with dummy data
    QVector<QString> names{"Danny", "Christine", "Lars",
                           "Roberto", "Maria"};
    for(int i=0; i<100; i++){
        QList<QStandardItem*> row;
        row.append(new QStandardItem(names[i%names.size()]));
        row.append(new QStandardItem(QString::number((i%9)+1980)));
        sourceModel.appendRow(row);
    }
    w.show();
    return a.exec();
}

7
投票

您可以对

QSortFilterProxyModel
进行子类化,使其采用两个单独的过滤器(一个用于名称,另一个用于年份),并覆盖
filterAcceptsRow
以仅当两个过滤器都满足时才返回
true

Qt 文档的 自定义排序/过滤器模型示例 显示了一个子类

QSortFilterProxyModel
,除了用于搜索的主字符串过滤器之外,它还可以采用日期过滤器。

这是一个完整的示例,说明如何创建子类

QSortFilterProxyModel
为一个表应用两个单独的过滤器:

#include <QApplication>
#include <QtWidgets>

class NameYearFilterProxyModel : public QSortFilterProxyModel{
    Q_OBJECT
public:
    explicit NameYearFilterProxyModel(QObject* parent= nullptr):
        QSortFilterProxyModel(parent){
        //general parameters for the custom model
        nameRegExp.setCaseSensitivity(Qt::CaseInsensitive);
        yearRegExp.setCaseSensitivity(Qt::CaseInsensitive);
        yearRegExp.setPatternSyntax(QRegExp::RegExp);
        nameRegExp.setPatternSyntax(QRegExp::RegExp);
    }

    bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const  override{
        QModelIndex nameIndex= sourceModel()->index(sourceRow, 0, sourceParent);
        QModelIndex yearIndex= sourceModel()->index(sourceRow, 1, sourceParent);

        QString name= sourceModel()->data(nameIndex).toString();
        QString year= sourceModel()->data(yearIndex).toString();

        return (name.contains(nameRegExp) && year.contains(yearRegExp));
    }
public slots:
    void setNameFilter(const QString& regExp){
        nameRegExp.setPattern(regExp);
        invalidateFilter();
    }
    void setYearFilter(const QString& regExp){
        yearRegExp.setPattern(regExp);
        invalidateFilter();
    }
private:
    QRegExp nameRegExp;
    QRegExp yearRegExp;
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    //set up GUI
    QWidget w;
    QVBoxLayout layout(&w);
    QHBoxLayout hLayout;
    QLineEdit lineEditName;
    QLineEdit lineEditYear;
    lineEditName.setPlaceholderText("name filter");
    lineEditYear.setPlaceholderText("year filter");
    lineEditYear.setValidator(new QRegExpValidator(QRegExp("[0-9]*")));
    lineEditYear.setMaxLength(4);
    hLayout.addWidget(&lineEditName);
    hLayout.addWidget(&lineEditYear);

    QTableView tableView;
    layout.addLayout(&hLayout);
    layout.addWidget(&tableView);

    //set up models
    QStandardItemModel sourceModel;
    NameYearFilterProxyModel filterModel;;
    filterModel.setSourceModel(&sourceModel);
    tableView.setModel(&filterModel);

    QObject::connect(&lineEditName, &QLineEdit::textChanged,
                     &filterModel, &NameYearFilterProxyModel::setNameFilter);
    QObject::connect(&lineEditYear, &QLineEdit::textChanged,
                     &filterModel, &NameYearFilterProxyModel::setYearFilter);

    //fill with dummy data
    QVector<QString> names{"Danny", "Christine", "Lars",
                           "Roberto", "Maria"};
    for(int i=0; i<100; i++){
        QList<QStandardItem*> row;
        row.append(new QStandardItem(names[i%names.size()]));
        row.append(new QStandardItem(QString::number((i%9)+1980)));
        sourceModel.appendRow(row);
    }
    w.show();
    return a.exec();
}

#include "main.moc"

2
投票

如果您想使用“and”过滤器连接 2 个输入,您只需将它们分层即可。

这样的东西应该有效。

QSortFilterProxyModel namefilter;
nameFilter.setFilterKeyColumn(nameColum);
QSortFilterProxyModel yearFilter;
yearFilter.setFilterKeyColumn(yearColumn);

yearFilter.setSourceModel(model);
nameFilter.setSourceModel(&yearFilter);
view.setSource(&nameFilter);

//....


void onTextChange(int index, QString ntext)
{
    switch(index)
    {
        case yearColumn:
            yearFilter.setFilterRegExp(QRegExp(ntext, Qt::CaseInsensitive));
            break;
        case nameColum:
            namefilter.setFilterRegExp(QRegExp(ntext, Qt::CaseInsensitive));
            break;    
    }
}

1
投票

我知道这是一个旧线程,但这里是带有多列过滤器实现的 QSortFilterProxyModel 的更通用版本。我看到有人的评论(Mike)回避了这样的解决方案,但我没有看到任何代码示例。

此设计允许您在创建 SortFilterProxyModel 对象时指示模型是否为多重过滤器。这样做的原因是您可以添加其他自定义行为,而无需创建单独的 QSortFilterProxyModel 子类。换句话说,如果您通过以这种方式重写 QSortFilterProxyModel 函数来创建其他自定义行为,您可以针对给定对象选择您想要的自定义排序/过滤行为,以及您想要的标准排序/过滤行为。显然,如果您不需要或不希望子类具有这种灵活性,您可以通过一些小的调整使其成为您自己的子类。

标题:

class SortFilterProxyModel : public QSortFilterProxyModel
{
    Q_OBJECT
public:
    explicit SortFilterProxyModel(bool multiFilterModel, QObject *parent = nullptr);
    void setMultiFilterRegularExpression(const int &column, const QString &pattern);
    void clearMultiFilter();

protected:
    virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
    virtual bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;

private:
    QMap<int, QRegularExpression> m_multiFilterMap;
    bool m_multiFilterModel = false;

signals:

};

实施:

#include "sortfilterproxymodel.h"

//The constructor takes one additional argument (multiFilterModel) that 
//will dictate filtering behavior. If multiFilterModel is false the 
//setMultiFilterRegularExpression and clearMultifilter will do nothing.

SortFilterProxyModel::SortFilterProxyModel(bool multiFilterModel, QObject *parent) : QSortFilterProxyModel(parent)
{
    m_multiFilterModel = multiFilterModel;
}

//This loads the QMap with the column numbers and their corresponding filters.
//This member function that should be called from your main to filter model.

void SortFilterProxyModel::setMultiFilterRegularExpression(const int &column, const QString &pattern)
{
    if(!m_multiFilterModel)  //notifying that this does nothing and returning
    {
        qDebug() << "Object is not a multiFilterModel!";
        return;
    }

    QRegularExpression filter;
    filter.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
    filter.setPattern(pattern);
    m_multiFilterMap.insert(column, filter);
    invalidateFilter();  //this causes filterAcceptsRow to run
}

//This will effectively unfilter the model by making the pattern for all 
//existing regular expressions in the QMap to an empty string, and then invalidating the filter.
//This member function should be called from main to clear filter.

void SortFilterProxyModel::clearMultiFilter()
{
    if(!m_multiFilterModel)  //notifying that this does nothing and returning
    {
        qDebug() << "Object is not a multiFilterModel!";
        return;
    }

    QMap<int, QRegularExpression>::const_iterator i = m_multiFilterMap.constBegin();

    while(i != m_multiFilterMap.constEnd())
    {
        QRegularExpression blankExpression("");
        m_multiFilterMap.insert(i.key(), blankExpression);
        i++;
    }

    invalidateFilter();  //this causes filterAcceptsRow to run
}

//This checks to see if the model should be multifiltered, else it will 
//work like the standard QSortFilterProxyModel.

bool SortFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{
    if(m_multiFilterModel)
    {
        QMap<int, QRegularExpression>::const_iterator i = m_multiFilterMap.constBegin();

        while(i != m_multiFilterMap.constEnd())
        {
            QModelIndex index = sourceModel()->index(source_row, i.key(), source_parent);
            QString indexValue = sourceModel()->data(index).toString();

            if(!indexValue.contains(i.value()))
            {
                return false;  //if a value doesn't match returns false
            }
            
            i++;
         }  

        return true;  //if all column values match returns true
    }

    //This is standard QSortFilterProxyModel behavoir. It only runs if object is not multiFilterModel

    QModelIndex index = sourceModel()->index(source_row, filterKeyColumn(), source_parent);
    QString indexValue = sourceModel()->data(index).toString();

    return indexValue.contains(filterRegularExpression());

}

0
投票

这里有一个MixedFilterModel继承QSortFilterProxyModel

https://gist.github.com/mindon/a2a0432c9ede07b627f62af94ac95a96

© www.soinside.com 2019 - 2024. All rights reserved.