Sur cette page

Programmation modèle/vue

Introduction à la programmation Modèle/Vue

Qt contient un ensemble de classes de vues d'éléments qui utilisent une architecture modèle/vue pour gérer la relation entre les données et la manière dont elles sont présentées à l'utilisateur. La séparation des fonctionnalités introduite par cette architecture offre aux développeurs une plus grande flexibilité pour personnaliser la présentation des éléments, et fournit une interface modèle standard permettant d'utiliser un large éventail de sources de données avec les vues d'éléments existantes. Dans ce document, nous présentons brièvement le paradigme modèle/vue, exposons les concepts impliqués et décrivons l'architecture du système de vues d'éléments. Chacun des composants de l'architecture est expliqué, et des exemples sont fournis pour illustrer l'utilisation des classes proposées.

L’architecture modèle-vue

Le modèle-vue-contrôleur (MVC) est un modèle de conception issu de Smalltalk, souvent utilisé lors de la création d’interfaces utilisateur. Dans *Design Patterns*, Gamma et al. écrivent :

Le MVC se compose de trois types d’objets. Le modèle est l’objet application, la vue est sa présentation à l’écran, et le contrôleur définit la manière dont l’interface utilisateur réagit aux entrées de l’utilisateur. Avant le MVC, la conception des interfaces utilisateur avait tendance à regrouper ces objets. Le MVC les dissocie afin d’accroître la flexibilité et la réutilisabilité.

Si les objets Vue et Contrôleur sont combinés, on obtient l’architecture modèle/vue. Celle-ci sépare toujours la manière dont les données sont stockées de la manière dont elles sont présentées à l’utilisateur, mais offre un cadre plus simple reposant sur les mêmes principes. Cette séparation permet d’afficher les mêmes données dans plusieurs vues différentes et d’implémenter de nouveaux types de vues, sans modifier les structures de données sous-jacentes. Pour permettre une gestion flexible des entrées utilisateur, nous introduisons le concept de délégué. L’avantage d’un délégué dans ce cadre est qu’il permet de personnaliser la manière dont les éléments de données sont affichés et modifiés.

Diagramme d'interaction entre le modèle, la vue et le déléguéL’architecture modèle/vue

Le modèle communique avec une source de données, fournissant une interface aux autres composants de l’architecture. La nature de cette communication dépend du type de source de données et de la manière dont le modèle est implémenté.

La vue obtient du modèle des index, qui sont des références à des éléments de données. En fournissant ces index au modèle, la vue peut récupérer des éléments de données à partir de la source de données.

Dans les vues standard, un délégué se charge de l'affichage des éléments de données. Lorsqu'un élément est modifié, le délégué communique directement avec le modèle à l'aide des index du modèle.

En général, les classes modèle/vue peuvent être réparties en trois groupes décrits ci-dessus : les modèles, les vues et les délégués. Chacun de ces composants est défini par des classes abstraites qui fournissent des interfaces communes et, dans certains cas, des implémentations par défaut de fonctionnalités. Les classes abstraites sont destinées à être sous-classées afin de fournir l’ensemble complet des fonctionnalités attendues par les autres composants ; cela permet également d’écrire des composants spécialisés.

Les modèles, les vues et les délégués communiquent entre eux à l’aide de signaux et de slots:

  • Les signaux provenant du modèle informent la vue des modifications apportées aux données contenues dans la source de données.
  • Les signaux provenant de la vue fournissent des informations sur l’interaction de l’utilisateur avec les éléments affichés.
  • Les signaux provenant du délégué sont utilisés pendant l’édition pour indiquer au modèle et à la vue l’état de l’éditeur.

Modèles

Tous les modèles d’éléments sont basés sur la classe ` QAbstractItemModel `. Cette classe définit une interface utilisée par les vues et les délégués pour accéder aux données. Les données elles-mêmes ne doivent pas nécessairement être stockées dans le modèle ; elles peuvent être conservées dans une structure de données ou un référentiel fourni par une classe distincte, un fichier, une base de données ou tout autre composant d’application.

Les concepts de base relatifs aux modèles sont présentés dans la section consacrée aux classes de modèles.

QAbstractItemModel fournit une interface vers les données suffisamment flexible pour prendre en charge les vues qui représentent les données sous forme de tableaux, de listes et d’arborescences. Toutefois, lors de la mise en œuvre de nouveaux modèles pour des structures de données de type liste ou tableau, les classes ` QAbstractListModel ` et ` QAbstractTableModel ` constituent de meilleurs points de départ, car elles fournissent des implémentations par défaut appropriées des fonctions courantes. Chacune de ces classes peut être sous-classée pour fournir des modèles prenant en charge des types spécialisés de listes et de tableaux.

Le processus de création de sous-classes de modèles est abordé dans la section intitulée « Création de nouveaux modèles ».

Qt fournit des modèles prêts à l’emploi pouvant être utilisés pour gérer des éléments de données :

  • QRangeModel adapte un conteneur C++ existant ou toute plage C++ itérable au framework modèle/vue sans créer de sous-classe.
  • QStringListModel est utilisé pour stocker une simple liste d’éléments de type « QString ».
  • QStandardItemModel gère des structures arborescentes plus complexes d’éléments, dont chacun peut contenir des données arbitraires.
  • QFileSystemModel fournit des informations sur les fichiers et les répertoires du système de fichiers local.
  • QSqlQueryModel, ` QSqlTableModel` et ` QSqlRelationalTableModel ` sont utilisés pour accéder à des bases de données à l'aide des conventions du modèle/vue.

Si ces modèles standard ne répondent pas à vos besoins, vous pouvez créer des sous-classes de ` QAbstractItemModel`, ` QAbstractListModel` ou ` QAbstractTableModel ` pour créer vos propres modèles personnalisés. Par ailleurs, si vos données se trouvent déjà dans un conteneur ou une plage C++, ` QRangeModel ` peut souvent les adapter sans avoir à créer de sous-classe.

Vues

Des implémentations complètes sont fournies pour différents types de vues : QListView affiche une liste d’éléments, QTableView affiche les données d’un modèle sous forme de tableau, et QTreeView présente les éléments du modèle sous forme de liste hiérarchique. Chacune de ces classes est basée sur la classe de base abstraite QAbstractItemView. Bien que ces classes soient des implémentations prêtes à l’emploi, elles peuvent également être sous-classées pour fournir des vues personnalisées.

Les vues disponibles sont présentées dans la section consacrée aux classes de vue.

Les délégués

QAbstractItemDelegate est la classe de base abstraite pour les délégués dans le framework modèle/vue. L’implémentation par défaut du délégué est fournie par QStyledItemDelegate, et celle-ci est utilisée comme délégué par défaut par les vues standard de Qt. Cependant, QStyledItemDelegate et QItemDelegate constituent des alternatives indépendantes pour le dessin et la mise à disposition d’éditeurs pour les éléments des vues. La différence entre ces deux classes réside dans le fait que QStyledItemDelegate utilise le style actuel pour dessiner ses éléments. Nous recommandons donc d’utiliser QStyledItemDelegate comme classe de base lors de l’implémentation de délégués personnalisés ou lorsque vous travaillez avec des feuilles de style Qt.

Les délégués sont décrits dans la section consacrée aux classes de délégués.

Tri

Il existe deux façons d’aborder le tri dans l’architecture modèle/vue ; le choix de l’approche dépend de votre modèle sous-jacent.

Si votre modèle est triable, c'est-à-dire s'il réimplémente la fonction QAbstractItemModel::sort(), les classes QTableView et QTreeView fournissent toutes deux une API qui vous permet de trier les données de votre modèle par programmation. De plus, vous pouvez activer le tri interactif (c’est-à-dire permettre aux utilisateurs de trier les données en cliquant sur les en-têtes de la vue), en connectant le signal QHeaderView::sortIndicatorChanged() au slot QTableView::sortByColumn() ou au slot QTreeView::sortByColumn(), respectivement.

Une autre approche, si votre modèle ne dispose pas de l’interface requise ou si vous souhaitez utiliser une vue de liste pour présenter vos données, consiste à utiliser un modèle proxy pour transformer la structure de votre modèle avant de présenter les données dans la vue. Ce sujet est abordé en détail dans la section consacrée aux modèles proxy.

Classes de commodité

Un certain nombre de classes de commodité sont dérivées des classes de vue standard à l’intention des applications qui s’appuient sur les classes de vue d’éléments et de tableaux de Qt. Elles ne sont pas destinées à être sous-classées.

Parmi ces classes, on peut citer notamment QListWidget, QTreeWidget et QTableWidget.

Ces classes sont moins flexibles que les classes de vue et ne peuvent pas être utilisées avec des modèles arbitraires. Nous vous recommandons d'adopter une approche modèle/vue pour la gestion des données dans les vues d'éléments, sauf si vous avez absolument besoin d'un ensemble de classes basées sur les éléments.

Si vous souhaitez tirer parti des fonctionnalités offertes par l'approche modèle/vue tout en continuant à utiliser une interface basée sur les éléments, envisagez d'utiliser des classes de vue, telles que QListView, QTableView et QTreeView avec QStandardItemModel.

Utilisation des modèles et des vues

Les sections suivantes expliquent comment utiliser le modèle « modèle/vue » dans Qt. Chaque section comprend un exemple et est suivie d’une section montrant comment créer de nouveaux composants.

Deux modèles inclus dans Qt

Deux des modèles standard fournis par Qt sont QStandardItemModel et QFileSystemModel. QStandardItemModel est un modèle polyvalent qui peut être utilisé pour représenter diverses structures de données nécessaires aux vues de liste, de tableau et d’arborescence. Ce modèle contient également les éléments de données. QFileSystemModel est un modèle qui gère les informations relatives au contenu d’un répertoire. Par conséquent, il ne contient pas d’éléments de données en soi, mais représente simplement les fichiers et les répertoires du système de fichiers local.

QFileSystemModel fournit un modèle prêt à l’emploi permettant de mener des expériences, et peut être facilement configuré pour exploiter des données existantes. À l’aide de ce modèle, nous pouvons montrer comment configurer un modèle destiné à être utilisé avec des vues prêtes à l’emploi, et explorer comment manipuler les données à l’aide d’index de modèle.

Utilisation de vues avec un modèle existant

Les classes ` QListView ` et ` QTreeView ` sont les vues les plus adaptées à une utilisation avec ` QFileSystemModel`. L’exemple présenté ci-dessous affiche le contenu d’un répertoire dans une vue arborescente, à côté des mêmes informations présentées dans une vue de liste. Les vues partagent la sélection de l’utilisateur, de sorte que les éléments sélectionnés sont mis en surbrillance dans les deux vues.

Affichage arborescent et affichage sous forme de liste pour présenter le même modèle de système de fichiers

Nous configurons un modèle ` QFileSystemModel ` afin qu’il soit prêt à l’emploi, puis nous créons quelques vues pour afficher le contenu d’un répertoire. Cela illustre la manière la plus simple d’utiliser un modèle. La création et l’utilisation du modèle s’effectuent au sein d’une seule fonction ` main() ` :

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    QSplitter *splitter = new QSplitter;

    QFileSystemModel *model = new QFileSystemModel;
    model->setRootPath(QDir::currentPath());

Le modèle est configuré pour utiliser les données d’un système de fichiers donné. L’appel à ` setRootPath()` indique au modèle quel lecteur du système de fichiers doit être exposé aux vues.

Nous créons deux vues afin de pouvoir examiner les éléments contenus dans le modèle de deux manières différentes :

QTreeView *tree = new QTreeView(splitter);
tree->setModel(model);
tree->setRootIndex(model->index(QDir::currentPath()));

QListView *list = new QListView(splitter);
list->setModel(model);
list->setRootIndex(model->index(QDir::currentPath()));

Les vues sont construites de la même manière que les autres widgets. Pour configurer une vue afin qu’elle affiche les éléments du modèle, il suffit d’appeler sa fonction ` setModel()` en lui passant le modèle de répertoire en argument. Nous filtrons les données fournies par le modèle en appelant la fonction ` setRootIndex()` sur chaque vue, en lui transmettant un index de modèle approprié issu du modèle de système de fichiers pour le répertoire courant.

La fonction index() utilisée dans ce cas est propre à QFileSystemModel; nous lui fournissons un répertoire et elle renvoie un index de modèle. Les index de modèle sont abordés dans la section Classes de modèles.

Le reste de la fonction se contente d’afficher les vues dans un widget « splitter » et d’exécuter la boucle d’événements de l’application :

    splitter->setWindowTitle("Two views onto the same file system model");
    splitter->show();
    return app.exec();
}

Dans l’exemple ci-dessus, nous avons omis de mentionner comment gérer la sélection des éléments. Ce sujet est abordé plus en détail dans la section consacrée à la gestion des sélections dans les vues d’éléments.

Classes de modèles

Avant d’examiner comment les sélections sont gérées, il peut être utile de passer en revue les concepts utilisés dans le framework modèle/vue.

Concepts de base

Dans l’architecture modèle/vue, le modèle fournit une interface standard que les vues et les délégués utilisent pour accéder aux données. Sous Qt, cette interface standard est définie par la classe ` QAbstractItemModel `. Quelle que soit la manière dont les éléments de données sont stockés dans une structure de données sous-jacente, toutes les sous-classes de ` QAbstractItemModel ` représentent les données sous la forme d’une structure hiérarchique contenant des tables d’éléments. Les vues utilisent cette convention pour accéder aux éléments de données dans le modèle, mais elles ne sont pas limitées dans la manière dont elles présentent ces informations à l’utilisateur.

Modèle de liste, modèle de tableau et modèle arborescent

Les modèles informent également les vues associées des modifications apportées aux données via le mécanisme des signaux et des slots.

Cette section décrit certains concepts de base qui sont au cœur de la manière dont les éléments de données sont accessibles par d’autres composants via une classe de modèle. Des concepts plus avancés sont abordés dans les sections suivantes.

Index de modèle

Afin de garantir que la représentation des données reste distincte de la manière dont on y accède, le concept d’index de modèle est introduit. Chaque élément d’information pouvant être obtenu via un modèle est représenté par un index de modèle. Les vues et les délégués utilisent ces index pour demander les éléments de données à afficher.

Par conséquent, seul le modèle doit savoir comment obtenir les données, et le type de données gérées par le modèle peut être défini de manière assez générale. Les index de modèle contiennent un pointeur vers le modèle qui les a créés, ce qui évite toute confusion lorsqu’on travaille avec plusieurs modèles.

const QAbstractItemModel *model = index.model();

Les index de modèle fournissent des références temporaires à des éléments d’information et peuvent être utilisés pour récupérer ou modifier des données via le modèle. Étant donné que les modèles peuvent réorganiser leurs structures internes de temps à autre, les index de modèle peuvent devenir invalides et ne doivent pas être stockés. Si une référence à long terme à une information est nécessaire, un index de modèle persistant doit être créé. Celui-ci fournit une référence à l’information que le modèle maintient à jour. Les index de modèle temporaires sont fournis par la classe ` QModelIndex `, tandis que les index de modèle persistants sont fournis par la classe ` QPersistentModelIndex `.

Pour obtenir un index de modèle correspondant à un élément de données, trois propriétés doivent être spécifiées au modèle : un numéro de ligne, un numéro de colonne et l’index de modèle d’un élément parent. Les sections suivantes décrivent et expliquent ces propriétés en détail.

Lignes et colonnes

