Qt model view framework: QSortFilterProxyModel

1, Description

QSortFilterProxyModel can be used to sort and filter items. The model does not need any transformation of the underlying data, nor does it need to copy the data in memory.

To sort and filter the items provided by the custom model. You need to create a QSortFilterProxyModel, call setSourceModel() with MyItemModel as a parameter, and install QSortFilterProxyModel on the view:

        QTreeView *treeView = new QTreeView;
        MyItemModel *sourceModel = new MyItemModel(this);
        QSortFilterProxyModel *proxyModel = new QSortFilterProxyModel(this);

        proxyModel->setSourceModel(sourceModel);
        treeView->setModel(proxyModel);

The raw data is displayed in the view. Any changes made through QSortFilterProxyModel are applied to the original model.

QSortFilterProxyModel acts as a wrapper for the original model. Source QModelIndexes and sorted / filtered model indexes are converted to each other. Use mapToSource(), mapFromSource(), mapSelectionToSource(), mapSelectionFromSource().

By default, as long as the original model changes, the model will dynamically reorder and re filter the data. You can change this behavior by setting the dynamicSortFilter property.

2, Attribute member

1,autoAcceptChildRows : bool

If true, the children of the acceptance line will not be filtered out (even when they themselves are filtered out). The default value is false.

2,dynamicSortFilter : bool

Whether to sort and filter dynamically when the content of the source model changes.

Note that when dynamicsortfilter is true, the source model should not be updated through the proxy model. For example, if you set up a proxy model on qcombobox, functions that update the model (such as addItem()) will not work as expected. Another way is to set dynamicSortFilter to false and call sort() after adding the item to QComboBox.

3,filterCaseSensitivity : Qt::CaseSensitivity

Whether the QRegularExpression mode used to filter the content of the source model is case sensitive. The default is case sensitive.

4,filterKeyColumn : int

The column from which the key used to filter the content of the source model is read. The default value is 0. If the value is - 1, the key is read from all columns.

5,filterRegularExpression : QRegularExpression

QRegularExpression used to filter the content of the source model

Setting this property through the QRegularExpression overload overrides the current filterCaseSensitivity. By default, QRegularExpression is an empty string that matches everything.

If QRegularExpression or an empty string is not set, everything in the source model is accepted.

6,filterRole : int

The project role used to query the source model data when filtering the project. The default value is Qt::DisplayRole.

7,isSortLocaleAware : bool

Whether to consider local factors when sorting strings. Not considered by default( QLocale).

8,recursiveFilteringEnabled : bool

Whether the filter is applied recursively to children, and its parent will also be visible for any matching children. The default value is false.

9,sortCaseSensitivity : Qt::CaseSensitivity

The case sensitivity setting used to compare strings when sorting. By default, sorting is case sensitive.

10,sortRole : int

Query the data role of the source model when sorting items. The default value is Qt::DisplayRole.

3, Important member function

1,void invalidate()

Invalidates the current sorting and filtering.

2,void setFilterFixedString(const QString &pattern)

Sets the string used to filter the content of the source model.

3,void setFilterWildcard(const QString &pattern)

Sets the wildcard expression used to filter the content of the source model.

4,void setFilterWildcard(const QString &pattern)

Sets the wildcard expression used to filter the content of the source model.

5,bool filterAcceptsColumn(int source_column, const QModelIndex &source_parent)

If source_column and source_ Returns true if the items in the column indicated by parent should be included in the model.

The default implementation always returns true.

6,bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) 

If by a given source_row and source_ Returns true if the items in the line indicated by parent should be included in the model.

If the value held by the related item matches the filter string, wildcard string, or regular expression, the default implementation returns true.

Note: by default, Qt::DisplayRole is used to determine whether the row should be accepted. This can be changed by setting the filterRole property.

7,void invalidateColumnsFilter()

Invalidates the current filter for the column.

This function should be called if custom filtering is in progress (through filterAcceptsColumn()) and the filter parameters have changed. This differs from invalidefilter () in that it does not call filterAcceptsRow(), but only filteracceptscolumn (). If you want to hide or show columns with unchanged rows, you can use it instead of invalidefilter().

8,void invalidateFilter()

Invalidates the current filter.

This function should be called if a custom filter is in progress (for example, filterAcceptsRow()) and the filter parameters have changed.

9,void invalidateRowsFilter()

Invalidates the current filter for the row.

This function should be called if custom filtering is in progress (through filterAcceptsRow()) and the filter parameters have changed. This differs from invalidefilter () in that it does not call filterAcceptsColumn(), but only filteracceptsrow (). If you want to hide or show rows with unchanged columns, you can use it instead of invalidefilter ().

