Sur cette page

Exemple de modèle de tri/filtre personnalisé

L'exemple de modèle de tri/filtre personnalisé illustre comment sous-classer QSortFilterProxyModel pour effectuer des tris et des filtrages avancés.

Capture d'écran de l'exemple de modèle de tri/filtre personnalisé

La classe QSortFilterProxyModel prend en charge le tri et le filtrage des 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 du modèle qu'il fournit à de nouveaux index, correspondant à différents emplacements, que les vues peuvent utiliser. Cette approche permet de restructurer un modèle source donné en ce qui concerne les vues, sans nécessiter de transformations des données sous-jacentes et sans dupliquer les données en mémoire.

L'exemple de modèle de tri/filtre personnalisé se compose de deux classes :

  • La classe MySortFilterProxyModel fournit un modèle de proxy personnalisé.
  • La classe Window fournit la fenêtre principale de l'application, qui utilise 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 mis en œuvre, 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.

Puisque QAbstractProxyModel et ses sous-classes sont dérivées de QAbstractItemModel, la plupart des conseils concernant la sous-classification des modèles normaux s'appliquent également aux modèles mandataires.

D'autre part, il convient de noter que de nombreuses implémentations de fonctions par défaut de QSortFilterProxyModel sont écrites de manière à appeler les fonctions équivalentes dans le modèle source concerné. Ce mécanisme simple de procuration peut nécessiter d'être surchargé pour les modèles sources ayant un comportement plus complexe. Dans cet exemple, nous dérivons de la classe QSortFilterProxyModel pour nous assurer que notre filtre peut reconnaître une plage de dates valide et pour contrôler le comportement du 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 voulons 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 pour n'accepter que les lignes contenant des dates valides, et la fonction QSortFilterProxyModel::lessThan() pour pouvoir trier les expéditeurs en fonction de leur adresse électronique. Enfin, nous implémentons une fonction de commodité dateInRange() que nous utiliserons pour déterminer si une date est valide.

Implémentation de la classe MySortFilterProxyModel

Le constructeur de MySortFilterProxyModel est trivial, 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. Jetons d'abord un coup d'œil à notre fonction personnalisée lessThan().

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

Nous voulons trier les expéditeurs en fonction de leur adresse électronique. La fonction lessThan() est utilisée comme opérateur < lors du tri. L'implémentation par défaut gère une collection de types, dont QDateTime et String, mais pour pouvoir trier les expéditeurs en fonction de leur adresse électronique, nous devons d'abord identifier l'adresse dans la chaîne donnée :

    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 pour les 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 vrai. Le résultat de la correspondance peut être récupéré à l'aide de la fonction captured() de QRegularExpressionMatch. L'ensemble de la correspondance a l'indice 0 et les sous-expressions parenthésées ont des indices commençant à 1 (à l'exclusion des parenthèses non capturantes).

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, est censée renvoyer un résultat positif si la ligne donnée doit être incluse dans le modèle. Dans notre exemple, une ligne est acceptée si le sujet 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.

Pour 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 minimales et maximales :

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 d'obtention, filterMinimumDate() et filterMaximumDate(), sont triviales et implémentées comme des fonctions en ligne dans le fichier d'en-tête.

Ceci complète notre modèle de proxy personnalisé. Voyons comment nous pouvons 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éagir lorsque l'utilisateur modifie le motif de filtrage, la sensibilité à la casse ou l'une des dates. En outre, nous implémentons une fonction de commodité publique setSourceModel() pour établir la relation entre le modèle et la vue.

Mise en œuvre de la classe Window

Dans cet exemple, nous avons choisi de créer et de définir 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 supposons qu'un modèle source existe déjà et nous 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 dynamiquement trié et filtré. En attribuant la valeur "true" à cette propriété, nous nous assurons 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 des vues du modèle source et du modèle proxy. La vue de la source est assez simple :

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

La classe QTreeView fournit une implémentation modèle/vue par défaut d'une vue arborescente. 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 modèle/vue par défaut d'une vue arborescente ; notre vue implémente une représentation arborescente des éléments dans le modèle source de l'application. Nous ajoutons notre widget de vue à une disposition que nous installons sur un cadre correspondant.

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. Tout ce que nous avons à faire est d'activer le tri pour notre vue proxy en définissant la propriété QTreeView::sortingEnabled (qui est fausse par défaut). Ensuite, nous ajoutons tous les widgets de filtrage et la vue proxy à un layout que nous installons sur un cadre correspondant.

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 boîtes de groupe dans une autre disposition que nous installons sur notre widget d'application principal, nous personnalisons la fenêtre d'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 indiqué 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() permet au modèle proxy de traiter les données du modèle donné, en l'occurrence le modèle de courrier électronique. La fonction setModel(), que le widget de vue hérite de la classe QAbstractItemModel, définit le modèle que la vue doit présenter. 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 motif de filtrage 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 motif donné), puis nous déterminons la sensibilité à la casse préférée. En fonction de ces préférences et du motif 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 à 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 et appelons les fonctions correspondantes (fournies par notre modèle proxy personnalisé) pour définir les dates minimales et maximales 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 QCoreApplication::exec();
}

Nous créons une classe MailModel, qui hérite de QRangeModel et réimplémente QAbstractItemView::headerData() pour renvoyer les bons titres de section. Elle est remplie à partir d'un tableau de struct MailHeader. MailHeader est déclarée comme étant une Q_GADGET avec des propriétés à utiliser 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.