自定义排序/过滤模型示例

自定义排序/过滤模型示例说明了如何子类化QSortFilterProxyModel 以执行高级排序和过滤。

自定义排序/筛选模型示例截图

QSortFilterProxyModel 类支持排序和过滤在另一个模型和视图之间传递的数据。

该模型通过将其提供的模型索引映射到新索引(对应不同位置)来转换源模型的结构,供视图使用。就视图而言,这种方法允许对给定的源模型进行重组,而不需要对底层数据进行任何转换,也不会在内存中重复数据。

自定义排序/过滤模型示例由两个类组成:

  • MySortFilterProxyModel 类提供一个自定义代理模型。
  • Window 类提供主应用程序窗口,使用自定义代理模型对标准项目模型进行排序和过滤。

我们将首先查看MySortFilterProxyModel 类,了解如何实现自定义代理模型,然后查看Window 类,了解如何使用该模型。最后,我们将快速浏览main() 函数。

MySortFilterProxyModel 类定义

MySortFilterProxyModel 类继承于QSortFilterProxyModel 类。

由于QAbstractProxyModel 及其子类都是从QAbstractItemModel 派生的,因此关于子类化普通模型的许多建议也适用于代理模型。

另一方面,值得注意的是,QSortFilterProxyModel 的许多默认函数实现都是这样编写的:它们调用相关源模型中的等价函数。对于具有更复杂行为的源模型,可能需要重写这种简单的代理机制。在本例中,我们派生自QSortFilterProxyModel 类,以确保我们的过滤器能识别有效的日期范围,并控制排序行为。

class MySortFilterProxyModel : public QSortFilterProxyModel
{
    Q_OBJECT

public:
    MySortFilterProxyModel(QObject *parent = nullptr);

    QDate filterMinimumDate() const { return minDate; }
    void setFilterMinimumDate(QDate date);

    QDate filterMaximumDate() const { return maxDate; }
    void setFilterMaximumDate(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(QDate date) const;

    QDate minDate;
    QDate maxDate;
};

我们希望能够通过指定给定的时间段来过滤数据。为此,我们实现了自定义的setFilterMinimumDate()setFilterMaximumDate() 函数,以及相应的filterMinimumDate()filterMaximumDate() 函数。我们重新实现了QSortFilterProxyModelfilterAcceptsRow() 函数,使其只接受包含有效日期的数据行,并实现了QSortFilterProxyModel::lessThan() 函数,使其能够按电子邮件地址对发件人进行排序。最后,我们实现了一个dateInRange() 方便函数,用于确定日期是否有效。

MySortFilterProxyModel 类的实现

MySortFilterProxyModel 的构造函数非常简单,只需将父参数传递给基类构造函数即可:

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

MySortFilterProxyModel 实现中最有趣的部分是对QSortFilterProxyModelfilterAcceptsRow() 和lessThan() 函数的重新实现。让我们先看看我们定制的lessThan() 函数。

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

我们希望按照电子邮件地址对发件人进行排序。lessThan() 函数在排序时用作 < 操作符。默认实现可处理包括QDateTime 和字符串在内的一系列类型,但为了能按发件人的电子邮件地址排序,我们必须首先识别给定字符串中的地址:

    if (leftData.userType() == QMetaType::QDateTime) {
        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;
    }
}

我们使用QRegularExpression 来定义我们要查找的地址模式。match() 函数返回一个QRegularExpressionMatch 对象,其中包含匹配结果。如果匹配成功,hasMatch() 返回 true。匹配结果可通过QRegularExpressionMatchcaptured() 函数获取。整个匹配结果的索引为 0,括号中的子表达式的索引从 1 开始(不包括非捕获括号)。

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(filterRegularExpression())
            || sourceModel()->data(index1).toString().contains(filterRegularExpression()))
            && dateInRange(sourceModel()->data(index2).toDate());
}

另一方面,如果给定的行应包含在模型中,则filterAcceptsRow() 函数应返回 true。在我们的示例中,如果主题或发件人包含给定的正则表达式,且日期有效,则该行被接受。

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

我们使用自定义的dateInRange() 函数来确定日期是否有效。

为了能够通过指定给定的时间段来过滤数据,我们还实现了用于获取和设置最小和最大日期的函数:

