【问题标题】:Change QSortFilterProxyModel behaviour for multiple column filtering更改多列过滤的 QSortFilterProxyModel 行为
【发布时间】:2017-01-22 04:03:23
【问题描述】:

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

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

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

在第一列我们有名字在第二我们有生日年份。

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

我们如何为自定义 QSortFilterProxyModel 更改此行为?

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

更新...

基于@Mike 的回答: 如果您使用 QMap<int, QRegExp> 与未知列数进行交互会帮助您

【问题讨论】:

  • 您可以将 2 层代理模型相互叠加。一个为名字,另一个为年份。并根据您更改的行编辑更改相应的过滤器。

标签: qt qt5 qtableview qsortfilterproxymodel


【解决方案1】:

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

Qt 文档的Custom Sort/Filter Model Example 显示了一个子类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】:

    基于@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();
    }
    

    【讨论】:

    • @Mike.这是一种通用方式吗?在许多实际情况下,不仅有 2 列,而且用户可能会在不同的列上进行过滤。例如,如果我们想在您的示例中添加一个额外的列(高度),那么应该是视图的源模型吗?与那些应该是孩子....
    • 您可以执行以下操作以使其更短、更中肯: 1. #include &lt;QtWidgets&gt; 2. 按值保存布局、小部件、模型,利用编译器为您管理内存 - 它是少打字! 3. 删除Widget 类,没有必要。 4. 将小部件传递给布局的构造函数以保存setLayout 调用。 5. 首选nullptr 而不是Q_NULLPTR,因为无论如何你都在使用C++11;后者仅适用于 C++11 之前的代码。
    • @IMAN4K ,您可以按照您喜欢的任何顺序链接它们,我会说(为了性能)让它们按照第一个过滤模型将是最需要的过滤器的顺序链接排出来,然后是第二个,等等......
    • @KubaOber ,感谢您的笔记,我将编辑答案。我很抱歉偷了你的答案风格:)
    • @IMAN4K ,如果你有很多列要应用过滤器,我认为my other answer 可能更合适。
    【解决方案3】:

    如果您想使用“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;    
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-11-17
      • 2015-06-16
      • 1970-01-01
      • 2018-04-02
      • 2015-07-01
      • 1970-01-01
      • 2021-10-14
      相关资源
      最近更新 更多