Sur cette page

Exemple de modèle de tri/filtrage personnalisé

L'exemple de modèle de tri et de filtrage personnalisés illustre comment créer une sous-classe de ` QSortFilterProxyModel ` pour effectuer des opérations avancées de tri et de filtrage.

Capture d'écran de l'exemple de modèle de tri et de filtrage personnalisés

La classe ` QSortFilterProxyModel ` permet de trier et de filtrer les données transmises entre un autre modèle et une vue.

Le modèle transforme la structure d’un modèle source en mappant les index qu’il fournit à de nouveaux index, correspondant à différents emplacements, destinés à être utilisés par les vues. Cette approche permet de restructurer un modèle source donné du point de vue des vues, sans nécessiter de transformations sur les données sous-jacentes et sans dupliquer les données en mémoire.

L’exemple « Custom Sort/Filter Model » se compose de deux classes :

  • La classe ` MySortFilterProxyModel ` fournit un modèle proxy personnalisé.
  • La classe ` Window ` fournit la fenêtre principale de l’application, en utilisant le modèle proxy personnalisé pour trier et filtrer un modèle d’élément standard.

Nous allons d’abord examiner la classe ` MySortFilterProxyModel ` pour voir comment le modèle proxy personnalisé est implémenté, puis nous examinerons la classe ` Window ` pour voir comment le modèle est utilisé. Enfin, nous jetterons un coup d’œil rapide à la fonction ` main() `.

Définition de la classe MySortFilterProxyModel

La classe ` MySortFilterProxyModel ` hérite de la classe ` QSortFilterProxyModel `.

Étant donné que QAbstractProxyModel et ses sous-classes sont dérivées de QAbstractItemModel, la plupart des conseils concernant la création de sous-classes de modèles normaux s’appliquent également aux modèles proxy.

En revanche, il convient de noter que bon nombre des implémentations par défaut des fonctions de la classe ` QSortFilterProxyModel` sont écrites de manière à appeler les fonctions équivalentes dans le modèle source correspondant. Ce mécanisme de proxy simple peut devoir être redéfini pour les modèles sources présentant un comportement plus complexe. Dans cet exemple, nous dérivons de la classe ` QSortFilterProxyModel ` afin de garantir que notre filtre puisse reconnaître une plage de dates valide et de contrôler le comportement de tri.

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

Nous souhaitons pouvoir filtrer nos données en spécifiant une période donnée. C’est pourquoi nous implémentons les fonctions personnalisées ` setFilterMinimumDate() ` et ` setFilterMaximumDate() `, ainsi que les fonctions correspondantes ` filterMinimumDate() ` et ` filterMaximumDate() `. Nous réimplémentons la fonction filterAcceptsRow() de QSortFilterProxyModel afin qu’elle n’accepte que les lignes comportant des dates valides, et la fonction QSortFilterProxyModel::lessThan() pour pouvoir trier les expéditeurs en fonction de leurs adresses e-mail. Enfin, nous implémentons une fonction pratique dateInRange() que nous utiliserons pour déterminer si une date est valide.

Implémentation de la classe MySortFilterProxyModel

Le constructeur de ` MySortFilterProxyModel ` est simple : il transmet le paramètre `parent` au constructeur de la classe de base :

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

Les parties les plus intéressantes de l’implémentation de MySortFilterProxyModel sont les réimplémentations des fonctions filterAcceptsRow() et lessThan() de QSortFilterProxyModel. Examinons d’abord notre fonction lessThan() personnalisée.

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

Nous souhaitons trier les expéditeurs en fonction de leurs adresses e-mail. La fonction lessThan() est utilisée comme opérateur < lors du tri. L’implémentation par défaut gère une collection de types, notamment QDateTime et String, mais pour pouvoir trier les expéditeurs en fonction de leurs adresses e-mail, nous devons d’abord identifier l’adresse dans la chaîne fournie :

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

Nous utilisons ` QRegularExpression ` pour définir un modèle correspondant aux adresses que nous recherchons. La fonction ` match()` renvoie un objet ` QRegularExpressionMatch ` qui contient le résultat de la correspondance. S’il y a une correspondance, ` hasMatch()` renvoie `true`. Le résultat de la correspondance peut être récupéré à l’aide de la fonction captured() de QRegularExpressionMatch. La correspondance complète porte l’index 0 et les sous-expressions entre parenthèses ont des indices commençant à 1 (à l’exclusion des parenthèses sans capture).

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

La fonction filterAcceptsRow(), quant à elle, doit renvoyer « true » si la ligne donnée doit être incluse dans le modèle. Dans notre exemple, une ligne est acceptée si l’objet ou l’expéditeur contient l’expression régulière donnée, et si la date est valide.

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

Nous utilisons notre fonction personnalisée ` dateInRange() ` pour déterminer si une date est valide.

Afin de pouvoir filtrer nos données en spécifiant une période donnée, nous implémentons également des fonctions permettant d’obtenir et de définir les dates minimale et maximale :

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

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