void MySortFilterProxyModel::setFilterMinimumDate(QDate date)
{
    beginFilterChange();
    minDate = date;
    invalidateRowsFilter();
}

void MySortFilterProxyModel::setFilterMaximumDate(QDate date)
{
    beginFilterChange();
    maxDate = date;
    invalidateRowsFilter();
}

获取函数filterMinimumDate()filterMaximumDate() 非常简单,在头文件中作为内联函数实现。

至此,我们的自定义代理模型就完成了。让我们看看如何在应用程序中使用它。

窗口类定义

CustomFilter 类继承于QWidget ,是本示例的主应用程序窗口:

class Window : public QWidget
{
    Q_OBJECT

public:
    Window();

    void setSourceModel(QAbstractItemModel *model);

private slots:
    void textFilterChanged();
    void dateFilterChanged();

private:
    MySortFilterProxyModel *proxyModel;

    QGroupBox *sourceGroupBox;
    QGroupBox *proxyGroupBox;
    QTreeView *sourceView;
    QTreeView *proxyView;
    QLabel *filterPatternLabel;
    QLabel *fromLabel;
    QLabel *toLabel;
    FilterWidget *filterWidget;
    QDateEdit *fromDateEdit;
    QDateEdit *toDateEdit;
};

我们实现了两个私有槽textFilterChanged()dateFilterChanged() ,以响应用户更改筛选模式、大小写敏感性或任何日期的操作。此外,我们还实现了一个公共setSourceModel() 方便函数,用于设置模型/视图关系。

窗口类的实现

在本例中,我们选择在main () 函数中创建和设置源模型(稍后我们将再次讨论)。因此,在构建主应用程序窗口时,我们假定已经存在源模型,并从创建自定义代理模型的实例开始:

Window::Window()
{
    proxyModel = new MySortFilterProxyModel(this);

我们设置dynamicSortFilter 属性,该属性用于确定代理模型是否动态排序和过滤。通过将该属性设置为 true,我们可以确保在源模型的内容发生变化时对模型进行排序和过滤。

主应用程序窗口显示源模型和代理模型的视图。源视图非常简单:

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

QTreeView 类提供了树形视图的默认模型/视图实现。我们的视图实现了应用程序源模型中项的树形表示。

sourceLayout->addWidget(sourceView);
sourceGroupBox = new QGroupBox(tr("Original Model"));
sourceGroupBox->setLayout(sourceLayout);

QTreeView 类提供了树形视图的默认模型/视图实现;我们的视图实现了应用程序源模型中项的树形表示。我们将视图部件添加到一个布局中,并将其安装到相应的组框中。

另一方面,代理模型视图包含了多个控制源模型数据结构转换各方面的 widget:

filterWidget = new FilterWidget;
filterWidget->setText(tr("Grace|Sports"));
connect(filterWidget, &FilterWidget::filterChanged, this, &Window::textFilterChanged);

filterPatternLabel = new QLabel(tr("&Filter pattern:"));
filterPatternLabel->setBuddy(filterWidget);

fromDateEdit = new QDateEdit;
fromDateEdit->setDate(QDate(1970, 01, 01));
fromLabel = new QLabel(tr("F&rom:"));
fromLabel->setBuddy(fromDateEdit);

toDateEdit = new QDateEdit;
toDateEdit->setDate(QDate(2099, 12, 31));
toLabel = new QLabel(tr("&To:"));
toLabel->setBuddy(toDateEdit);

connect(filterWidget, &QLineEdit::textChanged,
        this, &Window::textFilterChanged);
connect(fromDateEdit, &QDateTimeEdit::dateChanged,
        this, &Window::dateFilterChanged);
connect(toDateEdit, &QDateTimeEdit::dateChanged,
this, &Window::dateFilterChanged);

请注意,每当用户更改其中一个过滤选项时,我们都必须显式地重新应用该过滤器。这可以通过将各种编辑器连接到更新代理模型的函数来实现。

proxyView = new QTreeView;
proxyView->setRootIsDecorated(false);
proxyView->setAlternatingRowColors(true);
proxyView->setModel(proxyModel);
proxyView->setSortingEnabled(true);
proxyView->sortByColumn(1, Qt::AscendingOrder);

QGridLayout *proxyLayout = new QGridLayout;
proxyLayout->addWidget(proxyView, 0, 0, 1, 3);
proxyLayout->addWidget(filterPatternLabel, 1, 0);
proxyLayout->addWidget(filterWidget, 1, 1);
proxyLayout->addWidget(fromLabel, 3, 0);
proxyLayout->addWidget(fromDateEdit, 3, 1, 1, 2);
proxyLayout->addWidget(toLabel, 4, 0);
proxyLayout->addWidget(toDateEdit, 4, 1, 1, 2);

proxyGroupBox = new QGroupBox(tr("Sorted/Filtered Model"));
proxyGroupBox->setLayout(proxyLayout);

排序将由视图处理。我们要做的就是通过设置QTreeView::sortingEnabled 属性(默认为 false)来启用代理视图的排序功能。然后,我们将所有过滤部件和代理视图添加到一个布局中,并将其安装到相应的组框中。

QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addWidget(sourceGroupBox);
mainLayout->addWidget(proxyGroupBox);
setLayout(mainLayout);

setWindowTitle(tr("Custom Sort/Filter Model"));
resize(500, 450);
}