Dans sa forme la plus élémentaire, un modèle est accessible sous la forme d’un simple tableau dans lequel les éléments sont localisés par leurs numéros de ligne et de colonne. Cela ne signifie pas que les données sous-jacentes sont stockées dans une structure de tableau; l’utilisation des numéros de ligne et de colonne n’est qu’une convention permettant aux composants de communiquer entre eux. Nous pouvons récupérer des informations sur n’importe quel élément donné en spécifiant ses numéros de ligne et de colonne au modèle, et nous recevons alors un index qui représente cet élément :

QModelIndex index = model->index(row, column /*...*/);

Les modèles qui fournissent des interfaces vers des structures de données simples à un seul niveau, telles que des listes et des tableaux, ne nécessitent aucune autre information ; cependant, comme l’indique le code ci-dessus, nous devons fournir davantage d’informations pour obtenir un index de modèle.

Structure du modèle de tableau à l'aide de lignes et de colonnesLignes et colonnes

Le diagramme présente une représentation d’un modèle de tableau de base dans lequel chaque élément est localisé par une paire de numéros de ligne et de colonne. Nous obtenons un index de modèle qui fait référence à un élément de données en transmettant les numéros de ligne et de colonne correspondants au modèle.

QModelIndex indexA = model->index(0, 0, QModelIndex());
QModelIndex indexB = model->index(1, 1, QModelIndex());
QModelIndex indexC = model->index(2, 1, QModelIndex());

Les éléments de niveau supérieur d'un modèle sont toujours référencés en spécifiant QModelIndex() comme élément parent. Ce point est abordé dans la section suivante.

Éléments parents

L’interface de type tableau fournie par les modèles pour les données des éléments est idéale lorsque l’on utilise ces données dans une vue de type tableau ou liste ; le système de numérotation des lignes et des colonnes correspond exactement à la manière dont les vues affichent les éléments. Cependant, les structures telles que les arborescences exigent que le modèle expose une interface plus flexible pour les éléments qu’il contient. Par conséquent, chaque élément peut également être le parent d’un autre tableau d’éléments, de la même manière qu’un élément de niveau supérieur dans une arborescence peut contenir une autre liste d’éléments.

Lorsqu’on demande un index pour un élément de modèle, il faut fournir certaines informations sur le parent de cet élément. En dehors du modèle, la seule façon de faire référence à un élément est d’utiliser un index de modèle ; il faut donc également indiquer l’index du modèle parent :

QModelIndex index = model->index(row, column, parent);
Structure du modèle arborescent comprenant des éléments « parent », « ligne » et « colonne »Pères, lignes et colonnes

Le schéma présente une représentation d’un modèle arborescent dans lequel chaque élément est identifié par un parent, un numéro de ligne et un numéro de colonne.

Les éléments « A » et « C » sont représentés comme des éléments frères de niveau supérieur dans le modèle :

QModelIndex indexA = model->index(0, 0, QModelIndex());
QModelIndex indexC = model->index(2, 1, QModelIndex());

L'élément « A » possède plusieurs enfants. L'index du modèle correspondant à l'élément « B » s'obtient à l'aide du code suivant :

QModelIndex indexB = model->index(1, 0, indexA);

Rôles des éléments

Les éléments d’un modèle peuvent remplir divers rôles pour d’autres composants, ce qui permet de fournir différents types de données en fonction des situations. Par exemple, Qt::DisplayRole est utilisé pour accéder à une chaîne de caractères pouvant être affichée sous forme de texte dans une vue. En général, les éléments contiennent des données pour plusieurs rôles différents, et les rôles standard sont définis par Qt::ItemDataRole.

Nous pouvons demander au modèle les données de l'élément en lui transmettant l'index du modèle correspondant à l'élément et en spécifiant un rôle afin d'obtenir le type de données souhaité :

QVariant value = model->data(index, role);
Les différents rôles dans un modèleRôles des éléments

Le rôle indique au modèle à quel type de données on fait référence. Les vues peuvent afficher les rôles de différentes manières ; il est donc important de fournir les informations appropriées pour chaque rôle.

La section « Création de nouveaux modèles » aborde plus en détail certaines utilisations spécifiques des rôles.

Les utilisations les plus courantes des données d'élément sont couvertes par les rôles standard définis dans Qt::ItemDataRole. En fournissant des données d'élément appropriées pour chaque rôle, les modèles peuvent donner des indications aux vues et aux délégués sur la manière dont les éléments doivent être présentés à l'utilisateur. Les différents types de vues sont libres d'interpréter ou d'ignorer ces informations selon les besoins. Il est également possible de définir des rôles supplémentaires à des fins spécifiques à l'application.

Résumé

  • Les index de modèle fournissent aux vues et aux délégués des informations sur l’emplacement des éléments fournis par les modèles, et ce de manière indépendante de toute structure de données sous-jacente.
  • Les éléments sont identifiés par leurs numéros de ligne et de colonne, ainsi que par l'index du modèle de leurs éléments parents.
  • Les index de modèle sont construits par les modèles à la demande d’autres composants, tels que les vues et les délégués.
  • Si un index de modèle valide est spécifié pour l’élément parent lors d’une requête d’index à l’aide de la fonction ` index()`, l’index renvoyé fait référence à un élément situé sous cet élément parent dans le modèle. L’index obtenu fait référence à un enfant de cet élément.
  • Si un index de modèle non valide est spécifié pour l’élément parent lors d’une demande d’index à l’aide de la méthode ` index()`, l’index renvoyé fait référence à un élément de niveau supérieur dans le modèle.
  • La fonction ` role ` distingue les différents types de données associés à un élément.

Utilisation des index de modèle

Pour montrer comment récupérer des données à partir d’un modèle à l’aide d’index de modèle, nous mettons en place une fonction ` QFileSystemModel ` sans vue et affichons les noms des fichiers et des répertoires dans un widget. Bien que cela ne corresponde pas à une utilisation classique d’un modèle, cela illustre les conventions utilisées par les modèles lors de la gestion des index de modèle.

QFileSystemModel Le chargement est asynchrone afin de minimiser l'utilisation des ressources système. Il faut en tenir compte lors de l'utilisation de ce modèle.

Nous construisons un modèle de système de fichiers de la manière suivante :

auto *model = new QFileSystemModel;

auto onDirectoryLoaded = [model, layout, &window](const QString &directory) {
    QModelIndex parentIndex = model->index(directory);
    const int numRows = model->rowCount(parentIndex);
    for (int row = 0; row < numRows; ++row) {
        QModelIndex index = model->index(row, 0, parentIndex);

        QString text = model->data(index, Qt::DisplayRole).toString();

        // Display the text in a widget.
        auto *label = new QLabel(text, &window);
        layout->addWidget(label);
    }
};

QObject::connect(model, &QFileSystemModel::directoryLoaded, onDirectoryLoaded);
model->setRootPath(QDir::currentPath());

Dans ce cas, nous commençons par configurer un modèle par défaut QFileSystemModel. Nous connectons son signal directoryLoaded(QString) à un lambda, dans lequel nous obtiendrons un index parent pour le répertoire à l’aide d’une implémentation spécifique de la fonction index() fournie par ce modèle.

Dans la fonction lambda, nous déterminons le nombre de lignes du modèle à l’aide de la fonction rowCount().

Par souci de simplicité, nous ne nous intéressons qu’aux éléments de la première colonne du modèle. Nous examinons chaque ligne tour à tour, en obtenant un index de modèle pour le premier élément de chaque ligne, puis nous lisons les données stockées pour cet élément dans le modèle.

for (int row = 0; row < numRows; ++row) {
    QModelIndex index = model->index(row, 0, parentIndex);

Pour obtenir un index de modèle, nous spécifions le numéro de ligne, le numéro de colonne (zéro pour la première colonne) et l’index de modèle approprié pour le parent de tous les éléments souhaités. Le texte stocké dans chaque élément est récupéré à l’aide de la fonction ` data()` du modèle. Nous spécifions l’index du modèle et l’ DisplayRole e pour obtenir les données de l’élément sous forme de chaîne de caractères.

    QString text = model->data(index, Qt::DisplayRole).toString();

}

Enfin, nous définissons le chemin racine de l'QFileSystemModel afin qu’il commence à charger les données et déclenche la fonction lambda.

L’exemple ci-dessus illustre les principes de base utilisés pour récupérer des données à partir d’un modèle :

  • Les dimensions d’un modèle peuvent être obtenues à l’aide des fonctions ` rowCount()` et ` columnCount()`. Ces fonctions nécessitent généralement la spécification d’un index de modèle parent.
  • Les indices de modèle permettent d’accéder aux éléments du modèle. L’index de ligne, de colonne et du modèle parent sont nécessaires pour spécifier l’élément.
  • Pour accéder aux éléments de niveau supérieur d’un modèle, spécifiez un index de modèle nul comme index parent avec QModelIndex().
  • Les éléments contiennent des données pour différents rôles. Pour obtenir les données d’un rôle particulier, l’index du modèle et le rôle doivent tous deux être fournis au modèle.

Pour en savoir plus

De nouveaux modèles peuvent être créés en implémentant l’interface standard fournie par ` QAbstractItemModel`. Dans la section « Création de nouveaux modèles », nous illustrons cette procédure en créant un modèle pratique et prêt à l’emploi destiné à contenir des listes de chaînes de caractères.

Afficher les classes

Concepts

Dans l’architecture modèle/vue, la vue récupère des éléments de données auprès du modèle et les présente à l’utilisateur. La manière dont les données sont présentées n’a pas besoin de ressembler à la représentation des données fournie par le modèle, et peut être complètement différente de la structure de données sous-jacente utilisée pour stocker les éléments de données.

La séparation entre le contenu et la présentation est assurée par l’utilisation d’une interface de modèle standard fournie par QAbstractItemModel, d’une interface de vue standard fournie par QAbstractItemView, ainsi que par l’utilisation d’index de modèle qui représentent les éléments de données de manière générale. Les vues gèrent généralement la mise en page globale des données issues des modèles. Elles peuvent afficher elles-mêmes les éléments de données individuels ou utiliser des délégués pour gérer à la fois l’affichage et les fonctionnalités d’édition.

Outre la présentation des données, les vues gèrent la navigation entre les éléments, ainsi que certains aspects de la sélection des éléments. Elles implémentent également des fonctionnalités de base de l’interface utilisateur, telles que les menus contextuels et le glisser-déposer. Une vue peut fournir des fonctionnalités d’édition par défaut pour les éléments, ou fonctionner avec un délégué pour fournir un éditeur personnalisé.

Une vue peut être créée sans modèle, mais un modèle doit être fourni avant qu’elle ne puisse afficher des informations utiles. Les vues gardent en mémoire les éléments que l’utilisateur a sélectionnés à l’aide de sélections qui peuvent être gérées séparément pour chaque vue ou partagées entre plusieurs vues.

Certaines vues, telles que QTableView et QTreeView, affichent des en-têtes en plus des éléments. Celles-ci sont également implémentées par une classe de vue, QHeaderView. Les en-têtes accèdent généralement au même modèle que la vue qui les contient. Elles récupèrent les données du modèle à l’aide de la fonction QAbstractItemModel::headerData(), et affichent généralement les informations d’en-tête sous la forme d’une étiquette. De nouveaux en-têtes peuvent être créés en sous-classant la classe QHeaderView afin de fournir des libellés plus spécialisés aux vues.

Utilisation d’une vue existante

Qt fournit trois classes de vues prêtes à l’emploi qui présentent les données issues des modèles sous des formes familières à la plupart des utilisateurs. QListView permet d’afficher les éléments d’un modèle sous forme de simple liste ou de vue d’icônes classique. QTreeView affiche les éléments d’un modèle sous forme de hiérarchie de listes, ce qui permet de représenter de manière compacte des structures profondément imbriquées. QTableView présente les éléments d’un modèle sous forme de tableau, à l’instar de la mise en page d’un tableur.

Affichage sous forme de liste, d'arborescence et de tableau

Le comportement par défaut des vues standard présentées ci-dessus devrait suffire pour la plupart des applications. Elles offrent des fonctionnalités d'édition de base et peuvent être personnalisées pour répondre aux besoins d'interfaces utilisateur plus spécialisées.

Utilisation d’un modèle

Nous prenons le modèle de liste de chaînes de caractères que nous avons créé comme exemple, nous le configurons avec quelques données, puis nous construisons une vue pour afficher le contenu du modèle. Tout cela peut être réalisé au sein d’une seule fonction :

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

// Unindented for quoting purposes:
QStringList numbers;
numbers << "One" << "Two" << "Three" << "Four" << "Five";

QAbstractItemModel *model = new StringListModel(numbers);

Notez que le modèle « StringListModel » est déclaré comme un ` QAbstractItemModel`. Cela nous permet d’utiliser l’interface abstraite du modèle et garantit que le code continuera de fonctionner, même si nous remplaçons le modèle de liste de chaînes par un autre modèle.

La vue de liste fournie par ` QListView ` est suffisante pour présenter les éléments du modèle de liste de chaînes. Nous construisons la vue et configurons le modèle à l’aide des lignes de code suivantes :

QListView *view = new QListView;
view->setModel(model);

La vue s’affiche de manière classique :

    view->show();
    return app.exec();
}

La vue affiche le contenu d’un modèle, en accédant aux données via l’interface de ce dernier. Lorsque l’utilisateur tente de modifier un élément, la vue utilise un délégué par défaut pour fournir un widget d’édition.

Liste de textes utilisant un modèle de liste de chaînes de caractères

L’image ci-dessus montre comment une vue « QListView » représente les données du modèle de liste de chaînes. Le modèle étant modifiable, la vue permet automatiquement de modifier chaque élément de la liste à l’aide du délégué par défaut.

Utilisation de plusieurs vues d'un modèle

Pour fournir plusieurs vues sur un même modèle, il suffit de définir ce même modèle pour chaque vue. Dans le code suivant, nous créons deux vues de type « table », chacune utilisant le même modèle de table simple que nous avons créé pour cet exemple :

QTableView *firstTableView = new QTableView;
QTableView *secondTableView = new QTableView;

firstTableView->setModel(model);
secondTableView->setModel(model);

L’utilisation de signaux et de slots dans l’architecture modèle/vue permet de propager les modifications apportées au modèle à toutes les vues associées, garantissant ainsi un accès constant aux mêmes données, quelle que soit la vue utilisée.

Deux vues de table partagent un modèle, mais ne partagent pas le modèle de sélection.

L’image ci-dessus montre deux vues différentes d’un même modèle, chacune contenant un certain nombre d’éléments sélectionnés. Bien que les données du modèle s’affichent de manière cohérente d’une vue à l’autre, chaque vue conserve son propre modèle de sélection interne. Cela peut s’avérer utile dans certaines situations, mais pour de nombreuses applications, un modèle de sélection partagé est préférable.

Gestion des sélections d’éléments

Le mécanisme de gestion des sélections d’éléments au sein des vues est fourni par la classe ` QItemSelectionModel `. Toutes les vues standard construisent leurs propres modèles de sélection par défaut et interagissent avec eux de manière classique. Le modèle de sélection utilisé par une vue peut être obtenu via la fonction ` selectionModel()` et un modèle de sélection de remplacement peut être spécifié avec ` setSelectionModel()`. La possibilité de contrôler le modèle de sélection utilisé par une vue est utile lorsque l’on souhaite fournir plusieurs vues cohérentes sur les mêmes données de modèle.