10,bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) 

If index source_ The value of the item referenced by left is less than the index source_right refers to the value of the item, returns true, otherwise returns false.

This function is used as the < operator when sorting and handles the following QVariant types:

  • QMetaType::Int
  • QMetaType::UInt
  • QMetaType::LongLong
  • QMetaType::ULongLong
  • QMetaType::Float
  • QMetaType::Double
  • QMetaType::QChar
  • QMetaType::QDate
  • QMetaType::QTime
  • QMetaType::QDateTime
  • QMetaType::QString

Any other type will be converted to QString using QVariant::toString().

By default, Qt::DisplayRole associated with QModelIndexes is used for comparison. This can be changed by setting the sortRole property.

Note: the index passed in is the index of the source model.

4, Use example

1. Official demo: Basic Sort/Filter Model Example

Window::Window()
{
    proxyModel = new QSortFilterProxyModel;

    sourceView = new QTreeView;
    sourceView->setRootIsDecorated(false);
    sourceView->setAlternatingRowColors(true);

    proxyView = new QTreeView;
    proxyView->setRootIsDecorated(false);
    proxyView->setAlternatingRowColors(true);
    proxyView->setModel(proxyModel);
    proxyView->setSortingEnabled(true);

    auto sortCaseSensitivityCheckBox = new QCheckBox(tr("Case sensitive sorting"));
    filterCaseSensitivityCheckBox = new QCheckBox(tr("Case sensitive filter"));

    filterPatternLineEdit = new QLineEdit;
    auto filterPatternLabel = new QLabel(tr("Filter characters:"));
    filterPatternLabel->setBuddy(filterPatternLineEdit);

    filterSyntaxComboBox = new QComboBox;
    filterSyntaxComboBox->addItem(tr("regular expression "), QRegExp::RegExp);
    filterSyntaxComboBox->addItem(tr("wildcard"), QRegExp::Wildcard);
    filterSyntaxComboBox->addItem(tr("Fixed string"), QRegExp::FixedString);
    auto filterSyntaxLabel = new QLabel(tr("Filter syntax:"));
    filterSyntaxLabel->setBuddy(filterSyntaxComboBox);

    filterColumnComboBox = new QComboBox;
    filterColumnComboBox->addItem(tr("Subject"));
    filterColumnComboBox->addItem(tr("Sender"));
    filterColumnComboBox->addItem(tr("Date"));
    auto filterColumnLabel = new QLabel(tr("Filter rows:"));
    filterColumnLabel->setBuddy(filterColumnComboBox);

    connect(filterPatternLineEdit, &QLineEdit::textChanged,this, &Window::filterRegExpChanged);
    connect(filterSyntaxComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged),this, &Window::filterRegExpChanged);
    connect(filterColumnComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged),this, &Window::filterColumnChanged);
    connect(filterCaseSensitivityCheckBox, &QAbstractButton::toggled,this, &Window::filterRegExpChanged);
    connect(sortCaseSensitivityCheckBox, &QAbstractButton::toggled,this, &Window::sortChanged);

    auto sourceGroupBox = new QGroupBox(tr("Original model"));
    auto proxyGroupBox = new QGroupBox(tr("sort/Filtering model"));

    QHBoxLayout *sourceLayout = new QHBoxLayout;
    sourceLayout->addWidget(sourceView);
    sourceGroupBox->setLayout(sourceLayout);

    QGridLayout *proxyLayout = new QGridLayout;
    proxyLayout->addWidget(proxyView, 0, 0, 1, 3);
    proxyLayout->addWidget(filterPatternLabel, 1, 0);
    proxyLayout->addWidget(filterPatternLineEdit, 1, 1, 1, 2);
    proxyLayout->addWidget(filterSyntaxLabel, 2, 0);
    proxyLayout->addWidget(filterSyntaxComboBox, 2, 1, 1, 2);
    proxyLayout->addWidget(filterColumnLabel, 3, 0);
    proxyLayout->addWidget(filterColumnComboBox, 3, 1, 1, 2);
    proxyLayout->addWidget(filterCaseSensitivityCheckBox, 4, 0, 1, 2);
    proxyLayout->addWidget(sortCaseSensitivityCheckBox, 4, 2);
    proxyGroupBox->setLayout(proxyLayout);

    QVBoxLayout *mainLayout = new QVBoxLayout;

    mainLayout->addWidget(sourceGroupBox);
    mainLayout->addWidget(proxyGroupBox);

    setLayout(mainLayout);
    resize(500, 450);

    proxyView->sortByColumn(1, Qt::AscendingOrder);
    filterColumnComboBox->setCurrentIndex(1);

    filterPatternLineEdit->setText("Andy|Grace");
    filterCaseSensitivityCheckBox->setChecked(true);
    sortCaseSensitivityCheckBox->setChecked(true);
}

