Auf dieser Seite

Beispiel für ein benutzerdefiniertes Sortier-/Filtermodell

Das Beispiel für ein benutzerdefiniertes Sortier-/Filtermodell veranschaulicht, wie die Unterklasse QSortFilterProxyModel eine erweiterte Sortierung und Filterung durchführt.

Screenshot des Beispiels für das benutzerdefinierte Sortier-/Filtermodell

Die Klasse QSortFilterProxyModel bietet Unterstützung für das Sortieren und Filtern von Daten, die zwischen einem anderen Modell und einer Ansicht übergeben werden.

Das Modell wandelt die Struktur eines Quellmodells um, indem es die Modellindizes, die es liefert, auf neue Indizes abbildet, die verschiedenen Orten entsprechen, die die Ansichten verwenden können. Dieser Ansatz ermöglicht die Umstrukturierung eines bestimmten Quellmodells in Bezug auf die Ansichten, ohne dass die zugrunde liegenden Daten transformiert werden müssen und ohne dass die Daten im Speicher dupliziert werden.

Das Beispiel des benutzerdefinierten Sortier-/Filtermodells besteht aus zwei Klassen:

  • Die Klasse MySortFilterProxyModel stellt ein benutzerdefiniertes Proxy-Modell bereit.
  • Die Klasse Window stellt das Hauptanwendungsfenster bereit, das das benutzerdefinierte Proxy-Modell zum Sortieren und Filtern eines Standard-Elementmodells verwendet.

Wir werden zunächst einen Blick auf die Klasse MySortFilterProxyModel werfen, um zu sehen, wie das benutzerdefinierte Proxy-Modell implementiert ist, und dann einen Blick auf die Klasse Window werfen, um zu sehen, wie das Modell verwendet wird. Schließlich werfen wir einen kurzen Blick auf die Funktion main().

MySortFilterProxyModel Klassendefinition

Die Klasse MySortFilterProxyModel erbt von der Klasse QSortFilterProxyModel.

Da QAbstractProxyModel und seine Unterklassen von QAbstractItemModel abgeleitet sind, gelten viele der Ratschläge zur Unterklassifizierung normaler Modelle auch für Proxy-Modelle.

Andererseits ist es erwähnenswert, dass viele der Standardimplementierungen von Funktionen auf QSortFilterProxyModel so geschrieben sind, dass sie die entsprechenden Funktionen im jeweiligen Quellmodell aufrufen. Dieser einfache Proxy-Mechanismus muss möglicherweise für Quellmodelle mit komplexerem Verhalten außer Kraft gesetzt werden. In diesem Beispiel leiten wir von der Klasse QSortFilterProxyModel ab, um sicherzustellen, dass unser Filter einen gültigen Datumsbereich erkennen kann, und um das Sortierverhalten zu steuern.

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;
};

Wir möchten in der Lage sein, unsere Daten durch Angabe eines bestimmten Zeitraums zu filtern. Aus diesem Grund implementieren wir die benutzerdefinierten Funktionen setFilterMinimumDate() und setFilterMaximumDate() sowie die entsprechenden Funktionen filterMinimumDate() und filterMaximumDate(). Wir implementieren die Funktion filterAcceptsRow() von QSortFilterProxyModel neu, um nur Zeilen mit gültigen Daten zu akzeptieren, und QSortFilterProxyModel::lessThan(), um die Absender nach ihren E-Mail-Adressen sortieren zu können. Schließlich implementieren wir eine dateInRange() Komfortfunktion, mit der wir feststellen können, ob ein Datum gültig ist.

Implementierung der Klasse MySortFilterProxyModel

Der MySortFilterProxyModel -Konstruktor ist trivial und übergibt den übergeordneten Parameter an den Konstruktor der Basisklasse:

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

Die interessantesten Teile der Implementierung von MySortFilterProxyModel sind die Reimplementierungen der Funktionen filterAcceptsRow() und lessThan() von QSortFilterProxyModel. Werfen wir zunächst einen Blick auf unsere angepasste lessThan() Funktion.

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