En règle générale, à moins que vous ne créiez une sous-classe d’un modèle ou d’une vue, vous n’avez pas besoin de manipuler directement le contenu des sélections. Toutefois, l’interface du modèle de sélection est accessible si nécessaire ; ce sujet est abordé dans la section « Gestion des sélections dans les vues d’éléments ».

Partage des sélections entre les vues

Bien qu’il soit pratique que les classes de vue fournissent leurs propres modèles de sélection par défaut, lorsque l’on utilise plusieurs vues sur un même modèle, il est souvent souhaitable que les données du modèle et la sélection de l’utilisateur s’affichent de manière cohérente dans toutes les vues. Comme les classes de vue permettent de remplacer leurs modèles de sélection internes, nous pouvons obtenir une sélection unifiée entre les vues grâce à la ligne suivante :

secondTableView->setSelectionModel(firstTableView->selectionModel());

La deuxième vue hérite du modèle de sélection de la première vue. Les deux vues fonctionnent désormais avec le même modèle de sélection, ce qui permet de synchroniser à la fois les données et les éléments sélectionnés.

Deux vues de table avec le même modèle de sélection

Dans l’exemple ci-dessus, deux vues de même type ont été utilisées pour afficher les données d’un même modèle. Cependant, si deux types de vues différents étaient utilisés, les éléments sélectionnés pourraient être représentés de manière très différente dans chaque vue ; par exemple, une sélection contiguë dans une vue de type tableau peut être représentée sous la forme d’un ensemble fragmenté d’éléments mis en surbrillance dans une vue arborescente.

Classes de délégués

Concepts

Contrairement au modèle MVC (Modèle-Vue-Contrôleur), l’architecture modèle/vue ne comprend pas de composant entièrement distinct chargé de gérer l’interaction avec l’utilisateur. En général, la vue est chargée de présenter les données du modèle à l’utilisateur et de traiter les entrées de celui-ci. Afin d’offrir une certaine souplesse dans la manière dont ces entrées sont obtenues, l’interaction est assurée par des délégués. Ces composants fournissent des capacités de saisie et sont également chargés du rendu des éléments individuels dans certaines vues. L’interface standard permettant de contrôler les délégués est définie dans la classe ` QAbstractItemDelegate `.

Les délégués sont censés pouvoir afficher eux-mêmes leur contenu en implémentant les fonctions ` paint()` et ` sizeHint()`. Cependant, les délégués simples basés sur des widgets peuvent créer une sous-classe de ` QStyledItemDelegate ` au lieu de ` QAbstractItemDelegate`, et tirer parti des implémentations par défaut de ces fonctions.

Les éditeurs pour les délégués peuvent être implémentés soit en utilisant des widgets pour gérer le processus d’édition, soit en gérant directement les événements. La première approche est abordée plus loin dans cette section.

Utilisation d’un délégué existant

Les vues standard fournies avec Qt utilisent des instances de ` QStyledItemDelegate ` pour offrir des fonctionnalités d’édition. Cette implémentation par défaut de l’interface de délégué affiche les éléments selon le style habituel de chacune des vues standard : ` QListView`, ` QTableView` et ` QTreeView`.

Tous les rôles standard sont gérés par le délégué par défaut utilisé par les vues standard. La manière dont ceux-ci sont interprétés est décrite dans la documentation d’ QStyledItemDelegate.

Le délégué utilisé par une vue est renvoyé par la fonction itemDelegate(). La fonction setItemDelegate() vous permet d'installer un délégué personnalisé pour une vue standard ; il est nécessaire d'utiliser cette fonction lors de la configuration du délégué pour une vue personnalisée.

Un délégué simple

Le délégué implémenté ici utilise une fonction ` QSpinBox ` pour fournir des fonctionnalités d’édition, et est principalement destiné à être utilisé avec des modèles affichant des entiers. Bien que nous ayons configuré un modèle de table personnalisé basé sur des entiers à cette fin, nous aurions tout aussi bien pu utiliser ` QStandardItemModel `, puisque le délégué personnalisé contrôle la saisie des données. Nous créons une vue de table pour afficher le contenu du modèle, et celle-ci utilisera le délégué personnalisé pour l’édition.

Délégué de zone de sélection personnalisée pour la modification

Nous créons une sous-classe du délégué à partir de QStyledItemDelegate, car nous ne souhaitons pas écrire de fonctions d’affichage personnalisées. Cependant, nous devons tout de même fournir des fonctions pour gérer le widget d’édition :

class SpinBoxDelegate : public QStyledItemDelegate
{
    Q_OBJECT

public:
    SpinBoxDelegate(QObject *parent = nullptr);

    QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
                          const QModelIndex &index) const override;

    void setEditorData(QWidget *editor, const QModelIndex &index) const override;
    void setModelData(QWidget *editor, QAbstractItemModel *model,
                      const QModelIndex &index) const override;

    void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option,
                              const QModelIndex &index) const override;
};

SpinBoxDelegate::SpinBoxDelegate(QObject *parent)
    : QStyledItemDelegate(parent)
{
}

Notez qu’aucun widget d’édition n’est configuré lors de la construction du délégué. Nous ne créons un widget d’édition que lorsque cela est nécessaire.

Fournir un éditeur

Dans cet exemple, lorsque la vue de table doit fournir un éditeur, elle demande au délégué de fournir un widget d’édition adapté à l’élément en cours de modification. La fonction ` createEditor()` reçoit tous les éléments dont le délégué a besoin pour pouvoir configurer un widget approprié :

QWidget *SpinBoxDelegate::createEditor(QWidget *parent,
                                       const QStyleOptionViewItem &/* option */,
                                       const QModelIndex &/* index */) const
{
    QSpinBox *editor = new QSpinBox(parent);
    editor->setFrame(false);
    editor->setMinimum(0);
    editor->setMaximum(100);

    return editor;
}

Notez que nous n’avons pas besoin de conserver un pointeur vers le widget d’édition, car la vue se charge de le détruire lorsqu’il n’est plus nécessaire.

Nous installons le filtre d’événements par défaut du délégué sur l’éditeur afin de garantir qu’il propose les raccourcis d’édition standard auxquels s’attendent les utilisateurs. Des raccourcis supplémentaires peuvent être ajoutés à l’éditeur pour permettre un comportement plus sophistiqué ; ceux-ci sont abordés dans la section consacrée aux conseils d’édition.

La vue s’assure que les données et la géométrie de l’éditeur sont correctement définies en appelant des fonctions que nous définirons ultérieurement à ces fins. Nous pouvons créer différents éditeurs en fonction de l’index du modèle fourni par la vue. Par exemple, si nous disposons d’une colonne d’entiers et d’une colonne de chaînes de caractères, nous pourrions renvoyer soit un ` QSpinBox `, soit un ` QLineEdit`, selon la colonne en cours d’édition.

Le délégué doit fournir une fonction permettant de copier les données du modèle dans l’éditeur. Dans cet exemple, nous lisons les données stockées dans l’ display role et définissons la valeur de la zone de sélection en conséquence.

void SpinBoxDelegate::setEditorData(QWidget *editor,
                                    const QModelIndex &index) const
{
    int value = index.data(Qt::EditRole).toInt();

    QSpinBox *spinBox = static_cast<QSpinBox*>(editor);
    spinBox->setValue(value);
}

Dans cet exemple, nous savons que le widget d'édition est une zone de saisie à défilement, mais nous aurions pu fournir différents widgets d'édition pour différents types de données du modèle ; dans ce cas, il faudrait convertir le widget au type approprié avant d'accéder à ses fonctions membres.

Soumission des données au modèle

Lorsque l’utilisateur a fini de modifier la valeur dans la zone de saisie à défilement, la vue demande au délégué d’enregistrer la valeur modifiée dans le modèle en appelant la fonction ` setModelData()`.

void SpinBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
                                   const QModelIndex &index) const
{
    QSpinBox *spinBox = static_cast<QSpinBox*>(editor);
    spinBox->interpretText();
    int value = spinBox->value();

    model->setData(index, value, Qt::EditRole);
}

Étant donné que la vue gère les widgets de l'éditeur pour le délégué, il suffit de mettre à jour le modèle avec le contenu fourni par l'éditeur. Dans ce cas précis, nous nous assurons que la zone de sélection est à jour, puis nous mettons à jour le modèle avec la valeur qu'elle contient, en utilisant l'index spécifié.

La classe standard ` QStyledItemDelegate ` informe la vue lorsque l’édition est terminée en émettant le signal ` closeEditor()`. La vue s’assure que le widget d’édition est fermé et détruit. Dans cet exemple, nous ne fournissons que des fonctionnalités d’édition simples ; nous n’avons donc jamais besoin d’émettre ce signal.

Toutes les opérations sur les données sont effectuées via l’interface fournie par ` QAbstractItemModel`. Cela rend le délégué pratiquement indépendant du type de données qu’il manipule, mais certaines hypothèses doivent être posées pour pouvoir utiliser certains types de widgets d’édition. Dans cet exemple, nous avons supposé que le modèle contient toujours des valeurs entières, mais nous pouvons tout de même utiliser ce délégué avec différents types de modèles, car ` QVariant ` fournit des valeurs par défaut raisonnables pour les données inattendues.

Mise à jour de la géométrie de l’éditeur

C’est au délégué qu’il incombe de gérer la géométrie de l’éditeur. La géométrie doit être définie lors de la création de l’éditeur, ainsi que lorsque la taille ou la position de l’élément dans la vue est modifiée. Heureusement, la vue fournit toutes les informations géométriques nécessaires au sein d’un objet ` view option `.

void SpinBoxDelegate::updateEditorGeometry(QWidget *editor,
                                           const QStyleOptionViewItem &option,
                                           const QModelIndex &/* index */) const
{
    editor->setGeometry(option.rect);
}

Dans ce cas précis, nous utilisons simplement les informations géométriques fournies par l’option de vue dans le rectangle de l’élément. Un délégué qui affiche des éléments comportant plusieurs composants n’utiliserait pas directement le rectangle de l’élément. Il positionnerait l’éditeur par rapport aux autres composants de l’élément.

Conseils d’édition

Une fois l’édition terminée, les délégués doivent fournir des indications aux autres composants concernant le résultat du processus d’édition, ainsi que des indications qui faciliteront toute opération d’édition ultérieure. Pour ce faire, ils émettent le signal ` closeEditor()` accompagné d’une indication appropriée. Cette tâche est prise en charge par le filtre d’événement par défaut ` QStyledItemDelegate `, que nous avons installé sur la zone de saisie lors de sa création.

Le comportement de la zone de sélection pourrait être ajusté pour la rendre plus conviviale. Dans le filtre d’événement par défaut fourni par QStyledItemDelegate, si l’utilisateur clique sur Return pour confirmer son choix dans la zone de sélection, le délégué valide la valeur dans le modèle et ferme la zone de sélection. Nous pouvons modifier ce comportement en installant notre propre filtre d’événement sur la zone de sélection, et fournir des conseils d’édition adaptés à nos besoins ; par exemple, nous pourrions déclencher closeEditor() avec le conseil « EditNextItem » pour lancer automatiquement l’édition de l’élément suivant dans la vue.

Une autre approche, qui ne nécessite pas l’utilisation d’un filtre d’événements, consiste à fournir notre propre widget d’édition, éventuellement en créant une sous-classe de ` QSpinBox ` pour plus de commodité. Cette approche alternative nous donnerait davantage de contrôle sur le comportement du widget d’édition, au prix d’un effort de programmation supplémentaire. Il est généralement plus simple d’installer un filtre d’événement dans le délégué si vous avez besoin de personnaliser le comportement d’un widget d’édition Qt standard.

Les délégués ne sont pas tenus d’émettre ces indications, mais ceux qui ne le font pas seront moins bien intégrés aux applications et s’avéreront moins pratiques que ceux qui émettent des indications pour prendre en charge les actions d’édition courantes.

Gestion des sélections dans les vues d’éléments

Concepts

Le modèle de sélection utilisé dans les classes de vues d’éléments fournit une description générale des sélections en s’appuyant sur les fonctionnalités de l’architecture modèle/vue. Bien que les classes standard de manipulation des sélections soient suffisantes pour les vues d’éléments fournies, le modèle de sélection vous permet de créer des modèles de sélection spécialisés adaptés aux exigences de vos propres modèles d’éléments et vues.

Les informations relatives aux éléments sélectionnés dans une vue sont stockées dans une instance de la classe ` QItemSelectionModel `. Celle-ci gère les index du modèle pour les éléments d’un même modèle et est indépendante de toute vue. Étant donné qu’un modèle peut comporter plusieurs vues, il est possible de partager les sélections entre ces vues, ce qui permet aux applications d’afficher plusieurs vues de manière cohérente.

Les sélections sont constituées de plages de sélection. Celles-ci gèrent efficacement les informations relatives à de grandes sélections d’éléments en enregistrant uniquement les index de modèle de début et de fin pour chaque plage d’éléments sélectionnés. Les sélections d’éléments non contiguës sont construites à l’aide de plusieurs plages de sélection pour décrire la sélection.

Les sélections s’appliquent à un ensemble d’index de modèle détenus par un modèle de sélection. La sélection d’éléments la plus récente appliquée est appelée « sélection actuelle ». Les effets de cette sélection peuvent être modifiés même après son application, grâce à l’utilisation de certains types de commandes de sélection. Celles-ci sont abordées plus loin dans cette section.

Élément actuel et éléments sélectionnés

Dans une vue, il existe toujours un élément actuel et un élément sélectionné : il s’agit de deux états indépendants. Un élément peut être à la fois l’élément actuel et un élément sélectionné. La vue doit veiller à ce qu’il y ait toujours un élément actuel, car la navigation au clavier, par exemple, nécessite un élément actuel.

Le tableau ci-dessous met en évidence les différences entre l'élément actif et les éléments sélectionnés.

Élément actifÉléments sélectionnés
Il ne peut y avoir qu'un seul élément actif.Il peut y avoir plusieurs éléments sélectionnés.
L'élément actif est modifié à l'aide des touches de navigation ou en cliquant avec la souris.Le statut de sélection des éléments est activé ou désactivé, selon plusieurs modes prédéfinis (par exemple, sélection unique, sélection multiple, etc.) lorsque l'utilisateur interagit avec les éléments.
L'élément actif sera modifié si la touche d'édition ( F2) est enfoncée ou si l'on double-clique sur l'élément (à condition que la modification soit activée).L'élément actif peut être utilisé conjointement avec une ancre pour spécifier une plage à sélectionner ou à désélectionner (ou une combinaison des deux).
L'élément actif est indiqué par le rectangle de focus.Les éléments sélectionnés sont indiqués par le rectangle de sélection.

Lors de la manipulation des sélections, il est souvent utile de considérer « QItemSelectionModel » comme un enregistrement de l’état de sélection de tous les éléments d’un modèle d’éléments. Une fois qu’un modèle de sélection est configuré, des ensembles d’éléments peuvent être sélectionnés, désélectionnés ou leur état de sélection peut être basculé sans qu’il soit nécessaire de savoir quels éléments sont déjà sélectionnés. Les indices de tous les éléments sélectionnés peuvent être récupérés à tout moment, et les autres composants peuvent être informés des modifications apportées au modèle de sélection via le mécanisme des signaux et des slots.

Utilisation d’un modèle de sélection

Les classes de vue standard fournissent des modèles de sélection par défaut pouvant être utilisés dans la plupart des applications. Un modèle de sélection appartenant à une vue peut être obtenu à l’aide de la fonction ` selectionModel()` de la vue, et partagé entre plusieurs vues grâce à ` setSelectionModel()` ; il n’est donc généralement pas nécessaire de créer de nouveaux modèles de sélection.

