Search code examples
qtqt5qtableviewqsortfilterproxymodel

Change QSortFilterProxyModel behaviour for multiple column filtering


We have a QSortFilterProxyModel installed on a QTableView and two (or more) QLineEdit for filtering the view (based on the text of these QLineEdits)

In our view, we have a slot that tells us the string of line edits and the current column that we want. Something like this :

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

On the first column we have names, in the second we have year of birth.

Now we enter a year for column 2 (for example, 1985). Until now, filtering is ok, but when we switch to the first line edit and enter a name (for example, John), the previous filtering based on year will reset.

How could we change this behaviour for our custom QSortFilterProxyModel?


Solution

  • You can subclass QSortFilterProxyModel, to make it take two separate filters (one for the name and the other for the year), and override filterAcceptsRow to return true only when both filters are satisfied.

    The Qt documentation's Custom Sort/Filter Model Example shows a subclassed QSortFilterProxyModel that can take filters for dates in addition to the main string filter used for searching.

    Here is a fully working example on how to make a subclassed QSortFilterProxyModel apply two separate filters for one table:

    Example Screenshot

    #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"