Wir wollen die Absender nach ihren E-Mail-Adressen sortieren. Die Funktion lessThan() wird beim Sortieren als <-Operator verwendet. Die Standardimplementierung verarbeitet eine Reihe von Typen, darunter QDateTime und String, aber um die Absender nach ihren E-Mail-Adressen sortieren zu können, müssen wir zunächst die Adresse innerhalb der angegebenen Zeichenkette identifizieren:

    if (leftData.userType() == QMetaType::QDateTime)
        return leftData.toDateTime() < rightData.toDateTime();

    static const QRegularExpression emailPattern(u"[\\w\\.]*@[\\w\\.]*"_s);

    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;
}

Wir verwenden QRegularExpression, um ein Muster für die gesuchten Adressen zu definieren. Die Funktion match() gibt ein Objekt QRegularExpressionMatch zurück, das das Ergebnis des Abgleichs enthält. Wenn es eine Übereinstimmung gibt, gibt hasMatch() true zurück. Das Ergebnis der Übereinstimmung kann mit der Funktion captured() von QRegularExpressionMatch abgerufen werden. Die gesamte Übereinstimmung hat den Index 0 und die eingeklammerten Unterausdrücke haben Indizes ab 1 (außer nicht einklammernden Klammern).

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

Von der Funktion filterAcceptsRow() wird hingegen erwartet, dass sie true zurückgibt, wenn die angegebene Zeile in das Modell aufgenommen werden soll. In unserem Beispiel wird eine Zeile akzeptiert, wenn entweder der Betreff oder der Absender den angegebenen regulären Ausdruck enthält und das Datum gültig ist.

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

Wir verwenden unsere benutzerdefinierte Funktion dateInRange(), um festzustellen, ob ein Datum gültig ist.

Um unsere Daten durch Angabe eines bestimmten Zeitraums filtern zu können, implementieren wir auch Funktionen zum Abrufen und Setzen des Mindest- und Höchstdatums:

void MySortFilterProxyModel::setFilterMinimumDate(QDate date)
{
    beginFilterChange();
    minDate = date;
    endFilterChange(QSortFilterProxyModel::Direction::Rows);
}

void MySortFilterProxyModel::setFilterMaximumDate(QDate date)
{
    beginFilterChange();
    maxDate = date;
    endFilterChange(QSortFilterProxyModel::Direction::Rows);
}

Die "get"-Funktionen, filterMinimumDate() und filterMaximumDate(), sind trivial und als Inline-Funktion in der Header-Datei implementiert.

Damit ist unser benutzerdefiniertes Proxy-Modell fertig. Schauen wir uns nun an, wie wir es in einer Anwendung verwenden können.

Definition der Fensterklasse

Die Klasse CustomFilter erbt von QWidget und stellt das Hauptfenster der Anwendung in diesem Beispiel dar:

class Window : public QWidget
{
    Q_OBJECT

public:
    Window();

    void setSourceModel(QAbstractItemModel *model);

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

private:
    MySortFilterProxyModel *proxyModel;

    QTreeView *sourceView;
    QTreeView *proxyView;
    FilterWidget *filterWidget;
    QDateEdit *fromDateEdit;
    QDateEdit *toDateEdit;
};

Wir implementieren zwei private Slots, textFilterChanged() und dateFilterChanged(), um darauf zu reagieren, dass der Benutzer das Filtermuster, die Groß- und Kleinschreibung oder eines der Daten ändert. Außerdem implementieren wir eine öffentliche Funktion setSourceModel(), um die Beziehung zwischen Modell und Ansicht einzurichten.

Implementierung der Fensterklasse

In diesem Beispiel haben wir uns dafür entschieden, das Quellmodell in der Funktion main () zu erstellen und einzustellen (auf die wir später zurückkommen werden). Bei der Erstellung des Hauptanwendungsfensters gehen wir also davon aus, dass bereits ein Quellmodell existiert, und beginnen mit der Erstellung einer Instanz unseres benutzerdefinierten Proxy-Modells:

Window::Window()
    : proxyModel(new MySortFilterProxyModel(this)),

Wir setzen die Eigenschaft dynamicSortFilter, die festlegt, ob das Proxy-Modell dynamisch sortiert und gefiltert wird. Indem wir diese Eigenschaft auf true setzen, stellen wir sicher, dass das Modell sortiert und gefiltert wird, sobald sich der Inhalt des Quellmodells ändert.