Une sélection est créée en spécifiant un modèle et une paire d’indices de modèle à ` QItemSelection`. Cette fonction utilise les indices pour référencer des éléments dans le modèle donné et les interprète comme les éléments situés en haut à gauche et en bas à droite d’un bloc d’éléments sélectionnés. Pour appliquer la sélection aux éléments d’un modèle, il faut que celle-ci soit soumise à un modèle de sélection ; cela peut se faire de plusieurs façons, chacune ayant un effet différent sur les sélections déjà présentes dans le modèle de sélection.

Sélection d’éléments

Pour illustrer certaines des principales fonctionnalités des sélections, nous créons une instance d’un modèle de table personnalisé comprenant 32 éléments au total, puis nous ouvrons une vue de table sur ses données :

TableModel *model = new TableModel(8, 4, &app);

QTableView *table = new QTableView(0);
table->setModel(model);

QItemSelectionModel *selectionModel = table->selectionModel();

Le modèle de sélection par défaut de la vue de tableau est récupéré pour une utilisation ultérieure. Nous ne modifions aucun élément du modèle, mais sélectionnons plutôt quelques éléments que la vue affichera en haut à gauche du tableau. Pour ce faire, nous devons récupérer les index du modèle correspondant aux éléments situés en haut à gauche et en bas à droite de la région à sélectionner :

QModelIndex topLeft;
QModelIndex bottomRight;

topLeft = model->index(0, 0, QModelIndex());
bottomRight = model->index(5, 2, QModelIndex());

Pour sélectionner ces éléments dans le modèle et voir le changement correspondant s'afficher dans la vue de tableau, nous devons créer un objet de sélection, puis l'appliquer au modèle de sélection :

QItemSelection selection(topLeft, bottomRight);
selectionModel->select(selection, QItemSelectionModel::Select);

La sélection est appliquée au modèle de sélection à l’aide d’une commande définie par une combinaison d’ selection flags. Dans ce cas, les indicateurs utilisés font en sorte que les éléments enregistrés dans l’objet de sélection soient inclus dans le modèle de sélection, quel que soit leur état précédent. La sélection obtenue est affichée par la vue.

Le modèle de sélection d'un modèle de table est surligné en bleu

La sélection d’éléments peut être modifiée à l’aide de diverses opérations définies par les indicateurs de sélection. La sélection résultant de ces opérations peut présenter une structure complexe, mais elle est représentée de manière efficace par le modèle de sélection. L’utilisation de différents indicateurs de sélection pour manipuler les éléments sélectionnés est décrite lorsque nous examinons comment mettre à jour une sélection.

Lecture de l'état de la sélection

Les index de modèle stockés dans le modèle de sélection peuvent être lus à l'aide de la fonction ` selectedIndexes()`. Celle-ci renvoie une liste non triée d’index de modèle sur laquelle on peut itérer à condition de connaître le modèle auquel ils se rapportent :

const QModelIndexList indexes = selectionModel->selectedIndexes();

for (const QModelIndex &index : indexes) {
    QString text = QString("(%1,%2)").arg(index.row()).arg(index.column());
    model->setData(index, text);
}

Le code ci-dessus utilise une boucle « for » basée sur une plage pour parcourir et modifier les éléments correspondant aux index renvoyés par le modèle de sélection.

Le modèle de sélection émet des signaux pour signaler les changements dans la sélection. Ceux-ci informent les autres composants des modifications apportées à la sélection dans son ensemble ainsi qu’à l’élément actuellement sélectionné dans le modèle d’éléments. Nous pouvons connecter le signal ` selectionChanged()` à un slot et examiner les éléments du modèle qui sont sélectionnés ou désélectionnés lorsque la sélection change. Le slot est appelé avec deux objets ` QItemSelection ` : l’un contient une liste d’index correspondant aux éléments nouvellement sélectionnés ; l’autre contient les index correspondant aux éléments nouvellement désélectionnés.

Dans le code suivant, nous définissons un slot qui reçoit le signal ` selectionChanged()`, remplit les éléments sélectionnés avec une chaîne de caractères et efface le contenu des éléments désélectionnés.

void MainWindow::updateSelection(const QItemSelection &selected,
    const QItemSelection &deselected)
{
    QModelIndexList items = selected.indexes();

    for (const QModelIndex &index : std::as_const(items)) {
        QString text = QString("(%1,%2)").arg(index.row()).arg(index.column());
        model->setData(index, text);
    }

    items = deselected.indexes();

    for (const QModelIndex &index : std::as_const(items)) {
        model->setData(index, QString());
    }
}

Nous pouvons suivre l’élément actuellement sélectionné en connectant le signal currentChanged() à un slot appelé avec deux index du modèle. Ceux-ci correspondent à l’élément précédemment sélectionné et à l’élément actuellement sélectionné.

Dans le code suivant, nous définissons un slot qui reçoit le signal ` currentChanged()` et utilise les informations fournies pour mettre à jour la barre d’état d’un ` QMainWindow` :

void MainWindow::changeCurrent(const QModelIndex &current,
    const QModelIndex &previous)
{
    statusBar()->showMessage(
        tr("Moved from (%1,%2) to (%3,%4)")
            .arg(previous.row()).arg(previous.column())
            .arg(current.row()).arg(current.column()));
}

La surveillance des sélections effectuées par l’utilisateur est simple grâce à ces signaux, mais nous pouvons également mettre à jour directement le modèle de sélection.

Mise à jour d’une sélection

Les commandes de sélection sont assurées par une combinaison d’indicateurs de sélection, définis par QItemSelectionModel::SelectionFlag. Chaque indicateur de sélection indique au modèle de sélection comment mettre à jour son enregistrement interne des éléments sélectionnés lorsque l’une des fonctions select() est appelée. L’indicateur le plus couramment utilisé est l’indicateur « Select », qui ordonne au modèle de sélection d’enregistrer les éléments spécifiés comme étant sélectionnés. L’indicateur « Toggle » provoque l’inversion de l’état des éléments spécifiés par le modèle de sélection : il sélectionne les éléments désélectionnés et désélectionne les éléments actuellement sélectionnés. L’indicateur « Deselect » désélectionne tous les éléments spécifiés.

Les éléments individuels du modèle de sélection sont mis à jour en créant une sélection d’éléments, puis en l’appliquant au modèle de sélection. Dans le code suivant, nous appliquons une deuxième sélection d’éléments au modèle de table présenté ci-dessus, en utilisant la commande « Toggle » pour inverser l’état de sélection des éléments indiqués.

QItemSelection toggleSelection;

topLeft = model->index(2, 1, QModelIndex());
bottomRight = model->index(7, 3, QModelIndex());
toggleSelection.select(topLeft, bottomRight);

selectionModel->select(toggleSelection, QItemSelectionModel::Toggle);

Les résultats de cette opération s’affichent dans la vue de table, ce qui permet de visualiser facilement ce que nous avons obtenu :

Le modèle de sélection mis à jour présente des couleurs inversées

Par défaut, les commandes de sélection ne s'appliquent qu'aux éléments individuels spécifiés par les index du modèle. Cependant, l'indicateur utilisé pour décrire la commande de sélection peut être combiné avec des indicateurs supplémentaires afin de modifier des lignes et des colonnes entières. Par exemple, si vous appelez select() avec un seul index, mais avec une commande combinant Select et Rows, la ligne entière contenant l'élément référencé est sélectionnée. Le code suivant illustre l'utilisation des indicateurs Rows et Columns:

QItemSelection columnSelection;

topLeft = model->index(0, 1, QModelIndex());
bottomRight = model->index(0, 2, QModelIndex());

columnSelection.select(topLeft, bottomRight);

selectionModel->select(columnSelection,
    QItemSelectionModel::Select | QItemSelectionModel::Columns);

QItemSelection rowSelection;

topLeft = model->index(0, 0, QModelIndex());
bottomRight = model->index(1, 0, QModelIndex());

rowSelection.select(topLeft, bottomRight);

selectionModel->select(rowSelection,
    QItemSelectionModel::Select | QItemSelectionModel::Rows);

Bien que seuls quatre index soient fournis au modèle de sélection, l’utilisation des indicateurs de sélection « Columns » et « Rows » permet de sélectionner deux colonnes et deux lignes. L’image suivante montre le résultat de ces deux sélections :

Mise à jour de colonnes ou de lignes entières dans le modèle de sélection

Les commandes exécutées sur le modèle d’exemple ont toutes consisté à accumuler une sélection d’éléments dans le modèle. Il est également possible d’effacer la sélection ou de remplacer la sélection actuelle par une nouvelle.

Pour remplacer la sélection actuelle par une nouvelle sélection, combinez les autres indicateurs de sélection avec l’indicateur Current. Une commande utilisant cet indicateur ordonne au modèle de sélection de remplacer son ensemble actuel d’index de modèle par ceux spécifiés dans un appel à la fonction ` select()`. Pour effacer toutes les sélections avant d’en ajouter de nouvelles, combinez les autres indicateurs de sélection avec l’indicateur ` Clear `. Cela a pour effet de réinitialiser l’ensemble d’index de modèle du modèle de sélection.

Sélection de tous les éléments d’un modèle

Pour sélectionner tous les éléments d’un modèle, il est nécessaire de créer une sélection pour chaque niveau du modèle qui couvre tous les éléments de ce niveau. Pour ce faire, on récupère les index correspondant aux éléments situés en haut à gauche et en bas à droite d’un index parent donné :

QModelIndex topLeft = model->index(0, 0, parent);
QModelIndex bottomRight = model->index(model->rowCount(parent)-1,
    model->columnCount(parent)-1, parent);

Une sélection est construite à partir de ces index et du modèle. Les éléments correspondants sont ensuite sélectionnés dans le modèle de sélection :

QItemSelection selection(topLeft, bottomRight);
selectionModel->select(selection, QItemSelectionModel::Select);

Cette opération doit être effectuée pour tous les niveaux du modèle. Pour les éléments de niveau supérieur, on définirait l’index parent de la manière habituelle :

Pour les modèles hiérarchiques, la fonction ` hasChildren()` permet de déterminer si un élément donné est le parent d'un autre niveau d'éléments.

Création de nouveaux modèles

La séparation des fonctionnalités entre les composants « modèle » et « vue » permet de créer des modèles capables de tirer parti des vues existantes. Cette approche nous permet de présenter des données provenant de diverses sources à l’aide de composants standard d’interface utilisateur graphique, tels que QListView, QTableView et QTreeView.

La classe QAbstractItemModel fournit une interface suffisamment flexible pour prendre en charge des sources de données qui organisent les informations en structures hiérarchiques, permettant ainsi l'insertion, la suppression, la modification ou le tri des données de différentes manières. Elle prend également en charge les opérations de glisser-déposer.

Les classes ` QAbstractListModel ` et ` QAbstractTableModel ` permettent de gérer des interfaces vers des structures de données non hiérarchiques plus simples ; elles constituent un point de départ plus facile à utiliser pour créer des modèles simples de listes et de tableaux.

Dans cette section, nous créons un modèle simple en lecture seule afin d’explorer les principes de base de l’architecture modèle/vue. Plus loin dans cette section, nous adaptons ce modèle simple afin que les éléments puissent être modifiés par l’utilisateur.

Pour un exemple de modèle plus complexe, consultez l’exemple « Modèle d’arborescence simple ».

Les exigences relatives aux sous-classes d'QAbstractItemModel s sont décrites plus en détail dans le document de référence sur la sous-classification des modèles.

Conception d’un modèle

Lors de la création d’un nouveau modèle pour une structure de données existante, il est important de déterminer quel type de modèle doit être utilisé pour fournir une interface vers les données. Si les données sont déjà stockées dans un conteneur C++ ou dans un type quelconque modélisant une plage itérable en C++, ` QRangeModel ` peut fournir un modèle adapté sans aucune sous-classification, à condition que la plage d’entrée et le type de ses éléments soient pris en charge.

Si aucun modèle prêt à l’emploi n’est applicable et que la structure de données peut être représentée sous la forme d’une liste ou d’un tableau d’éléments, vous pouvez créer une sous-classe de ` QAbstractListModel ` ou de ` QAbstractTableModel `, car ces classes fournissent des implémentations par défaut adaptées à de nombreuses fonctions.

Toutefois, si la structure de données sous-jacente ne peut être représentée que par une structure arborescente hiérarchique, il est nécessaire de créer une sous-classe de ` QAbstractItemModel`. C'est l'approche adoptée dans l'exemple « Simple Tree Model ».

Dans cette section, nous implémentons un modèle simple basé sur une liste de chaînes de caractères ; ainsi, la classe ` QAbstractListModel ` constitue une classe de base idéale sur laquelle s’appuyer.

Quelle que soit la forme de la structure de données sous-jacente, il est généralement judicieux de compléter l'API standard d'QAbstractItemModel dans les modèles spécialisés par une API permettant un accès plus naturel à la structure de données sous-jacente. Cela facilite le remplissage du modèle avec des données, tout en permettant aux autres composants généraux du modèle/de la vue d'interagir avec lui à l'aide de l'API standard. Le modèle décrit ci-dessous fournit un constructeur personnalisé précisément à cette fin.

Un exemple de modèle en lecture seule

Le modèle implémenté ici est un modèle de données simple, non hiérarchique et en lecture seule, basé sur la classe standard ` QStringListModel `. Il utilise une ` QStringList ` comme source de données interne et n’implémente que ce qui est nécessaire pour créer un modèle fonctionnel. Pour faciliter l’implémentation, nous créons une sous-classe de ` QAbstractListModel `, car celle-ci définit un comportement par défaut judicieux pour les modèles de type liste et expose une interface plus simple que la classe ` QAbstractItemModel `.

Lors de l’implémentation d’un modèle, il est important de garder à l’esprit qu’ QAbstractItemModel ne stocke aucune donnée en soi ; elle se contente de présenter une interface que les vues utilisent pour accéder aux données. Pour un modèle minimal en lecture seule, il suffit d’implémenter quelques fonctions seulement, car il existe des implémentations par défaut pour la majeure partie de l’interface. La déclaration de la classe est la suivante :

class StringListModel : public QAbstractListModel
{
    Q_OBJECT

public:
    StringListModel(const QStringList &strings, QObject *parent = nullptr)
        : QAbstractListModel(parent), stringList(strings) {}

    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
    QVariant data(const QModelIndex &index, int role) const override;
    QVariant headerData(int section, Qt::Orientation orientation,
                        int role = Qt::DisplayRole) const override;

private:
    QStringList stringList;
};

Hormis le constructeur du modèle, nous n’avons besoin d’implémenter que deux fonctions : ` rowCount()` renvoie le nombre de lignes du modèle et ` data()` renvoie une donnée correspondant à un index spécifié du modèle.

Les modèles bien conçus implémentent également la fonction ` headerData()` afin de fournir aux vues arborescentes et tabulaires des éléments à afficher dans leurs en-têtes.

Notez qu’il s’agit d’un modèle non hiérarchique ; nous n’avons donc pas à nous soucier des relations parent-enfant. Si notre modèle était hiérarchique, nous devrions également implémenter les fonctions ` index()` et ` parent()`.

La liste de chaînes de caractères est stockée en interne dans la variable membre privée ` stringList `.

Dimensions du modèle