最后,将两个组框放入另一个布局(安装在主应用程序部件上),然后自定义应用程序窗口。

如上所述,我们在main () 函数中创建源模型,调用Window::setSourceModel() 函数使应用程序使用它:

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

    for (int i = 0; i < proxyModel->columnCount(); ++i)
        proxyView->resizeColumnToContents(i);
    for (int i = 0; i < model->columnCount(); ++i)
        sourceView->resizeColumnToContents(i);
}

QSortFilterProxyModel::setSourceModel() 函数使代理模型处理给定模型中的数据,在本例中是处理邮件模型。视图小部件从QAbstractItemModel 类继承的setModel() 会设置视图要显示的模型。请注意,后一个函数还将创建和设置一个新的选择模型。

void Window::textFilterChanged()
{
    FilterWidget::PatternSyntax s = filterWidget->patternSyntax();
    QString pattern = filterWidget->text();
    switch (s) {
    case FilterWidget::Wildcard:
        pattern = QRegularExpression::wildcardToRegularExpression(pattern);
        break;
    case FilterWidget::FixedString:
        pattern = QRegularExpression::escape(pattern);
        break;
    default:
        break;
    }

    QRegularExpression::PatternOptions options = QRegularExpression::NoPatternOption;
    if (filterWidget->caseSensitivity() == Qt::CaseInsensitive)
        options |= QRegularExpression::CaseInsensitiveOption;
    QRegularExpression regularExpression(pattern, options);
    proxyModel->setFilterRegularExpression(regularExpression);
}

每当用户更改过滤模式或大小写敏感度时,都会调用textFilterChanged() 函数。

我们首先检索首选语法(FilterWidget::PatternSyntax 枚举用于解释给定模式的含义),然后确定首选大小写敏感度。根据这些偏好和当前过滤模式,我们设置了代理模型的filterRegularExpression 属性。filterRegularExpression 属性包含用于过滤源模型内容的正则表达式。请注意,调用QSortFilterProxyModelsetFilterRegularExpression() 函数也会更新模型。

void Window::dateFilterChanged()
{
    proxyModel->setFilterMinimumDate(fromDateEdit->date());
    proxyModel->setFilterMaximumDate(toDateEdit->date());
}

每当用户修改有效日期范围时,就会调用dateFilterChanged() 函数。我们从用户界面获取新日期,并调用相应函数(由自定义代理模型提供)来设置代理模型的最小和最大日期。如上所述,调用这些函数也会更新模型。

主()函数

在本例中,我们通过在main () 函数中创建模型,将应用程序与源模型分离开来。首先创建应用程序,然后创建源模型:

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    Window window;
    window.setSourceModel(createMailModel(&window));
    window.show();
    return app.exec();
}

createMailModel() 函数是一个方便的函数,用于简化构造函数。它的作用只是创建并返回一个描述电子邮件集合的模型。该模型是QStandardItemModel 类的一个实例,即用于存储自定义数据的通用模型,通常用作标准 Qt XML 数据类型的存储库。每封邮件的描述都通过addMail() 添加到模型中,这是另一个方便的函数。详见itemviews/customsortfiltermodel/main.cpp

示例项目 @ code.qt.io

© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.