Les fonctions de récupération, ` filterMinimumDate() ` et ` filterMaximumDate()`, sont simples et implémentées en tant que fonctions inline dans le fichier d’en-tête.

Notre modèle de proxy personnalisé est ainsi complet. Voyons comment l’utiliser dans une application.

Définition de la classe de fenêtre

La classe ` CustomFilter ` hérite de ` QWidget` et fournit la fenêtre principale de l’application de cet exemple :

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

Nous implémentons deux slots privés, ` textFilterChanged() ` et ` dateFilterChanged()`, pour répondre aux modifications apportées par l’utilisateur au motif de filtrage, à la distinction entre majuscules et minuscules ou à l’une des dates. De plus, nous implémentons une fonction de commodité publique ` setSourceModel() ` pour établir la relation modèle/vue.

Implémentation de la classe de fenêtre

Dans cet exemple, nous avons choisi de créer et de configurer le modèle source dans la fonction main () (sur laquelle nous reviendrons plus tard). Ainsi, lors de la construction de la fenêtre principale de l’application, nous partons du principe qu’un modèle source existe déjà et commençons par créer une instance de notre modèle proxy personnalisé :

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

Nous définissons la propriété ` dynamicSortFilter `, qui indique si le modèle proxy est trié et filtré dynamiquement. En définissant cette propriété sur `true`, nous garantissons que le modèle est trié et filtré chaque fois que le contenu du modèle source change.

La fenêtre principale de l’application affiche à la fois le modèle source et le modèle proxy. La vue source est assez simple :

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

La classe ` QTreeView ` fournit une implémentation par défaut du modèle/vue sous forme d’arborescence. Notre vue implémente une représentation arborescente des éléments du modèle source de l’application.

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

La classe ` QTreeView ` fournit une implémentation par défaut du modèle/vue d’une arborescence ; notre vue implémente une représentation arborescente des éléments du modèle source de l’application. Nous ajoutons notre widget de vue à une mise en page que nous installons dans une zone de groupe correspondante.

La vue du modèle proxy, quant à elle, contient plusieurs widgets contrôlant les différents aspects de la transformation de la structure de données du modèle source :

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

Notez que chaque fois que l’utilisateur modifie l’une des options de filtrage, nous devons réappliquer explicitement le filtre. Pour ce faire, nous connectons les différents éditeurs à des fonctions qui mettent à jour le modèle proxy.

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

Le tri sera géré par la vue. Il suffit d’activer le tri pour notre vue proxy en définissant la propriété ` QTreeView::sortingEnabled ` (qui est à `false` par défaut). Nous ajoutons ensuite tous les widgets de filtrage et la vue proxy à une mise en page que nous installons sur une zone de groupe correspondante.

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

Enfin, après avoir placé nos deux zones de groupe dans une autre mise en page que nous installons sur le widget principal de notre application, nous personnalisons la fenêtre de l’application.

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

Comme mentionné ci-dessus, nous créons le modèle source dans la fonction ` main ()`, en appelant la fonction ` Window::setSourceModel() ` pour que l’application l’utilise :

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

La fonction ` QSortFilterProxyModel::setSourceModel()` fait en sorte que le modèle proxy traite les données du modèle donné, en l’occurrence notre modèle de messagerie. La fonction ` setModel()`, dont le widget de vue hérite de la classe ` QAbstractItemModel `, définit le modèle que la vue doit afficher. Notez que cette dernière fonction créera et définira également un nouveau modèle de sélection.

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

La fonction ` textFilterChanged() ` est appelée chaque fois que l'utilisateur modifie le modèle de filtre ou la sensibilité à la casse.

Nous récupérons d’abord la syntaxe préférée (l’énumération FilterWidget::PatternSyntax est utilisée pour interpréter la signification du modèle donné), puis nous déterminons la sensibilité à la casse préférée. En fonction de ces préférences et du modèle de filtrage actuel, nous définissons la propriété filterRegularExpression du modèle proxy. La propriété filterRegularExpression contient l’expression régulière utilisée pour filtrer le contenu du modèle source. Notez que l’appel de la fonction ` setFilterRegularExpression()` de ` QSortFilterProxyModel` met également à jour le modèle.

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

La fonction dateFilterChanged() est appelée chaque fois que l’utilisateur modifie la plage de dates valides. Nous récupérons les nouvelles dates depuis l’interface utilisateur, puis appelons les fonctions correspondantes (fournies par notre modèle proxy personnalisé) pour définir les dates minimale et maximale du modèle proxy. Comme nous l’avons expliqué plus haut, l’appel de ces fonctions met également à jour le modèle.

La fonction Main()

Dans cet exemple, nous avons séparé l’application du modèle source en créant le modèle dans la fonction main (). Nous créons d’abord l’application, puis le modèle source :

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

Nous créons une classe MailModel, qui hérite de QRangeModel et réimplémente QAbstractItemView::headerData() pour renvoyer les en-têtes de section corrects. Elle est renseignée à partir d’un tableau de structures MailHeader. MailHeader est déclarée comme une Q_GADGET dotée de propriétés destinées à être utilisées par QRangeModel:

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

Exemple de projet @ 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.