Nous souhaitons que le nombre de lignes du modèle corresponde au nombre de chaînes de caractères de la liste. Nous implémentons la fonction ` rowCount()` en tenant compte de cela :

int StringListModel::rowCount(const QModelIndex &parent) const
{
    return stringList.count();
}

Le modèle n’étant pas hiérarchique, nous pouvons sans risque ignorer l’index du modèle correspondant à l’élément parent. Par défaut, les modèles dérivés de ` QAbstractListModel ` ne contiennent qu’une seule colonne ; nous n’avons donc pas besoin de réimplémenter la fonction ` columnCount()`.

En-têtes et données du modèle

Pour les éléments de la vue, nous souhaitons renvoyer les chaînes de caractères de la liste de chaînes. La fonction ` data()` est chargée de renvoyer l’élément de données correspondant à l’argument `index` :

QVariant StringListModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
        return QVariant();

    if (index.row() >= stringList.size())
        return QVariant();

    if (role == Qt::DisplayRole)
        return stringList.at(index.row());
    else
        return QVariant();
}

Nous ne renvoyons une valeur QVariant valide que si l’index du modèle fourni est valide, si le numéro de ligne se situe dans la plage des éléments de la liste de chaînes de caractères et si le rôle demandé est pris en charge.

Certaines vues, telles que ` QTreeView ` et ` QTableView`, permettent d’afficher des en-têtes en plus des données des éléments. Si notre modèle est affiché dans une vue avec des en-têtes, nous souhaitons que celles-ci indiquent les numéros de ligne et de colonne. Nous pouvons fournir des informations sur les en-têtes en créant une sous-classe de la fonction ` headerData()` :

QVariant StringListModel::headerData(int section, Qt::Orientation orientation,
                                     int role) const
{
    if (role != Qt::DisplayRole)
        return QVariant();

    if (orientation == Qt::Horizontal)
        return QStringLiteral("Column %1").arg(section);
    else
        return QStringLiteral("Row %1").arg(section);
}

Là encore, nous ne renvoyons un ` QVariant ` valide que si le rôle est pris en charge. L’orientation de l’en-tête est également prise en compte lors du choix des données exactes à renvoyer.

Toutes les vues n’affichent pas d’en-têtes avec les données des éléments, et celles qui le font peuvent être configurées pour les masquer. Il est néanmoins recommandé d’implémenter la fonction headerData() afin de fournir des informations pertinentes sur les données fournies par le modèle.

Un élément peut avoir plusieurs rôles, fournissant des données différentes selon le rôle spécifié. Les éléments de notre modèle n’ont qu’un seul rôle, DisplayRole, nous renvoyons donc les données des éléments quel que soit le rôle spécifié. Cependant, nous pourrions réutiliser les données que nous fournissons pour l’ DisplayRole dans d’autres rôles, tels que l’ ToolTipRole que les vues peuvent utiliser pour afficher des informations sur les éléments dans une infobulle.

Un modèle modifiable

Le modèle en lecture seule montre comment des choix simples peuvent être présentés à l’utilisateur, mais, pour de nombreuses applications, un modèle de liste modifiable s’avère bien plus utile. Nous pouvons modifier le modèle en lecture seule pour rendre les éléments modifiables en modifiant la fonction `data()` que nous avons implémentée pour le mode lecture seule, et en implémentant deux fonctions supplémentaires : ` flags()` et ` setData()`. Les déclarations de fonctions suivantes sont ajoutées à la définition de la classe :

    Qt::ItemFlags flags(const QModelIndex &index) const override;
    bool setData(const QModelIndex &index, const QVariant &value,
                 int role = Qt::EditRole) override;

Rendre le modèle modifiable

Un délégué vérifie si un élément est modifiable avant de créer un éditeur. Le modèle doit indiquer au délégué que ses éléments sont modifiables. Pour ce faire, nous renvoyons les indicateurs appropriés pour chaque élément du modèle ; dans ce cas, nous activons tous les éléments et les rendons à la fois sélectionnables et modifiables :

Qt::ItemFlags StringListModel::flags(const QModelIndex &index) const
{
    if (!index.isValid())
        return Qt::ItemIsEnabled;

    return QAbstractItemModel::flags(index) | Qt::ItemIsEditable;
}

Notez que nous n’avons pas besoin de savoir comment le délégué effectue le processus d’édition proprement dit. Nous devons simplement fournir au délégué un moyen de définir les données dans le modèle. Cela s’effectue via la fonction ` setData()` :

bool StringListModel::setData(const QModelIndex &index,
                              const QVariant &value, int role)
{
    if (index.isValid() && role == Qt::EditRole) {

        stringList.replace(index.row(), value.toString());
        emit dataChanged(index, index, {role});
        return true;
    }
    return false;
}

Dans ce modèle, l’élément de la liste de chaînes correspondant à l’index du modèle est remplacé par la valeur fournie. Cependant, avant de pouvoir modifier la liste de chaînes, nous devons nous assurer que l’index est valide, que l’élément est du type approprié et que le rôle est pris en charge. Par convention, nous insistons pour que le rôle soit « EditRole », car c’est celui utilisé par le délégué d’élément standard. Pour les valeurs booléennes, vous pouvez toutefois utiliser « Qt::CheckStateRole » et activer l’indicateur « Qt::ItemIsUserCheckable » ; une case à cocher est alors utilisée pour modifier la valeur. Les données sous-jacentes de ce modèle sont identiques pour tous les rôles ; ce détail facilite donc simplement l’intégration du modèle avec des composants standard.

Une fois les données définies, le modèle doit signaler aux vues que certaines données ont changé. Pour ce faire, il émet le signal ` dataChanged()`. Étant donné qu’un seul élément de données a changé, la plage d’éléments spécifiée dans le signal est limitée à un seul index du modèle.

De plus, la fonction `data()` doit être modifiée pour ajouter le test ` Qt::EditRole ` :

QVariant StringListModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
        return QVariant();

    if (index.row() >= stringList.size())
        return QVariant();

    if (role == Qt::DisplayRole || role == Qt::EditRole)
        return stringList.at(index.row());
    else
        return QVariant();
}

Insertion et suppression de lignes

Il est possible de modifier le nombre de lignes et de colonnes d’un modèle. Dans le modèle de liste de chaînes, il n’est pertinent de modifier que le nombre de lignes ; nous ne réimplémentons donc que les fonctions d’insertion et de suppression de lignes. Celles-ci sont déclarées dans la définition de la classe :

    bool insertRows(int position, int rows, const QModelIndex &index = QModelIndex()) override;
    bool removeRows(int position, int rows, const QModelIndex &index = QModelIndex()) override;

Comme les lignes de ce modèle correspondent à des chaînes de caractères dans une liste, la fonction ` insertRows() ` insère un certain nombre de chaînes vides dans la liste de chaînes avant la position spécifiée. Le nombre de chaînes insérées est équivalent au nombre de lignes spécifié.

L’index parent sert normalement à déterminer à quel endroit du modèle les lignes doivent être ajoutées. Dans ce cas précis, nous ne disposons que d’une seule liste de chaînes de caractères de niveau supérieur ; nous nous contentons donc d’y insérer des chaînes vides.

bool StringListModel::insertRows(int position, int rows, const QModelIndex &parent)
{
    beginInsertRows(QModelIndex(), position, position+rows-1);

    for (int row = 0; row < rows; ++row) {
        stringList.insert(position, "");
    }

    endInsertRows();
    return true;
}

Le modèle appelle d’abord la fonction ` beginInsertRows()` pour informer les autres composants que le nombre de lignes est sur le point de changer. La fonction précise les numéros de ligne de la première et de la dernière des nouvelles lignes à insérer, ainsi que l’index du modèle correspondant à leur élément parent. Après avoir modifié la liste de chaînes de caractères, elle appelle endInsertRows() pour finaliser l’opération et signaler aux autres composants que les dimensions du modèle ont changé, en renvoyant « true » pour indiquer que l’opération a réussi.

La fonction permettant de supprimer des lignes du modèle est également simple à écrire. Les lignes à supprimer du modèle sont spécifiées par leur position et leur numéro. Nous ignorons l’index du parent pour simplifier notre implémentation, et nous nous contentons de supprimer les éléments correspondants de la liste de chaînes de caractères.

bool StringListModel::removeRows(int position, int rows, const QModelIndex &parent)
{
    beginRemoveRows(QModelIndex(), position, position+rows-1);

    for (int row = 0; row < rows; ++row) {
        stringList.removeAt(position);
    }

    endRemoveRows();
    return true;
}

La fonction ` beginRemoveRows()` est toujours appelée avant la suppression de toute donnée sous-jacente et spécifie la première et la dernière ligne à supprimer. Cela permet aux autres composants d'accéder aux données avant qu'elles ne deviennent indisponibles. Une fois les lignes supprimées, le modèle émet ` endRemoveRows()` pour terminer l'opération et signaler aux autres composants que les dimensions du modèle ont changé.

Étapes suivantes

Nous pouvons afficher les données fournies par ce modèle, ou par tout autre modèle, en utilisant la classe ` QListView ` pour présenter les éléments du modèle sous la forme d’une liste verticale. Pour le modèle de liste de chaînes de caractères, cette vue fournit également un éditeur par défaut permettant de manipuler les éléments. Nous examinons les possibilités offertes par les classes de vue standard dans la section Classes de vue.

Le document « Référence sur la sous-classification des modèles » aborde plus en détail les exigences relatives aux sous-classes de ` QAbstractItemModel ` et fournit un guide sur les fonctions virtuelles qui doivent être implémentées pour activer diverses fonctionnalités dans différents types de modèles.

Classes utilitaires de vue d’éléments

Les widgets basés sur des éléments portent des noms qui reflètent leur utilisation : ` QListWidget ` fournit une liste d’éléments, ` QTreeWidget ` affiche une arborescence à plusieurs niveaux et ` QTableWidget ` fournit un tableau d’éléments de cellule. Chaque classe hérite du comportement de la classe ` QAbstractItemView `, qui implémente un comportement commun pour la sélection des éléments et la gestion des en-têtes.

Widgets de liste

Les listes d’éléments à un seul niveau sont généralement affichées à l’aide d’un QListWidget et de plusieurs QListWidgetItem. Un widget de liste est construit de la même manière que n’importe quel autre widget :

QListWidget *listWidget = new QListWidget(this);

Les éléments de liste peuvent être ajoutés directement au widget de liste lors de sa construction :

new QListWidgetItem(tr("Sycamore"), listWidget);
new QListWidgetItem(tr("Chestnut"), listWidget);
new QListWidgetItem(tr("Mahogany"), listWidget);

Ils peuvent également être créés sans widget de liste parent et ajoutés à une liste ultérieurement :

QListWidgetItem *newItem = new QListWidgetItem;
newItem->setText(itemText);
listWidget->insertItem(row, newItem);

Chaque élément d’une liste peut afficher une étiquette de texte et une icône. Les couleurs et la police utilisées pour afficher le texte peuvent être modifiées afin de personnaliser l’apparence des éléments. Les infobulles, les indications d’état et l’aide « Qu’est-ce que c’est ? » sont toutes facilement configurables pour garantir une intégration optimale de la liste dans l’application.

newItem->setToolTip(toolTipText);
newItem->setStatusTip(toolTipText);
newItem->setWhatsThis(whatsThisText);

Par défaut, les éléments d’une liste sont présentés dans l’ordre de leur création. Les listes d’éléments peuvent être triées selon les critères indiqués dans Qt::SortOrder afin d’obtenir une liste d’éléments triée par ordre alphabétique croissant ou décroissant :

listWidget->sortItems(Qt::AscendingOrder);
listWidget->sortItems(Qt::DescendingOrder);

Widgets arborescents

Les arborescences, ou listes hiérarchiques d'éléments, sont fournies par les classes ` QTreeWidget ` et ` QTreeWidgetItem `. Chaque élément du widget arborescent peut avoir ses propres éléments enfants et afficher plusieurs colonnes d'informations. Les widgets arborescents se créent comme n'importe quel autre widget :

QTreeWidget *treeWidget = new QTreeWidget(this);

Avant de pouvoir ajouter des éléments au widget arborescent, il faut définir le nombre de colonnes. Par exemple, on pourrait définir deux colonnes et créer un en-tête pour afficher des libellés en haut de chaque colonne :

treeWidget->setColumnCount(2);
QStringList headers;
headers << tr("Subject") << tr("Default");
treeWidget->setHeaderLabels(headers);

La manière la plus simple de configurer les libellés de chaque section consiste à fournir une liste de chaînes de caractères. Pour des en-têtes plus sophistiqués, vous pouvez construire un élément d’arborescence, le décorer à votre guise, puis l’utiliser comme en-tête du widget arborescence.

Les éléments de niveau supérieur du widget arborescent sont créés en utilisant le widget arborescent comme widget parent. Ils peuvent être insérés dans un ordre arbitraire, ou vous pouvez vous assurer qu’ils apparaissent dans un ordre particulier en spécifiant l’élément précédent lors de la création de chaque élément :

QTreeWidgetItem *cities = new QTreeWidgetItem(treeWidget);
cities->setText(0, tr("Cities"));
QTreeWidgetItem *osloItem = new QTreeWidgetItem(cities);
osloItem->setText(0, tr("Oslo"));
osloItem->setText(1, tr("Yes"));

QTreeWidgetItem *planets = new QTreeWidgetItem(treeWidget, cities);

Les widgets arborescents traitent les éléments de niveau supérieur de manière légèrement différente des autres éléments situés plus en profondeur dans l’arborescence. Les éléments peuvent être supprimés du niveau supérieur de l’arborescence en appelant la fonction ` takeTopLevelItem()` du widget arborescent, mais les éléments des niveaux inférieurs sont supprimés en appelant la fonction ` takeChild()` de leur élément parent. Les éléments sont insérés au niveau supérieur de l’arborescence à l’aide de la fonction ` insertTopLevelItem()`. Aux niveaux inférieurs de l’arborescence, on utilise la fonction ` insertChild()` de l’élément parent.

Il est facile de déplacer des éléments entre le niveau supérieur et les niveaux inférieurs de l’arborescence. Il suffit de vérifier si les éléments sont des éléments de niveau supérieur ou non, et cette information est fournie par la fonction parent() de chaque élément. Par exemple, on peut supprimer l’élément actuel du widget arborescence, quel que soit son emplacement :

QTreeWidgetItem *parent = currentItem->parent();
int index;

if (parent) {
    index = parent->indexOfChild(treeWidget->currentItem());
    delete parent->takeChild(index);
} else {
    index = treeWidget->indexOfTopLevelItem(treeWidget->currentItem());
    delete treeWidget->takeTopLevelItem(index);
}

L'insertion de l'élément à un autre endroit du widget arborescent suit le même principe :

QTreeWidgetItem *parent = currentItem->parent();
QTreeWidgetItem *newItem;
if (parent)
    newItem = new QTreeWidgetItem(parent, treeWidget->currentItem());
else
    newItem = new QTreeWidgetItem(treeWidget, treeWidget->currentItem());

Widgets de table

Les tableaux d’éléments, similaires à ceux que l’on trouve dans les tableurs, sont construits à l’aide des méthodes ` QTableWidget ` et ` QTableWidgetItem`. Celles-ci fournissent un widget de tableau défilant avec des en-têtes et des éléments à utiliser à l’intérieur.

Les tableaux peuvent être créés avec un nombre défini de lignes et de colonnes, ou celles-ci peuvent être ajoutées à un tableau de taille indéterminée au fur et à mesure des besoins.