Im Hauptfenster der Anwendung werden sowohl das Quellmodell als auch das Proxymodell angezeigt. Die Quellansicht ist recht einfach:

sourceView->setRootIsDecorated(false);
sourceView->setAlternatingRowColors(true);

Die Klasse QTreeView bietet eine Standard-Modell/Ansicht-Implementierung einer Baumansicht. Unsere Ansicht implementiert eine Baumdarstellung der Elemente im Quellmodell der Anwendung.

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

Die Klasse QTreeView bietet eine Standardmodell/Ansichtsimplementierung einer Baumansicht; unsere Ansicht implementiert eine Baumdarstellung von Elementen im Quellmodell der Anwendung. Wir fügen unser View-Widget zu einem Layout hinzu, das wir in einem entsprechenden Gruppenrahmen installieren.

Die Proxymodellansicht hingegen enthält mehrere Widgets, die die verschiedenen Aspekte der Umwandlung der Datenstruktur des Quellmodells steuern:

filterWidget->setText(tr("Grace|Sports"));
fromDateEdit->setDate(QDate(1970, 01, 01));
toDateEdit->setDate(QDate(2099, 12, 31));

Wenn der Benutzer eine der Filteroptionen ändert, müssen wir den Filter explizit neu anwenden. Dies geschieht, indem die verschiedenen Editoren mit Funktionen verbunden werden, die das Proxy-Modell aktualisieren.

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

Die Sortierung wird von der Ansicht übernommen. Alles, was wir tun müssen, ist, die Sortierung für unsere Proxy-Ansicht zu aktivieren, indem wir die Eigenschaft QTreeView::sortingEnabled setzen (die standardmäßig falsch ist). Dann fügen wir alle Filter-Widgets und die Proxy-Ansicht zu einem Layout hinzu, das wir in einem entsprechenden Gruppenrahmen installieren.

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

auto *proxyGroupBox = new QGroupBox(tr("Sorted/Filtered Model"));
auto *proxyLayout = new QVBoxLayout(proxyGroupBox);
proxyLayout->addWidget(proxyView);
auto *formLayout = new QFormLayout;
proxyLayout->addLayout(formLayout);
formLayout->addRow(tr("&Filter pattern:"), filterWidget);
formLayout->addRow(tr("F&rom:"), fromDateEdit);
formLayout->addRow(tr("&To:"), toDateEdit);

Nachdem wir unsere beiden Gruppenrahmen in ein anderes Layout eingefügt haben, das wir auf unserem Hauptanwendungs-Widget installieren, passen wir das Anwendungsfenster an.

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

    setWindowTitle(tr("Custom Sort/Filter Model"));
    auto screenGeometry = screen()->geometry();
    resize(screenGeometry.width() / 2, screenGeometry.height() * 2 / 3);
}

Wie oben erwähnt, erstellen wir das Quellmodell in der Funktion main () und rufen die Funktion Window::setSourceModel() auf, damit die Anwendung es verwendet:

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);
}

Die Funktion QSortFilterProxyModel::setSourceModel() veranlasst das Proxy-Modell, die Daten im angegebenen Modell zu verarbeiten, in diesem Fall unser Mail-Modell. Die Funktion setModel(), die das View-Widget von der Klasse QAbstractItemModel erbt, legt das Modell fest, das in der Ansicht dargestellt werden soll. Beachten Sie, dass die letztgenannte Funktion auch ein neues Auswahlmodell erstellt und einstellt.

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);
}

Die Funktion textFilterChanged() wird immer dann aufgerufen, wenn der Benutzer das Filtermuster oder die Groß-/Kleinschreibung ändert.

Zunächst wird die bevorzugte Syntax abgerufen (das Enum FilterWidget::PatternSyntax wird verwendet, um die Bedeutung des gegebenen Musters zu interpretieren), dann wird die bevorzugte Groß- und Kleinschreibung bestimmt. Auf der Grundlage dieser Präferenzen und des aktuellen Filtermusters wird die Eigenschaft filterRegularExpression des Proxy-Modells festgelegt. Die Eigenschaft filterRegularExpression enthält den regulären Ausdruck, der zum Filtern des Inhalts des Quellmodells verwendet wird. Beachten Sie, dass der Aufruf der Funktion setFilterRegularExpression() von QSortFilterProxyModel auch das Modell aktualisiert.

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