void Window::setSourceModel(QAbstractItemModel *model)
{
    proxyModel->setSourceModel(model);
    sourceView->setModel(model);
}

void Window::filterRegExpChanged()
{
    QRegExp::PatternSyntax syntax = QRegExp::PatternSyntax(filterSyntaxComboBox->itemData(filterSyntaxComboBox->currentIndex()).toInt());
    Qt::CaseSensitivity caseSensitivity = filterCaseSensitivityCheckBox->isChecked() ? Qt::CaseSensitive : Qt::CaseInsensitive;

    QRegExp regExp(filterPatternLineEdit->text(), caseSensitivity, syntax);
    proxyModel->setFilterRegExp(regExp);
}

void Window::filterColumnChanged()
{
    proxyModel->setFilterKeyColumn(filterColumnComboBox->currentIndex());
}

void Window::sortChanged()
{
    proxyModel->setSortCaseSensitivity(sortCaseSensitivityCheckBox->isChecked() ? Qt::CaseSensitive : Qt::CaseInsensitive);
}

2. Official demo: Custom Sort/Filter Model Example

#ifndef MYSORTFILTERPROXYMODEL_H
#define MYSORTFILTERPROXYMODEL_H

#include <QDate>
#include <QSortFilterProxyModel>

class MySortFilterProxyModel : public QSortFilterProxyModel
{
    Q_OBJECT

public:
    MySortFilterProxyModel(QObject *parent = nullptr);
    QDate filterMinimumDate() const
    {
        return minDate;
    }
    QDate filterMaximumDate() const
    {
        return maxDate;
    }
    void setFilterMinimumDate(const QDate &date);
    void setFilterMaximumDate(const QDate &date);

protected:
    bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
    bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;

private:
    bool dateInRange(const QDate &date) const;
    QDate minDate;
    QDate maxDate;
};

#endif // MYSORTFILTERPROXYMODEL_H
#include <QtWidgets>
#include "mysortfilterproxymodel.h"

MySortFilterProxyModel::MySortFilterProxyModel(QObject *parent)
    : QSortFilterProxyModel(parent)
{
}

void MySortFilterProxyModel::setFilterMinimumDate(const QDate &date)
{
    minDate = date;
    invalidateFilter();
}

void MySortFilterProxyModel::setFilterMaximumDate(const QDate &date)
{
    maxDate = date;
    invalidateFilter();
}

bool MySortFilterProxyModel::filterAcceptsRow(int sourceRow,const QModelIndex &sourceParent) const
{
    QModelIndex index0 = sourceModel()->index(sourceRow, 0, sourceParent);
    QModelIndex index1 = sourceModel()->index(sourceRow, 1, sourceParent);
    QModelIndex index2 = sourceModel()->index(sourceRow, 2, sourceParent);

    return (sourceModel()->data(index0).toString().contains(filterRegExp()) ||
            sourceModel()->data(index1).toString().contains(filterRegExp()))
            && dateInRange(sourceModel()->data(index2).toDate());
}

bool MySortFilterProxyModel::lessThan(const QModelIndex &left,
                                      const QModelIndex &right) const
{
    QVariant leftData = sourceModel()->data(left);
    QVariant rightData = sourceModel()->data(right);

    if (leftData.type() == QVariant::DateTime)
    {
        return leftData.toDateTime() < rightData.toDateTime();
    }
    else
    {
        static const QRegularExpression emailPattern("[\\w\\.]*@[\\w\\.]*");

        QString leftString = leftData.toString();
        if (left.column() == 1)
        {
            const QRegularExpressionMatch match = emailPattern.match(leftString);
            if (match.hasMatch())
                leftString = match.captured(0);
        }
        QString rightString = rightData.toString();
        if (right.column() == 1)
        {
            const QRegularExpressionMatch match = emailPattern.match(rightString);
            if (match.hasMatch())
                rightString = match.captured(0);
        }

        return QString::localeAwareCompare(leftString, rightString) < 0;
    }
}

bool MySortFilterProxyModel::dateInRange(const QDate &date) const
{
    return (!minDate.isValid() || date > minDate) && (!maxDate.isValid() || date < maxDate);
}

use:

    proxyModel->setFilterMinimumDate(fromDateEdit->date());
    proxyModel->setFilterMaximumDate(toDateEdit->date());

This model sets a date range to match the data in this range.

Added by vtbruinsfan on Thu, 23 Dec 2021 07:40:53 +0200