QTableWidget *tableWidget;
tableWidget = new QTableWidget(12, 3, this);

Les éléments sont créés en dehors de la table avant d’être ajoutés à celle-ci à l’emplacement souhaité :

QTableWidgetItem *newItem = new QTableWidgetItem(tr("%1").arg(
    pow(row, column+1)));
tableWidget->setItem(row, column, newItem);

Des en-têtes horizontaux et verticaux peuvent être ajoutés au tableau en créant des éléments en dehors de celui-ci et en les utilisant comme en-têtes :

QTableWidgetItem *valuesHeaderItem = new QTableWidgetItem(tr("Values"));
tableWidget->setHorizontalHeaderItem(0, valuesHeaderItem);

Notez que les lignes et les colonnes du tableau commencent à zéro.

Fonctionnalités communes

Il existe un certain nombre de fonctionnalités liées aux éléments, communes à chacune des classes de commodité, qui sont accessibles via les mêmes interfaces dans chaque classe. Nous les présentons dans les sections suivantes à l’aide d’exemples pour différents widgets. Consultez la liste des classes Modèle/Vue pour chacun des widgets afin d’obtenir plus de détails sur l’utilisation de chaque fonction.

Éléments masqués

Il est parfois utile de pouvoir masquer des éléments dans un widget de vue d'éléments plutôt que de les supprimer. Les éléments de tous les widgets ci-dessus peuvent être masqués puis affichés à nouveau ultérieurement. Vous pouvez déterminer si un élément est masqué en appelant la fonction `isItemHidden()`, et les éléments peuvent être masqués à l'aide de ` setItemHidden()`.

Comme cette opération s’applique au niveau des éléments, la même fonction est disponible pour les trois classes de commodité.

Sélections

La manière dont les éléments sont sélectionnés est contrôlée par le mode de sélection du widget (QAbstractItemView::SelectionMode). Cette propriété détermine si l’utilisateur peut sélectionner un ou plusieurs éléments et, dans le cas d’une sélection de plusieurs éléments, si celle-ci doit obligatoirement former une plage continue d’éléments. Le mode de sélection fonctionne de la même manière pour tous les widgets mentionnés ci-dessus.

Sélectionner un seul élément

Sélection d’un seul élément : lorsque l’utilisateur doit choisir un seul élément dans un widget, le mode par défaut de SingleSelection est le plus adapté. Dans ce mode, l’élément actuel et l’élément sélectionné sont identiques.

Sélection de plusieurs éléments

Sélections de plusieurs éléments : dans ce mode, l’utilisateur peut activer ou désactiver la sélection de n’importe quel élément du widget sans modifier la sélection existante, de la même manière que les cases à cocher non exclusives peuvent être activées ou désactivées indépendamment les unes des autres.

Sélection d'éléments étendus et adjacents

Sélections étendues : les widgets qui nécessitent souvent la sélection de nombreux éléments adjacents, comme ceux que l’on trouve dans les feuilles de calcul, requièrent le mode « ExtendedSelection ». Dans ce mode, des plages continues d’éléments dans le widget peuvent être sélectionnées à la fois avec la souris et le clavier. Des sélections complexes, impliquant de nombreux éléments qui ne sont pas adjacents à d’autres éléments sélectionnés dans le widget, peuvent également être créées à l’aide de touches de modification.

Si l’utilisateur sélectionne un élément sans utiliser de touche de modification, la sélection existante est effacée.

Les éléments sélectionnés dans un widget sont lus à l’aide de la fonction ` selectedItems() `, qui fournit une liste des éléments concernés sur laquelle il est possible d’itérer. Par exemple, on peut calculer la somme de toutes les valeurs numériques d’une liste d’éléments sélectionnés à l’aide du code suivant :

const QList<QTableWidgetItem *> selected = tableWidget->selectedItems();
int number = 0;
double total = 0;

for (QTableWidgetItem *item : selected) {
    bool ok;
    double value = item->text().toDouble(&ok);

    if (ok && !item->text().isEmpty()) {
        total += value;
        number++;
    }
}

Notez que, en mode de sélection unique, l’élément courant fait partie de la sélection. En modes de sélection multiple et de sélection étendue, l’élément courant peut ne pas faire partie de la sélection, selon la manière dont l’utilisateur a effectué sa sélection.

Recherche

Il est souvent utile de pouvoir rechercher des éléments dans un widget d'affichage d'éléments, que ce soit en tant que développeur ou dans le cadre d'un service destiné aux utilisateurs. Les trois classes de commodité d'affichage d'éléments fournissent une fonction commune ` findItems() ` afin de rendre cette opération aussi cohérente et simple que possible.

La recherche d’éléments s’effectue en fonction du texte qu’ils contiennent, selon des critères spécifiés par une sélection de valeurs issues de ` Qt::MatchFlags`. On peut obtenir une liste des éléments correspondants à l’aide de la fonction ` findItems() ` :

const QList<QTreeWidgetItem *> found = treeWidget->findItems(
    itemText, Qt::MatchWildcard);

for (QTreeWidgetItem *item : found) {
    item->setSelected(true);
    // Show the item->text(0) for each item.
}

Le code ci-dessus permet de sélectionner les éléments d’un widget arborescent s’ils contiennent le texte indiqué dans la chaîne de recherche. Ce modèle peut également être utilisé dans les widgets de liste et de tableau.

Utilisation du glisser-déposer avec les vues d'éléments

L'infrastructure de glisser-déposer de Qt est entièrement prise en charge par le framework modèle/vue. Les éléments des listes, des tableaux et des arborescences peuvent être déplacés au sein des vues, et les données peuvent être importées et exportées sous forme de données encodées MIME.

Les vues standard prennent automatiquement en charge le glisser-déposer interne, qui permet de déplacer les éléments pour modifier leur ordre d'affichage. Par défaut, le glisser-déposer n'est pas activé pour ces vues, car elles sont configurées pour les utilisations les plus simples et les plus courantes. Pour permettre le glisser-déposer des éléments, certaines propriétés de la vue doivent être activées, et les éléments eux-mêmes doivent également autoriser cette opération.

Les exigences pour un modèle qui autorise uniquement l’exportation d’éléments depuis une vue, et qui ne permet pas d’y déposer des données, sont moins nombreuses que celles d’un modèle prenant pleinement en charge le glisser-déposer.

Consultez également la référence sur la sous-classification des modèles pour plus d’informations sur l’activation de la prise en charge du glisser-déposer dans les nouveaux modèles.

Utilisation des vues de commodité

Chacun des types d’éléments utilisés avec QListWidget, QTableWidget et QTreeWidget est configuré par défaut pour utiliser un ensemble différent de drapeaux. Par exemple, chaque QListWidgetItem ou QTreeWidgetItem est initialement activé, cochable, sélectionnable et peut servir de source à une opération de glisser-déposer ; chaque QTableWidgetItem peut également être modifié et servir de cible à une opération de glisser-déposer.

Bien que tous les éléments standard aient un ou les deux indicateurs activés pour le glisser-déposer, vous devez généralement définir diverses propriétés dans la vue elle-même pour tirer parti de la prise en charge intégrée du glisser-déposer :

  • Pour activer le glissement d’éléments, définissez la propriété ` dragEnabled ` de la vue sur ` true`.
  • Pour permettre à l’utilisateur de déposer des éléments internes ou externes au sein de la vue, définissez la propriété ` viewport()` de la vue sur ` acceptDrops `, puis sur ` true`.
  • Pour indiquer à l’utilisateur où l’élément en cours de glissement sera placé s’il est déposé, définissez la propriété showDropIndicator de la vue. Cela fournit à l’utilisateur des informations mises à jour en continu concernant l’emplacement de l’élément au sein de la vue.

Par exemple, nous pouvons activer le glisser-déposer dans un widget de liste à l’aide des lignes de code suivantes :

QListWidget *listWidget = new QListWidget(this);
listWidget->setSelectionMode(QAbstractItemView::SingleSelection);
listWidget->setDragEnabled(true);
listWidget->viewport()->setAcceptDrops(true);
listWidget->setDropIndicatorShown(true);

On obtient ainsi un widget de liste qui permet de copier les éléments au sein de la vue, et même de faire glisser des éléments entre des vues contenant le même type de données. Dans les deux cas, les éléments sont copiés plutôt que déplacés.

Pour permettre à l’utilisateur de déplacer les éléments au sein de la vue, nous devons définir la propriété ` dragDropMode` du widget de liste :

listWidget->setDragDropMode(QAbstractItemView::InternalMove);

Utilisation des classes de modèle/vue

La configuration d’une vue pour le glisser-déposer suit le même modèle que celui utilisé avec les vues de commodité. Par exemple, une méthode ` QListView ` peut être configurée de la même manière qu’une méthode ` QListWidget` :

QListView *listView = new QListView(this);
listView->setSelectionMode(QAbstractItemView::ExtendedSelection);
listView->setDragEnabled(true);
listView->setAcceptDrops(true);
listView->setDropIndicatorShown(true);

L’accès aux données affichées par la vue étant contrôlé par un modèle, celui-ci doit également prendre en charge les opérations de glisser-déposer. Les actions prises en charge par un modèle peuvent être spécifiées en réimplémentant la fonction ` QAbstractItemModel::supportedDropActions()`. Par exemple, les opérations de copie et de déplacement sont activées avec le code suivant :

Qt::DropActions DragDropListModel::supportedDropActions() const
{
    return Qt::CopyAction | Qt::MoveAction;
}

Bien que n’importe quelle combinaison de valeurs issues de ` Qt::DropActions ` puisse être fournie, le modèle doit être écrit de manière à les prendre en charge. Par exemple, pour permettre à ` Qt::MoveAction ` de fonctionner correctement avec un modèle de liste, le modèle doit fournir une implémentation de ` QAbstractItemModel::removeRows()`, soit directement, soit en héritant de l’implémentation de sa classe de base.

Activation du glisser-déposer pour les éléments

Les modèles indiquent aux vues quels éléments peuvent être glissés et lesquels acceptent d’être déposés, en réimplémentant la fonction QAbstractItemModel::flags() afin de fournir les indicateurs appropriés.

Par exemple, un modèle fournissant une liste simple basée sur ` QAbstractListModel ` peut activer le glisser-déposer pour chacun des éléments en s’assurant que les indicateurs renvoyés contiennent les valeurs ` Qt::ItemIsDragEnabled ` et ` Qt::ItemIsDropEnabled ` :

Qt::ItemFlags DragDropListModel::flags(const QModelIndex &index) const
{
    Qt::ItemFlags defaultFlags = QStringListModel::flags(index);

    if (index.isValid())
        return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags;
    else
        return Qt::ItemIsDropEnabled | defaultFlags;
}

Notez que les éléments peuvent être déposés au niveau supérieur du modèle, mais que le glisser-déposer n’est activé que pour les éléments valides.

Dans le code ci-dessus, le modèle étant dérivé de ` QStringListModel`, nous obtenons un ensemble par défaut de drapeaux en appelant son implémentation de la fonction `flags()`.

Encodage des données exportées

Lorsque des éléments de données sont exportés depuis un modèle lors d’une opération de glisser-déposer, ils sont encodés dans un format approprié correspondant à un ou plusieurs types MIME. Les modèles déclarent les types MIME qu’ils peuvent utiliser pour fournir des éléments en réimplémentant la fonction ` QAbstractItemModel::mimeTypes()`, qui renvoie une liste de types MIME standard.

Par exemple, un modèle ne fournissant que du texte brut proposerait l’implémentation suivante :

QStringList DragDropListModel::mimeTypes() const
{
    QStringList types;
    types << "application/vnd.text.list";
    return types;
}

Le modèle doit également fournir du code permettant d’encoder les données dans le format annoncé. Pour ce faire, il suffit de réimplémenter la fonction ` QAbstractItemModel::mimeData()` afin de fournir un objet ` QMimeData `, comme dans toute autre opération de glisser-déposer.

Le code suivant montre comment chaque élément de données, correspondant à une liste donnée d’index, est encodé en texte brut et stocké dans un objet ` QMimeData `.

QMimeData *DragDropListModel::mimeData(const QModelIndexList &indexes) const
{
    QMimeData *mimeData = new QMimeData;
    QByteArray encodedData;

    QDataStream stream(&encodedData, QIODevice::WriteOnly);

    for (const QModelIndex &index : indexes) {
        if (index.isValid()) {
            QString text = data(index, Qt::DisplayRole).toString();
            stream << text;
        }
    }

    mimeData->setData("application/vnd.text.list", encodedData);
    return mimeData;
}

Comme une liste d’index de modèle est fournie à la fonction, cette approche est suffisamment générale pour être utilisée aussi bien dans des modèles hiérarchiques que non hiérarchiques.

Notez que les types de données personnalisés doivent être déclarés comme ` meta objects ` et que des opérateurs de flux doivent être implémentés pour ceux-ci. Voir la description de la classe ` QMetaObject ` pour plus de détails.

Insertion de données supprimées dans un modèle

La manière dont un modèle donné gère les données supprimées dépend à la fois de son type (liste, table ou arborescence) et de la façon dont son contenu est susceptible d’être présenté à l’utilisateur. En règle générale, l’approche adoptée pour prendre en charge les données supprimées doit être celle qui convient le mieux au magasin de données sous-jacent du modèle.

Les différents types de modèles ont tendance à gérer les données déposées de différentes manières. Les modèles de liste et de tableau ne fournissent qu’une structure plate dans laquelle les éléments de données sont stockés. Par conséquent, ils peuvent insérer de nouvelles lignes (et colonnes) lorsque des données sont déposées sur un élément existant dans une vue, ou bien ils peuvent écraser le contenu de l’élément dans le modèle en utilisant une partie des données fournies. Les modèles arborescents sont souvent capables d’ajouter des éléments enfants contenant de nouvelles données à leurs bases de données sous-jacentes, et se comportent donc de manière plus prévisible du point de vue de l’utilisateur.