Die Funktion dateFilterChanged() wird immer dann aufgerufen, wenn der Benutzer den Bereich der gültigen Daten ändert. Wir rufen die neuen Daten von der Benutzeroberfläche ab und rufen die entsprechenden Funktionen auf (die von unserem benutzerdefinierten Proxymodell bereitgestellt werden), um das minimale und maximale Datum des Proxymodells festzulegen. Wie wir oben erklärt haben, wird durch den Aufruf dieser Funktionen auch das Modell aktualisiert.

Die Funktion Main()

In diesem Beispiel haben wir die Anwendung vom Quellmodell getrennt, indem wir das Modell in der Funktion main () erstellt haben. Zuerst erstellen wir die Anwendung, dann das Quellmodell:

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

Wir erstellen eine Klasse MailModel, die von QRangeModel erbt und QAbstractItemView::headerData() neu implementiert, um die richtigen Abschnittsüberschriften zurückzugeben. Sie wird aus einem Array von struct MailHeader gefüllt. MailHeader wird als Q_GADGET mit Eigenschaften deklariert, die von QRangeModel verwendet werden:

struct MailHeader
{
private:
    Q_GADGET
    Q_PROPERTY(QString subject MEMBER subject)
    Q_PROPERTY(QString sender MEMBER sender)
    Q_PROPERTY(QDateTime date MEMBER date)

public:
    QString subject;
    QString sender;
    QDateTime date;
};
class MailModel : public QRangeModel
{
public:
    explicit MailModel(QObject *parent = nullptr) : QRangeModel(mails, parent) {}

    QVariant headerData(int section, Qt::Orientation orientation,
                        int role = Qt::DisplayRole) const override
    {
        if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
            switch (section) {
            case 0:
                return Window::tr("Subject");
            case 1:
                return Window::tr("Sender");
            case 2:
                return Window::tr("Date");
            default:
                break;
            }
        }
        return QRangeModel::headerData(section, orientation, role);
    }

private:
    static const std::array<MailHeader, 10> mails;
};

const std::array<MailHeader, 10> MailModel::mails = {
    MailHeader{ u"RE: Sports"_s, u"Petra Schmidt <petras@nospam.com>"_s,
                QDateTime(QDate(2007, 01, 05), QTime(12, 01)) },
    MailHeader{ u"AW: Sports"_s, u"Rolf Newschweinstein <rolfn@nospam.com>"_s,
                QDateTime(QDate(2007, 01, 05), QTime(12, 00)) },
    MailHeader{ u"Sports"_s, u"Linda Smith <linda.smith@nospam.com>"_s,
                QDateTime(QDate(2007, 01, 05), QTime(11, 33)) },
    MailHeader{ u"Re: Accounts"_s, u"Andy <andy@nospam.com>"_s,
                QDateTime(QDate(2007, 01, 03), QTime(14, 26)) },
    MailHeader{ u"Re: Accounts"_s, u"Joe Bloggs <joe@bloggs.com>"_s,
                QDateTime(QDate(2007, 01, 03), QTime(14, 18)) },
    MailHeader{ u"Re: Expenses"_s, u"Andy <andy@nospam.com>"_s,
                QDateTime(QDate(2007, 01, 02), QTime(16, 05)) },
    MailHeader{ u"Expenses"_s, u"Joe Bloggs <joe@bloggs.com>"_s,
                QDateTime(QDate(2006, 12, 25), QTime(11, 39)) },
    MailHeader{ u"Accounts"_s, u"pascale@nospam.com"_s,
                QDateTime(QDate(2006, 12, 31), QTime(12, 50)) },
    MailHeader{ u"Radically new concept"_s, u"Grace K. <grace@software-inc.com>"_s,
                QDateTime(QDate(2006, 12, 22), QTime(9, 44)) },
    MailHeader{ u"Happy New Year!"_s, u"Grace K. <grace@software-inc.com>"_s,
                QDateTime(QDate(2006, 12, 31), QTime(17, 03)) }
};

Beispielprojekt @ code.qt.io

© 2026 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.