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

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

Screenshot of the Custom Sort/Filter Model Example

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 和 String 在内的一组类型,但为了能够按电子邮件地址排序发件人,我们必须首先在给定的字符串中识别地址。

    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)
{
    minDate = date;
    invalidateFilter();
}

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

获取函数,如 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 类提供了一个树视图的默认模型/视图实现;我们的视图实现了应用程序源模型中项的树状表示。我们将在对应群组框上安装的布局中添加我们的视图小部件。

另一方面,代理模型视图包含了多个小部件,用于控制源模型数据结构的各个方面的转换。

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 属性。该属性保存用于过滤源模型内容的正则表达式。请注意,调用 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 数据类型的存储库。使用 addMail()(另一个便利函数)将每个邮件描述添加到模型中。有关详细信息,请参阅 itemviews/customsortfiltermodel/main.cpp

示例项目 @ code.qt.io

版权所有© 2024 The Qt Company Ltd。其中包含的文档贡献版权属于其各自所有者。提供的文档根据自由软件基金会发布的GNU自由文档许可证版本1.3进行许可。Qt及其相关标志是The Qt Company Ltd在芬兰以及其他国家和地区的商标。所有其他商标都是其各自所有者的财产。