Les données déposées sont gérées par la réimplémentation, par le modèle, de la méthode ` QAbstractItemModel::dropMimeData()`. Par exemple, un modèle gérant une simple liste de chaînes de caractères peut fournir une implémentation qui traite séparément les données déposées sur des éléments existants et celles déposées au niveau supérieur du modèle (c'est-à-dire sur un élément non valide).

Les modèles peuvent interdire le dépôt sur certains éléments, ou en fonction des données déposées, en réimplémentant la méthode ` QAbstractItemModel::canDropMimeData()`.

Le modèle doit d’abord s’assurer que l’opération doit être effectuée, que les données fournies sont dans un format utilisable et que leur destination au sein du modèle est valide :

bool DragDropListModel::canDropMimeData(const QMimeData *data,
    Qt::DropAction action, int row, int column, const QModelIndex &parent) const
{
    Q_UNUSED(action);
    Q_UNUSED(row);
    Q_UNUSED(parent);

    if (!data->hasFormat("application/vnd.text.list"))
        return false;

    if (column > 0)
        return false;

    return true;
}
bool DragDropListModel::dropMimeData(const QMimeData *data,
    Qt::DropAction action, int row, int column, const QModelIndex &parent)
{
    if (!canDropMimeData(data, action, row, column, parent))
        return false;

    if (action == Qt::IgnoreAction)
        return true;

Un modèle simple, constitué d’une liste de chaînes de caractères sur une seule colonne, peut signaler un échec si les données fournies ne sont pas du texte brut, ou si le numéro de colonne indiqué pour le dépôt n’est pas valide.

Les données à insérer dans le modèle sont traitées différemment selon qu’elles sont déposées sur un élément existant ou non. Dans cet exemple simple, nous souhaitons autoriser les dépôts entre des éléments existants, avant le premier élément de la liste et après le dernier élément.

Lorsqu’un dépôt a lieu, l’index du modèle correspondant à l’élément parent sera soit valide, indiquant que le dépôt a eu lieu sur un élément, soit invalide, indiquant que le dépôt a eu lieu quelque part dans la vue correspondant au niveau supérieur du modèle.

int beginRow;

if (row != -1)
    beginRow = row;

Nous examinons d’abord le numéro de ligne fourni pour voir si nous pouvons l’utiliser pour insérer des éléments dans le modèle, que l’index parent soit valide ou non.

else if (parent.isValid())
    beginRow = parent.row();

Si l’index du modèle parent est valide, la suppression a eu lieu sur un élément. Dans ce modèle de liste simple, nous déterminons le numéro de ligne de l’élément et utilisons cette valeur pour insérer les éléments supprimés au niveau supérieur du modèle.

else
    beginRow = rowCount(QModelIndex());

Lorsqu’un glissement se produit ailleurs dans la vue et que le numéro de ligne est inutilisable, nous ajoutons les éléments au niveau supérieur du modèle.

Dans les modèles hiérarchiques, lorsqu’un glissement se produit sur un élément, il serait préférable d’insérer les nouveaux éléments dans le modèle en tant qu’enfants de cet élément. Dans l’exemple simple présenté ici, le modèle ne comporte qu’un seul niveau ; cette approche n’est donc pas appropriée.

Décodage des données importées

Chaque implémentation de la méthode ` dropMimeData()` doit également décoder les données et les insérer dans la structure de données sous-jacente du modèle.

Pour un modèle simple de liste de chaînes de caractères, les éléments encodés peuvent être décodés et transmis via un flux vers une méthode ` QStringList` :

QByteArray encodedData = data->data("application/vnd.text.list");
QDataStream stream(&encodedData, QIODevice::ReadOnly);
QStringList newItems;
int rows = 0;

while (!stream.atEnd()) {
    QString text;
    stream >> text;
    newItems << text;
    ++rows;
}

Les chaînes de caractères peuvent ensuite être insérées dans le magasin de données sous-jacent. Par souci de cohérence, cela peut être effectué via l’interface propre au modèle :

    insertRows(beginRow, rows, QModelIndex());
    for (const QString &text : std::as_const(newItems)) {
        QModelIndex idx = index(beginRow, 0, QModelIndex());
        setData(idx, text);
        beginRow++;
    }

    return true;
}

Notez que le modèle devra généralement fournir des implémentations des fonctions ` QAbstractItemModel::insertRows()` et ` QAbstractItemModel::setData()`.

Modèles proxy

Dans le cadre modèle/vue, les éléments de données fournis par un modèle unique peuvent être partagés par un nombre illimité de vues, et chacune d’entre elles peut éventuellement représenter les mêmes informations de manières complètement différentes. Les vues personnalisées et les délégués constituent des moyens efficaces de fournir des représentations radicalement différentes des mêmes données. Cependant, les applications doivent souvent fournir des vues conventionnelles sur des versions traitées de ces mêmes données, telles que des vues triées différemment d’une liste d’éléments.

Bien qu’il semble approprié d’effectuer les opérations de tri et de filtrage en tant que fonctions internes des vues, cette approche ne permet pas à plusieurs vues de partager les résultats de ces opérations potentiellement coûteuses. L’approche alternative, consistant à effectuer le tri au sein même du modèle, conduit à un problème similaire : chaque vue doit afficher des éléments de données organisés selon la dernière opération de traitement effectuée.

Pour résoudre ce problème, le cadre modèle/vue utilise des modèles proxy pour gérer les informations échangées entre les modèles individuels et les vues. Les modèles proxy sont des composants qui se comportent comme des modèles ordinaires du point de vue d’une vue et qui accèdent aux données des modèles sources pour le compte de cette vue. Les signaux et les slots utilisés par le framework modèle/vue garantissent que chaque vue est mise à jour de manière appropriée, quel que soit le nombre de modèles proxy placés entre elle et le modèle source.

Utilisation des modèles proxy

Les modèles proxy peuvent être insérés entre un modèle existant et un nombre quelconque de vues. Qt est fourni avec un modèle proxy standard, ` QSortFilterProxyModel`, qui est généralement instancié et utilisé directement, mais qui peut également être sous-classé pour offrir des comportements personnalisés de filtrage et de tri. La classe ` QSortFilterProxyModel ` peut être utilisée de la manière suivante :

QSortFilterProxyModel *filterModel = new QSortFilterProxyModel(parent);
filterModel->setSourceModel(stringListModel);

QListView *filteredView = new QListView;
filteredView->setModel(filterModel);

Comme les modèles proxy héritent de ` QAbstractItemModel`, ils peuvent être connectés à n’importe quel type de vue et partagés entre les vues. Ils peuvent également être utilisés pour traiter les informations obtenues à partir d’autres modèles proxy dans une configuration en pipeline.

La classe ` QSortFilterProxyModel ` est conçue pour être instanciée et utilisée directement dans les applications. Des modèles proxy plus spécialisés peuvent être créés en sous-classant cette classe et en implémentant les opérations de comparaison requises.

Personnalisation des modèles proxy

En général, le type de traitement utilisé dans un modèle proxy consiste à mapper chaque élément de données depuis son emplacement d’origine dans le modèle source vers un emplacement différent dans le modèle proxy. Dans certains modèles, certains éléments peuvent ne pas avoir d’emplacement correspondant dans le modèle proxy ; ces modèles sont des modèles proxy de filtrage. Les vues accèdent aux éléments à l’aide d’index de modèle fournis par le modèle proxy, et ceux-ci ne contiennent aucune information sur le modèle source ni sur l’emplacement des éléments d’origine dans ce modèle.

QSortFilterProxyModel Cela permet de filtrer les données d’un modèle source avant de les fournir aux vues, et permet également de fournir aux vues le contenu d’un modèle source sous forme de données pré-triées.

Modèles de filtrage personnalisés

La classe ` QSortFilterProxyModel ` fournit un modèle de filtrage assez polyvalent, qui peut être utilisé dans diverses situations courantes. Pour les utilisateurs avancés, ` QSortFilterProxyModel ` peut être sous-classée, offrant ainsi un mécanisme permettant de mettre en œuvre des filtres personnalisés.

Les sous-classes de ` QSortFilterProxyModel ` peuvent réimplémenter deux fonctions virtuelles qui sont appelées chaque fois qu’un index de modèle provenant du modèle proxy est demandé ou utilisé :

  • filterAcceptsColumn() sert à filtrer des colonnes spécifiques d’une partie du modèle source.
  • filterAcceptsRow() sert à filtrer des lignes spécifiques d’une partie du modèle source.

Les implémentations par défaut des fonctions ci-dessus dans ` QSortFilterProxyModel ` renvoient `true` pour garantir que tous les éléments sont transmis aux vues ; les réimplémentations de ces fonctions doivent renvoyer `false` pour filtrer des lignes et des colonnes individuelles.

Modèles de tri personnalisés

QSortFilterProxyModel utilisent la fonction std::stable_sort() pour établir des correspondances entre les éléments du modèle source et ceux du modèle proxy, ce qui permet d’exposer une hiérarchie triée d’éléments aux vues sans modifier la structure du modèle source. Pour fournir un comportement de tri personnalisé, réimplémentez la fonction lessThan() afin d’effectuer des comparaisons personnalisées.

Référence sur la sous-classification des modèles

Les sous-classes de modèle doivent fournir des implémentations de nombreuses fonctions virtuelles définies dans la classe de base ` QAbstractItemModel `. Le nombre de ces fonctions à implémenter dépend du type de modèle : s’il fournit aux vues une simple liste, un tableau ou une hiérarchie complexe d’éléments. Les modèles héritant de ` QAbstractListModel ` et ` QAbstractTableModel ` peuvent tirer parti des implémentations par défaut des fonctions fournies par ces classes. Les modèles qui exposent des éléments de données sous forme d’arborescences doivent fournir des implémentations pour bon nombre des fonctions virtuelles de ` QAbstractItemModel`.

Les fonctions qui doivent être implémentées dans une sous-classe de modèle peuvent être réparties en trois groupes :

  • Gestion des données des éléments : tous les modèles doivent implémenter des fonctions permettant aux vues et aux délégués d’interroger les dimensions du modèle, d’examiner les éléments et de récupérer des données.
  • Navigation et création d’index : les modèles hiérarchiques doivent fournir des fonctions que les vues peuvent appeler pour naviguer dans les structures arborescentes qu’ils exposent et obtenir des index de modèle pour les éléments.
  • Prise en charge du glisser-déposer et gestion des types MIME : les modèles héritent de fonctions qui contrôlent la manière dont les opérations de glisser-déposer internes et externes sont effectuées. Ces fonctions permettent de décrire les éléments de données en termes de types MIME compréhensibles par d’autres composants et applications.

Gestion des données des éléments

Les modèles peuvent offrir différents niveaux d’accès aux données qu’ils fournissent : il peut s’agir de simples composants en lecture seule ; certains modèles peuvent prendre en charge les opérations de redimensionnement, tandis que d’autres peuvent permettre la modification des éléments.

Accès en lecture seule

Pour fournir un accès en lecture seule aux données fournies par un modèle, les fonctions suivantes doivent être implémentées dans la sous-classe du modèle :

flags()Utilisée par d’autres composants pour obtenir des informations sur chaque élément fourni par le modèle. Dans de nombreux modèles, la combinaison d’indicateurs doit inclure Qt::ItemIsEnabled et Qt::ItemIsSelectable.
data()Utilisée pour fournir des données d’élément aux vues et aux délégués. En général, les modèles n’ont besoin de fournir des données que pour Qt::DisplayRole et les rôles utilisateur spécifiques à l’application, mais il est également recommandé de fournir des données pour Qt::ToolTipRole, Qt::AccessibleTextRole et Qt::AccessibleDescriptionRole. Consultez la documentation sur l’énumération Qt::ItemDataRole pour plus d’informations sur les types associés à chaque rôle.
headerData()Fournit aux vues les informations à afficher dans leurs en-têtes. Ces informations ne sont récupérées que par les vues capables d’afficher des informations d’en-tête.
rowCount()Fournit le nombre de lignes de données exposées par le modèle.

Ces quatre fonctions doivent être implémentées dans tous les types de modèles, y compris les modèles de liste (sous-classes de `QAbstractListModel `) et les modèles de table (sous-classes de `QAbstractTableModel `).

De plus, les fonctions suivantes doivent être implémentées dans les sous-classes directes de QAbstractTableModel et QAbstractItemModel:

columnCount()Fournit le nombre de colonnes de données exposées par le modèle. Les modèles de liste ne fournissent pas cette fonction car elle est déjà implémentée dans QAbstractListModel.

Éléments modifiables

Les modèles modifiables permettent de modifier des éléments de données et peuvent également fournir des fonctions permettant d’insérer et de supprimer des lignes et des colonnes. Pour activer la modification, les fonctions suivantes doivent être correctement implémentées :

flags()Doit renvoyer une combinaison appropriée d’indicateurs pour chaque élément. En particulier, la valeur renvoyée par cette fonction doit inclure Qt::ItemIsEditable en plus des valeurs appliquées aux éléments d’un modèle en lecture seule.
setData()Utilisée pour modifier l’élément de données associé à un index de modèle spécifié. Pour pouvoir accepter les entrées utilisateur, fournies par des éléments d’interface utilisateur, cette fonction doit traiter les données associées à Qt::EditRole. L’implémentation peut également accepter des données associées à de nombreux types de rôles différents spécifiés par Qt::ItemDataRole. Après avoir modifié l’élément de données, les modèles doivent émettre le signal dataChanged() pour informer les autres composants de la modification.
setHeaderData()Permet de modifier les informations d’en-tête horizontales et verticales. Après avoir modifié l’élément de données, les modèles doivent émettre le signal headerDataChanged() pour informer les autres composants de la modification.

Modèles redimensionnables

Tous les types de modèles peuvent prendre en charge l’insertion et la suppression de lignes. Les modèles de table et les modèles hiérarchiques peuvent également prendre en charge l’insertion et la suppression de colonnes. Il est important d’informer les autres composants des modifications apportées aux dimensions du modèle, tant avant qu’après leur survenue. Par conséquent, les fonctions suivantes peuvent être implémentées pour permettre le redimensionnement du modèle, mais les implémentations doivent s’assurer que les fonctions appropriées sont appelées pour notifier les vues et les délégués associés :

insertRows()Utilisée pour ajouter de nouvelles lignes et de nouveaux éléments de données à tous les types de modèles. Les implémentations doivent appeler ` beginInsertRows()` avant d’insérer de nouvelles lignes dans toute structure de données sous-jacente, puis appeler ` endInsertRows()` immédiatement après.
removeRows()Permet de supprimer des lignes et les éléments de données qu’elles contiennent de tous les types de modèles. Les implémentations doivent appeler beginRemoveRows() avant de supprimer des lignes de toute structure de données sous-jacente, puis appeler endRemoveRows() immédiatement après.
insertColumns()Permet d’ajouter de nouvelles colonnes et de nouveaux éléments de données aux modèles de table et aux modèles hiérarchiques. Les implémentations doivent appeler ` beginInsertColumns()` avant d’ insérer de nouvelles colonnes dans toute structure de données sous-jacente, puis appeler ` endInsertColumns()` immédiatement après.
removeColumns()Permet de supprimer des colonnes et les éléments de données qu’elles contiennent des modèles de table et des modèles hiérarchiques. Les implémentations doivent appeler beginRemoveColumns() avant de supprimer des colonnes de toute structure de données sous-jacente, puis appeler endRemoveColumns() immédiatement après.

En règle générale, ces fonctions doivent renvoyer « true » si l’opération a abouti. Cependant, il peut arriver que l’opération n’ait abouti que partiellement ; par exemple, si le nombre de lignes insérées est inférieur au nombre spécifié. Dans de tels cas, le modèle doit renvoyer « false » pour signaler l’échec, afin de permettre aux composants associés de gérer la situation.

Les signaux émis par les fonctions appelées dans les implémentations de l’API de redimensionnement permettent aux composants associés d’intervenir avant que des données ne deviennent indisponibles. L’encapsulation des opérations d’insertion et de suppression à l’aide des fonctions `begin` et `end` permet également au modèle de gérer correctement l’ persistent model indexes.

Normalement, les fonctions `begin` et `end` sont capables d’informer les autres composants des modifications apportées à la structure sous-jacente du modèle. Pour des modifications plus complexes de la structure du modèle, impliquant éventuellement une réorganisation interne, un tri des données ou tout autre changement structurel, il est nécessaire d’effectuer la séquence suivante :

Cette séquence peut être utilisée pour toute mise à jour structurelle à la place des méthodes protégées, plus générales et plus pratiques. Par exemple, si un modèle de deux millions de lignes doit voir toutes ses lignes impaires supprimées, cela correspond à un million de plages non contiguës d’un élément chacune. Il serait possible d’utiliser beginRemoveRows et endRemoveRows un million de fois, mais cela serait manifestement inefficace. Au lieu de cela, cette opération peut être signalée comme un seul changement de structure qui met à jour tous les index persistants nécessaires en une seule fois.

Remplissage différé des données du modèle

Le remplissage différé des données du modèle permet en effet de reporter les requêtes d’informations sur le modèle jusqu’à ce que les vues en aient réellement besoin.

Certains modèles doivent récupérer des données auprès de sources distantes, ou effectuer des opérations chronophages pour obtenir des informations sur l’organisation des données. Étant donné que les vues demandent généralement autant d’informations que possible afin d’afficher avec précision les données du modèle, il peut être utile de limiter la quantité d’informations qui leur est renvoyée afin de réduire les requêtes de données de suivi inutiles.

Dans les modèles hiérarchiques où le calcul du nombre d’enfants d’un élément donné est une opération coûteuse, il est utile de s’assurer que l’implémentation de la méthode rowCount() du modèle n’est appelée qu’en cas de nécessité. Dans de tels cas, la fonction ` hasChildren()` peut être réimplémentée afin d’offrir aux vues un moyen peu coûteux de vérifier la présence d’enfants et, dans le cas de ` QTreeView`, d’afficher la décoration appropriée pour leur élément parent.

Que la réimplémentation de ` hasChildren()` renvoie ` true ` ou ` false`, il n’est pas forcément nécessaire que la vue appelle ` rowCount()` pour connaître le nombre d’enfants présents. Par exemple, ` QTreeView ` n’a pas besoin de savoir combien il y a d’enfants si l’élément parent n’a pas été développé pour les afficher.

Si l’on sait que de nombreux éléments auront des sous-éléments, la réimplémentation de ` hasChildren()` pour renvoyer sans condition ` true ` est parfois une approche utile. Cela garantit que chaque élément pourra être examiné ultérieurement pour vérifier la présence de sous-éléments, tout en rendant le remplissage initial des données du modèle aussi rapide que possible. Le seul inconvénient est que les éléments sans enfants peuvent s’afficher de manière incorrecte dans certaines vues jusqu’à ce que l’utilisateur tente de consulter les éléments enfants inexistants.

Les modèles hiérarchiques doivent fournir des fonctions que les vues peuvent appeler pour naviguer dans les structures arborescentes qu’ils exposent, et obtenir les index du modèle pour les éléments.

Éléments parents et enfants

Étant donné que la structure exposée aux vues est déterminée par la structure de données sous-jacente, il appartient à chaque sous-classe de modèle de créer ses propres index de modèle en fournissant des implémentations des fonctions suivantes :

index()Compte tenu d’un index de modèle pour un élément parent, cette fonction permet aux vues et aux délégués d’accéder aux enfants de cet élément. Si aucun élément enfant valide — correspondant à la ligne, à la colonne et à l’index de modèle parent spécifiés — ne peut être trouvé, la fonction doit renvoyer QModelIndex(), qui est un index de modèle non valide.
parent()Fournit un index de modèle correspondant au parent de n’importe quel élément enfant donné. Si l’index de modèle spécifié correspond à un élément de niveau supérieur dans le modèle, ou s’il n’existe aucun élément parent valide dans le modèle, la fonction doit renvoyer un index de modèle invalide, créé à l’aide du constructeur vide de QModelIndex().

Les deux fonctions ci-dessus utilisent la fonction d’usine ` createIndex()` pour générer des index destinés à être utilisés par d’autres composants. Il est courant que les modèles fournissent un identifiant unique à cette fonction afin de garantir que l’index de modèle puisse être réassocié ultérieurement à l’élément correspondant.

Prise en charge du glisser-déposer et gestion des types MIME

Les classes modèle/vue prennent en charge les opérations de glisser-déposer, offrant un comportement par défaut suffisant pour de nombreuses applications. Cependant, il est également possible de personnaliser la manière dont les éléments sont encodés lors des opérations de glisser-déposer, s’ils sont copiés ou déplacés par défaut, et la façon dont ils sont insérés dans des modèles existants.

De plus, les classes de vues de commodité implémentent un comportement spécialisé qui devrait correspondre étroitement à celui attendu par les développeurs existants. La section « Vues de commodité » fournit un aperçu de ce comportement.

Données MIME

Par défaut, les modèles et vues intégrés utilisent un type MIME interne (application/x-qabstractitemmodeldatalist) pour transmettre des informations sur les index des modèles. Celui-ci spécifie les données d’une liste d’éléments, contenant les numéros de ligne et de colonne de chaque élément, ainsi que des informations sur les rôles pris en charge par chaque élément.

Les données encodées à l'aide de ce type MIME peuvent être obtenues en appelant la méthode QAbstractItemModel::mimeData() avec un objet QModelIndexList contenant les éléments à sérialiser.

Lors de la mise en œuvre de la prise en charge du glisser-déposer dans un modèle personnalisé, il est possible d'exporter des éléments de données dans des formats spécialisés en réimplémentant la fonction suivante :

mimeData()Cette fonction peut être réimplémentée pour renvoyer des données dans des formats autres que le type MIME interne par défaut d'application/x-qabstractitemmodeldatalist.

Les sous-classes peuvent récupérer l'objet QMimeData par défaut à partir de la classe de base et y ajouter des données dans des formats supplémentaires.

Pour de nombreux modèles, il est utile de fournir le contenu des éléments dans un format courant représenté par des types MIME tels que text/plain et image/png. Notez que des images, des couleurs et des documents HTML peuvent facilement être ajoutés à un objet QMimeData à l’aide des fonctions QMimeData::setImageData(), QMimeData::setColorData() et QMimeData::setHtml().

Prise en charge des données déposées

Lorsqu’une opération de glisser-déposer est effectuée sur une vue, le modèle sous-jacent est interrogé afin de déterminer les types d’opérations qu’il prend en charge et les types MIME qu’il peut accepter. Ces informations sont fournies par les fonctions QAbstractItemModel::supportedDropActions() et QAbstractItemModel::mimeTypes(). Les modèles qui ne redéfinissent pas les implémentations fournies par QAbstractItemModel prennent en charge les opérations de copie et le type MIME interne par défaut pour les éléments.

Lorsque des données d’élément sérialisées sont déposées sur une vue, elles sont insérées dans le modèle actuel à l’aide de son implémentation de QAbstractItemModel::dropMimeData(). L’implémentation par défaut de cette fonction n’écrasera jamais aucune donnée du modèle ; elle tente plutôt d’insérer les éléments de données soit en tant qu’éléments frères d’un élément, soit en tant qu’enfants de cet élément.

Pour tirer parti de l’implémentation par défaut de ` QAbstractItemModel` pour le type MIME intégré, les nouveaux modèles doivent fournir des réimplémentations des fonctions suivantes :

insertRows()Ces fonctions permettent au modèle d’insérer automatiquement de nouvelles données en utilisant l’implémentation existante fournie par QAbstractItemModel::dropMimeData().
insertColumns()
setData()Permet de remplir les nouvelles lignes et colonnes avec des éléments.
setItemData()Cette fonction offre une prise en charge plus efficace pour le remplissage de nouveaux éléments.

Pour accepter d'autres types de données, ces fonctions doivent être réimplémentées :

supportedDropActions()Permet de renvoyer une combinaison de ` drop actions`, indiquant les types d'opérations de glisser-déposer pris en charge par le modèle.
mimeTypes()Permet de renvoyer une liste des types MIME pouvant être décodés et gérés par le modèle. En règle générale, les types MIME pris en charge en entrée par le modèle sont les mêmes que ceux qu’il peut utiliser lors de l’encodage des données destinées à des composants externes.
dropMimeData()Effectue le décodage proprement dit des données transférées par des opérations de glisser-déposer, détermine à quel endroit du modèle elles seront placées et insère de nouvelles lignes et colonnes si nécessaire. La manière dont cette fonction est implémentée dans les sous-classes dépend des exigences liées aux données exposées par chaque modèle.

Si l’implémentation de la fonction ` dropMimeData()` modifie les dimensions d’un modèle en insérant ou en supprimant des lignes ou des colonnes, ou si des éléments de données sont modifiés, il faut veiller à ce que tous les signaux pertinents soient émis. Il peut être utile d’appeler simplement les réimplémentations d’autres fonctions dans la sous-classe, telles que ` setData()`, ` insertRows()` et ` insertColumns()`, afin de garantir la cohérence du comportement du modèle.

Afin de garantir le bon fonctionnement des opérations de glisser-déposer, il est important de réimplémenter les fonctions suivantes qui suppriment des données du modèle :

Pour plus d’informations sur le glisser-déposer avec les vues d’éléments, consultez la section Utilisation du glisser-déposer avec les vues d’éléments.

Vues de commodité

Les vues pratiques (QListWidget, QTableWidget et QTreeWidget) remplacent la fonctionnalité de glisser-déposer par défaut afin d’offrir un comportement moins flexible, mais plus naturel, adapté à de nombreuses applications. Par exemple, comme il est plus courant de déposer des données dans les cellules d’un tableau de données ( QTableWidget), en remplaçant le contenu existant par les données transférées, le modèle sous-jacent définira les données des éléments cibles plutôt que d’insérer de nouvelles lignes et colonnes dans le modèle. Pour plus d’informations sur le glisser-déposer dans les vues pratiques, consultez la section « Utilisation du glisser-déposer avec les vues d’éléments ».

Optimisation des performances pour les volumes importants de données

La fonction ` canFetchMore()` vérifie si le parent dispose de données supplémentaires et renvoie ` true ` ou `false` en conséquence. La fonction fetchMore() récupère les données en fonction du parent spécifié. Ces deux fonctions peuvent être combinées, par exemple dans une requête de base de données impliquant des données incrémentielles pour remplir une vue de type « QAbstractItemModel ». Nous réimplémentons canFetchMore() pour indiquer s’il reste des données à récupérer et fetchMore() pour remplir le modèle selon les besoins.

Un autre exemple serait celui des modèles arborescents remplis dynamiquement, où nous réimplémentons fetchMore() lorsqu’une branche du modèle arborescent est développée.

Si votre réimplémentation de fetchMore() ajoute des lignes au modèle, vous devez appeler beginInsertRows() et endInsertRows(). De plus, canFetchMore() et fetchMore() doivent tous deux être réimplémentés, car leur implémentation par défaut renvoie « false » et n’effectue aucune action.

Les classes Modèle/Vue

Ces classes utilisent le modèle de conception « modèle/vue », dans lequel les données sous-jacentes (dans le modèle) sont dissociées de la manière dont elles sont présentées et manipulées par l’utilisateur (dans la vue).

QAbstractItemDelegate

Utilisées pour afficher et modifier les éléments de données d’un modèle

QAbstractItemModel

L'interface abstraite pour les classes de modèle d'élément

QAbstractItemView

Les fonctionnalités de base pour les classes de vue d'élément

QAbstractListModel

Modèle abstrait pouvant être sous-classé pour créer des modèles de liste unidimensionnels

QAbstractProxyModel

Classe de base pour les modèles d'éléments proxy permettant d'effectuer des opérations de tri, de filtrage ou d'autres tâches de traitement des données

QAbstractTableModel

Modèle abstrait pouvant être sous-classé pour créer des modèles de table

QColumnView

Implémentation modèle/vue d’une vue en colonnes

QConcatenateTablesProxyModel

Proxy de plusieurs modèles source, en concaténant leurs lignes

QDataWidgetMapper

Mappage entre une section d’un modèle de données et des widgets

QFileSystemModel

Modèle de données pour le système de fichiers local

QHeaderView

Ligne d’en-tête ou colonne d’en-tête pour les vues d’éléments

QIdentityProxyModel

Reflète son modèle source sans modification

QItemDelegate

Fonctionnalités d'affichage et d'édition pour les éléments de données d'un modèle

QItemEditorCreator

Permet de créer des bases de créateurs d'éditeurs d'éléments sans sous-classer QItemEditorCreatorBase

QItemEditorCreatorBase

Classe de base abstraite qui doit être sous-classée lors de l’implémentation de nouveaux créateurs d’éditeurs d’éléments

QItemEditorFactory

Widgets permettant de modifier les données des éléments dans les vues et les délégués

QItemSelection

Gère les informations relatives aux éléments sélectionnés dans un modèle

QItemSelectionModel

Assure le suivi des éléments sélectionnés dans une vue

QItemSelectionRange

Gère les informations relatives à une plage d’éléments sélectionnés dans un modèle

QListView

Vue sous forme de liste ou d’icônes d’un modèle

QListWidget

Widget de liste basé sur les éléments

QListWidgetItem

Élément à utiliser avec la classe de vue d'élément QListWidget

QModelIndex

Permet de localiser des données dans un modèle de données

QModelRoleData

Contient un rôle et les données associées à ce rôle

QModelRoleDataSpan

S'étend sur des objets QModelRoleData

QPersistentModelIndex

Utilisé pour localiser des données dans un modèle de données

QRangeModel

Implémente QAbstractItemModel pour toute plage C++

QRangeModel::ItemAccess

Le modèle fournit un point de personnalisation permettant de contrôler la manière dont QRangeModel accède aux données de rôle des éléments individuels

QRangeModel::RowOptions

Le modèle fournit un point de personnalisation permettant de contrôler la manière dont QRangeModel représente les types utilisés comme lignes

QRangeModelAdapter

Accès conforme à QAbstractItemModel à n'importe quelle plage C++

QRangeModelAdapter::ColumnIterator

Fournit un itérateur non-const de type STL sur les colonnes d’une ligne de modèle

QRangeModelAdapter::ConstColumnIterator

Fournit un itérateur non-const de type STL sur les colonnes d'une ligne du modèle

QRangeModelAdapter::ConstRowIterator

Fournit un itérateur const de type STL sur les lignes d'un modèle

QRangeModelAdapter::ConstRowReference

Conteneur de référence autour d’une ligne const dans un QRangeModel

QRangeModelAdapter::DataReference

Enveloppe de référence autour d’un élément d’un QRangeModel

QRangeModelAdapter::RowIterator

Fournit un itérateur non constant de type STL sur les lignes d'un modèle

QRangeModelAdapter::RowReference

Enveloppe de référence autour d’une ligne d’un QRangeModel

QRangeModelAdapter::RowReferenceBase

API commune pour RowReference et ConstRowReference

QSortFilterProxyModel

Prise en charge du tri et du filtrage des données transmises entre un autre modèle et une vue

QStandardItem

Élément destiné à être utilisé avec la classe QStandardItemModel

QStandardItemEditorCreator

Possibilité d’enregistrer des widgets sans avoir à créer de sous-classe de QItemEditorCreatorBase

QStandardItemModel

Modèle générique pour le stockage de données personnalisées

QStringListModel

Modèle fournissant des chaînes de caractères aux vues

QStyledItemDelegate

Fonctionnalités d'affichage et d'édition des éléments de données provenant d'un modèle

QTableView

Implémentation par défaut du modèle/de la vue d’une vue de table

QTableWidget

Vue de tableau basée sur des éléments avec un modèle par défaut

QTableWidgetItem

Élément destiné à être utilisé avec la classe QTableWidget

QTableWidgetSelectionRange

Méthode permettant d'interagir avec la sélection dans un modèle sans utiliser d'index de modèle ni de modèle de sélection

QTreeView

Implémentation par défaut du modèle/de la vue d'une arborescence

QTreeWidget

Vue arborescente utilisant un modèle d'arborescence prédéfini

QTreeWidgetItem

Élément destiné à être utilisé avec la classe de commodité QTreeWidget

QTreeWidgetItemIterator

Méthode permettant de parcourir les éléments d'une instance de QTreeWidget

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