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 donne aux développeurs une plus grande flexibilité pour personnaliser la présentation des éléments, et fournit une interface de modèle standard pour permettre à un large éventail de sources de données d'être utilisées avec les vues d'éléments existantes. Dans ce document, nous présentons brièvement le paradigme modèle/vue, nous exposons les concepts impliqués et nous décrivons l'architecture du système de vues d'éléments. Chacun des composants de l'architecture est expliqué et des exemples sont donnés pour montrer comment utiliser les classes fournies.
L'architecture modèle/vue
Le modèle-vue-contrôleur (MVC) est un modèle de conception issu de Smalltalk qui est souvent utilisé lors de la construction d'interfaces utilisateur. Dans Design Patterns, Gamma et al. écrivent :
MVC consiste en trois types d'objets. Le modèle est l'objet de l'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 MVC, les conceptions d'interface utilisateur avaient tendance à regrouper ces objets. MVC les dissocie afin d'accroître la flexibilité et la réutilisation.
Si les objets vue et contrôleur sont combinés, on obtient l'architecture modèle/vue. Cette architecture 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 elle fournit un cadre plus simple basé sur les mêmes principes. Cette séparation permet d'afficher les mêmes données dans plusieurs vues différentes et de mettre en œuvre 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'avoir un délégué dans ce cadre est qu'il permet de personnaliser la façon dont les éléments de données sont rendus et édités.
| L'architecture modèle/vue Le modèle communique avec une source de données, fournissant une interface pour les autres composants de l'architecture. La nature de la communication dépend du type de source de données et de la manière dont le modèle est mis en œuvre. La vue obtient des index de modèle à partir du modèle ; il s'agit de références à des éléments de données. En fournissant des index de modèle au modèle, la vue peut extraire des éléments de données de la source de données. Dans les vues standard, un délégué rend les éléments de données. Lorsqu'un élément est modifié, le délégué communique directement avec le modèle en utilisant les index du modèle. |
En règle générale, les classes de modèles et de vues peuvent être réparties dans les 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 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 du modèle informent la vue des modifications apportées aux données détenues par 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 du délégué sont utilisés pendant l'édition pour informer le modèle et la vue de 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 un autre composant de l'application.
Les concepts de base entourant les modèles sont présentés dans la section sur les classes de modèles.
QAbstractItemModel fournit une interface avec les données qui est suffisamment flexible pour gérer les vues qui représentent les données sous forme de tableaux, de listes et d'arbres. Toutefois, lors de la mise en œuvre de nouveaux modèles pour les structures de données de type liste et tableau, les classes QAbstractListModel et QAbstractTableModel constituent de meilleurs points de départ, car elles fournissent des implémentations par défaut appropriées de fonctions communes. Chacune de ces classes peut être sous-classée pour fournir des modèles qui prennent en charge des types de listes et de tableaux spécialisés.
Le processus de sous-classification des modèles est abordé dans la section sur la création de nouveaux modèles.
Qt fournit quelques modèles prêts à l'emploi qui peuvent être utilisés pour gérer des éléments de données :
- QStringListModel est utilisé pour stocker une simple liste d'éléments 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 d'archivage local.
- QSqlQueryModelLes modèles de vues, QSqlTableModel et QSqlRelationalTableModel sont utilisés pour accéder aux bases de données à l'aide des conventions modèle/vue.
Si ces modèles standard ne répondent pas à vos besoins, vous pouvez sous-classer QAbstractItemModel, QAbstractListModel ou QAbstractTableModel pour créer vos propres modèles personnalisés.
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 dans un tableau et QTreeView affiche les éléments de données d'un modèle dans une 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 examinées dans la section sur les classes de vues.
Délégués
QAbstractItemDelegate est la classe de base abstraite pour les délégués dans le cadre modèle/vue. L'implémentation par défaut des délégués est fournie par QStyledItemDelegate, et elle est utilisée comme délégué par défaut par les vues standard de Qt. Cependant, QStyledItemDelegate et QItemDelegate sont des alternatives indépendantes pour peindre et fournir des éditeurs pour les éléments dans les vues. La différence entre elles est que QStyledItemDelegate utilise le style actuel pour peindre 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 sur les classes de délégués.
Le tri
Il existe deux façons d'aborder le tri dans l'architecture modèle/vue ; l'approche à choisir 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(), QTableView et QTreeView fournissent une API qui vous permet de trier les données de votre modèle de manière programmatique. En outre, 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.
L'approche alternative, si votre modèle n'a pas l'interface requise ou si vous voulez utiliser une vue en liste pour présenter vos données, est d'utiliser un modèle proxy pour transformer la structure de votre modèle avant de présenter les données dans la vue. Ce point est abordé en détail dans la section sur les modèles mandataires.
Classes de commodité
Un certain nombre de classes de commodité sont dérivées des classes de vue standard pour le bénéfice des applications qui s'appuient sur les classes de vue et de table basées sur les éléments de Qt. Elles ne sont pas destinées à être sous-classées.
Parmi les exemples de ces classes, citons 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'utiliser une approche modèle/vue pour traiter les données dans les vues d'éléments, à moins que vous n'ayez fortement besoin d'un ensemble de classes basées sur les éléments.
Si vous souhaitez bénéficier 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/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 différentes structures de données nécessaires aux vues de listes, de tableaux et d'arbres. Ce modèle contient également les éléments de données. QFileSystemModel est un modèle qui contient des informations sur le contenu d'un répertoire. Par conséquent, il ne contient pas lui-même d'éléments de données, mais représente simplement les fichiers et les répertoires du système d'archivage local.
QFileSystemModel Ce modèle est prêt à l'emploi et peut être facilement configuré pour utiliser des données existantes. À l'aide de ce modèle, nous pouvons montrer comment configurer un modèle pour l'utiliser avec des vues prêtes à l'emploi et explorer comment manipuler des 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 appropriées à utiliser 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 dans une vue en liste. Les vues partagent la sélection de l'utilisateur, de sorte que les éléments sélectionnés sont mis en évidence dans les deux vues.

Nous configurons un site QFileSystemModel pour qu'il soit prêt à l'emploi et nous créons quelques vues pour afficher le contenu d'un répertoire. Ceci montre la manière la plus simple d'utiliser un modèle. La construction et l'utilisation du modèle s'effectuent à partir 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 certain système de fichiers. L'appel à setRootPath() indique au modèle le lecteur du système de fichiers à exposer 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 qu'une vue affiche les éléments du modèle, il suffit d'appeler sa fonction setModel() avec le modèle de répertoire comme argument. Nous filtrons les données fournies par le modèle en appelant la fonction setRootIndex() sur chaque vue, en passant un index de modèle approprié à partir du modèle du système de fichiers pour le répertoire actuel.
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èle.
Le reste de la fonction se contente d'afficher les vues dans un widget de séparation 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 les sélections d'éléments. Ce sujet est traité plus en détail dans la section traitant de la gestion des sélections dans les vues d'éléments.
Classes de modèles
Avant d'examiner la manière dont les sélections sont gérées, il peut être utile d'examiner les concepts utilisés dans le cadre 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. Dans Qt XML, l'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 tableaux d'éléments. Les vues utilisent cette convention pour accéder aux éléments de données du modèle, mais elles ne sont pas limitées dans la manière dont elles présentent ces informations à l'utilisateur.
Les modèles informent également les vues attachées des modifications apportées aux données par le biais du mécanisme des signaux et des créneaux.
Cette section décrit certains concepts de base qui sont essentiels à 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
Pour 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 information pouvant être obtenue via un modèle est représentée par un index de modèle. Les vues et les délégués utilisent ces index pour demander des éléments de données à afficher.
Par conséquent, seul le modèle doit savoir comment obtenir des 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 lorsque l'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 à un élément d'information est nécessaire, un index de modèle persistant doit être créé. Celui-ci fournit une référence aux informations que le modèle tient à jour. Les index de modèle temporaires sont fournis par la classe QModelIndex, et 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 basique, un modèle est accessible comme 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 de 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 un élément donné en spécifiant ses numéros de ligne et de colonne au modèle, et nous recevons un index qui représente l'élément :
QModelIndex index = model->index(row, column /*...*/);
Les modèles qui fournissent des interfaces à des structures de données simples, à un seul niveau, comme les listes et les tableaux, n'ont pas besoin de fournir d'autres informations, mais, comme l'indique le code ci-dessus, nous devons fournir plus d'informations pour obtenir un index de modèle.
| Lignes et colonnes Le diagramme montre 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 pertinents 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 premier niveau d'un modèle sont toujours référencés en spécifiant |
Parents des éléments
L'interface de type tableau avec les données d'éléments fournie par les modèles est idéale lorsque l'on utilise des données dans une vue de tableau ou de liste ; le système de numérotation des lignes et des colonnes correspond exactement à la manière dont les vues affichent les éléments. Toutefois, les structures telles que les vues arborescentes exigent que le modèle expose une interface plus souple aux éléments qu'elles contiennent. Par conséquent, chaque élément peut également être le parent d'une autre table d'éléments, de la même manière qu'un élément de premier niveau dans une vue arborescente peut contenir une autre liste d'éléments.
Lorsque nous demandons un index pour un élément du modèle, nous devons fournir des informations sur le parent de l'élément. En dehors du modèle, le seul moyen de faire référence à un élément est d'utiliser l'index du modèle, de sorte que l'index du modèle parent doit également être fourni :
QModelIndex index = model->index(row, column, parent);
| Parents, lignes et colonnes Le diagramme montre une représentation d'un modèle arborescent dans lequel chaque élément est désigné 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 frères et sœurs de premier niveau dans le modèle : QModelIndex indexA = model->index(0, 0, QModelIndex()); QModelIndex indexC = model->index(2, 1, QModelIndex()); L'élément "A" a un certain nombre d'enfants. Un index de modèle pour l'élément "B" est obtenu avec le code suivant : QModelIndex indexB = model->index(1, 0, indexA); |
Rôles des éléments
Les éléments d'un modèle peuvent jouer différents rôles pour d'autres composants, ce qui permet de fournir différents types de données dans différentes situations. Par exemple, Qt::DisplayRole est utilisé pour accéder à une chaîne de caractères qui peut être affichée sous forme de texte dans une vue. En général, les éléments contiennent des données pour un certain nombre de 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 passant l'index du modèle correspondant à l'élément et en spécifiant un rôle pour obtenir le type de données que nous voulons :
QVariant value = model->data(index, role);
![]() | Rôles des éléments Le rôle indique au modèle le type de données auquel il est 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 couvre plus en détail certaines utilisations spécifiques des rôles. |
Les utilisations les plus courantes des données d'éléments sont couvertes par les rôles standard définis dans Qt::ItemDataRole. En fournissant des données d'éléments 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 ont la liberté d'interpréter ou d'ignorer ces informations selon les besoins. Il est également possible de définir des rôles supplémentaires pour des applications spécifiques.
Résumé
- Les index de modèle donnent aux vues et aux délégués des informations sur l'emplacement des éléments fournis par les modèles d'une manière indépendante de toute structure de données sous-jacente.
- Les éléments sont désignés par leurs numéros de ligne et de colonne, ainsi que par l'index de 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 lorsqu'un index est demandé à l'aide de 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 lorsqu'un index est demandé à l'aide de index(), l'index renvoyé fait référence à un élément de niveau supérieur dans le modèle.
- Le site role fait la distinction entre les différents types de données associées à un élément.
Utilisation des index de modèle
Pour démontrer comment les données peuvent être extraites d'un modèle à l'aide d'index de modèle, nous configurons un site QFileSystemModel sans vue et affichons les noms des fichiers et des répertoires dans un widget. Bien que cela ne soit pas une façon normale d'utiliser un modèle, cela démontre les conventions utilisées par les modèles lors de l'utilisation des index de modèle.
QFileSystemModel est asynchrone afin de minimiser l'utilisation des ressources du système. Nous devons en tenir compte lorsque nous traitons 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 mettre en place un QFileSystemModel par défaut. Nous connectons son signal directoryLoaded(QString) à un lambda, dans lequel nous obtiendrons un index parent pour le répertoire en utilisant une implémentation spécifique de index() fournie par ce modèle.
Dans la lambda, nous déterminons le nombre de lignes du modèle à l'aide de la fonction rowCount().
Pour des raisons de simplicité, nous ne nous intéressons qu'aux éléments de la première colonne du modèle. Nous examinons chaque ligne à tour de rôle, en obtenant un index de modèle pour le premier élément de chaque ligne, et 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 que nous voulons. 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 le site DisplayRole pour obtenir les données de l'élément sous la forme d'une chaîne de caractères.
Enfin, nous définissons le chemin racine de QFileSystemModel pour qu'il commence à charger les données et déclenche la lambda.
L'exemple ci-dessus démontre les principes de base utilisés pour récupérer les données d'un modèle :
- Les dimensions d'un modèle peuvent être trouvées en utilisant rowCount() et columnCount(). Ces fonctions requièrent généralement la spécification d'un index de modèle parent.
- Les index de modèle sont utilisés pour accéder aux éléments du modèle. La ligne, la colonne et l'index 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 à l'aide de
QModelIndex(). - Les éléments contiennent des données pour différents rôles. Pour obtenir les données relatives à 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 démontrons cela en créant un modèle pratique et prêt à l'emploi pour contenir des listes de chaînes de caractères.
Voir les classes
Concepts
Dans l'architecture modèle/vue, la vue obtient des éléments de données du modèle et les présente à l'utilisateur. La manière dont les données sont présentées ne doit pas nécessairement 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 du contenu et de la présentation est réalisée par l'utilisation d'une interface de modèle standard fournie par QAbstractItemModel, d'une interface de vue standard fournie par QAbstractItemView, et par l'utilisation d'index de modèle qui représentent les éléments de données d'une manière générale. Les vues gèrent généralement la présentation générale des données obtenues à partir des modèles. Elles peuvent rendre elles-mêmes des éléments de données individuels ou utiliser des délégués pour gérer à la fois les fonctions de rendu et d'édition.
Outre la présentation des données, les vues gèrent la navigation entre les éléments et certains aspects de la sélection des éléments. Les vues mettent également en œuvre les fonctionnalités de base de l'interface utilisateur, telles que les menus contextuels et le glisser-déposer. Une vue peut fournir des fonctions d'édition par défaut pour les éléments, ou travailler avec un délégué pour fournir un éditeur personnalisé.
Une vue peut être construite sans modèle, mais un modèle doit être fourni avant qu'elle puisse afficher des informations utiles. Les vues gardent la trace des éléments sélectionnés par l'utilisateur grâce à des 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 ainsi que 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. Ils récupèrent les données du modèle à l'aide de la fonction QAbstractItemModel::headerData() et affichent généralement les informations de l'en-tête sous la forme d'une étiquette. De nouveaux en-têtes peuvent être sous-classés à partir de la classe QHeaderView afin de fournir des étiquettes plus spécialisées pour les vues.
Utilisation d'une vue existante
Qt XML fournit trois classes de vues prêtes à l'emploi qui présentent les données des modèles d'une manière familière à la plupart des utilisateurs. QListView peut afficher les éléments d'un modèle sous la forme d'une simple liste ou d'une icône classique. QTreeView affiche les éléments d'un modèle sous la forme d'une hiérarchie de listes, ce qui permet de représenter des structures profondément imbriquées de manière compacte. QTableView présente les éléments d'un modèle sous la forme d'un tableau, à l'instar de la présentation d'un tableur.

Le comportement par défaut des vues standard présentées ci-dessus devrait suffire à la plupart des applications. Elles offrent des possibilité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 que nous avons créé comme modèle d'exemple, nous le configurons avec des données et 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 la fonction StringListModel est déclarée en tant que QAbstractItemModel, ce qui nous permet d'utiliser l'interface abstraite du modèle et garantit que le code fonctionne toujours, même si nous remplaçons le modèle de liste de chaînes par un modèle différent.
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 :
La vue est affichée normalement :
view->show(); return app.exec(); }
La vue rend le contenu d'un modèle, en accédant aux données via l'interface du modèle. 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.

L'image ci-dessus montre comment une page QListView représente les données du modèle de liste de chaînes. Comme le modèle est 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 d'un même modèle, il suffit de définir le même modèle pour chaque vue. Dans le code suivant, nous créons deux vues de 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 d'emplacements dans l'architecture modèle/vue signifie que les modifications apportées au modèle peuvent être propagées à toutes les vues attachées, ce qui garantit que nous pouvons toujours accéder aux mêmes données, quelle que soit la vue utilisée.

L'image ci-dessus montre deux vues différentes du même modèle, chacune contenant un certain nombre d'éléments sélectionnés. Bien que les données du modèle soient affichées de manière cohérente d'une vue à l'autre, chaque vue conserve son propre modèle de sélection interne. Cela peut être utile dans certaines situations, mais pour de nombreuses applications, un modèle de sélection partagé est souhaitable.
Traitement des sélections d'éléments
Le mécanisme de gestion des sélections d'éléments dans les vues est fourni par la classe QItemSelectionModel. Toutes les vues standard construisent par défaut leurs propres modèles de sélection et interagissent avec eux de la manière habituelle. Le modèle de sélection utilisé par une vue peut être obtenu grâce à 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 nous voulons fournir plusieurs vues cohérentes sur les mêmes données de modèle.
En général, à moins de sous-classer un modèle ou une vue, il n'est pas nécessaire de manipuler directement le contenu des sélections. Toutefois, il est possible d'accéder à l'interface du modèle de sélection, si nécessaire, ce qui est étudié dans la section Manipulation 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 vues fournissent leurs propres modèles de sélection par défaut, lorsque nous utilisons plus d'une vue sur le même modèle, il est souvent souhaitable que les données du modèle et la sélection de l'utilisateur soient affichées de manière cohérente dans toutes les vues. Puisque les classes de vues permettent de remplacer leurs modèles de sélection internes, nous pouvons obtenir une sélection unifiée entre les vues avec la ligne suivante :
secondTableView->setSelectionModel(firstTableView->selectionModel());
La deuxième vue reçoit le modèle de sélection de la première vue. Les deux vues fonctionnent désormais sur le même modèle de sélection, ce qui permet de synchroniser les données et les éléments sélectionnés.

Dans l'exemple ci-dessus, deux vues du même type ont été utilisées pour afficher les données du même modèle. Toutefois, si deux types de vues différents étaient utilisés, les éléments sélectionnés pourraient être représentés très différemment dans chaque vue ; par exemple, une sélection contiguë dans une vue de tableau peut être représentée comme un ensemble fragmenté d'éléments mis en évidence dans une vue d'arbre.
Classes de délégués
Concepts
Contrairement au modèle Modèle-Vue-Contrôleur, la conception modèle/vue n'inclut pas de composant complètement séparé pour gérer l'interaction avec l'utilisateur. En général, la vue est responsable de la présentation des données du modèle à l'utilisateur et du traitement de ses données. Pour permettre une certaine flexibilité dans la manière dont ces données sont obtenues, l'interaction est effectuée par des délégués. Ces composants fournissent des capacités d'entrée et sont également responsables du rendu des éléments individuels dans certaines vues. L'interface standard de contrôle des délégués est définie dans la classe QAbstractItemDelegate.
Les délégués sont censés être capables de rendre leur contenu eux-mêmes en implémentant les fonctions paint() et sizeHint(). Toutefois, les délégués simples basés sur des widgets peuvent sous-classer QStyledItemDelegate au lieu de QAbstractItemDelegate et profiter des implémentations par défaut de ces fonctions.
Les éditeurs pour les délégués peuvent être mis en œuvre 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 XML utilisent des instances de QStyledItemDelegate pour fournir des fonctions d'édition. Cette implémentation par défaut de l'interface de délégué rend les éléments dans le style habituel pour 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 ils sont interprétés est décrite dans la documentation de 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, et il est nécessaire d'utiliser cette fonction pour définir le délégué d'une vue personnalisée.
Un délégué simple
Le délégué implémenté ici utilise un QSpinBox pour fournir des facilités d'édition, et est principalement destiné à être utilisé avec des modèles qui affichent des nombres entiers. Bien que nous ayons mis en place un modèle de tableau personnalisé basé sur les nombres entiers à cette fin, nous aurions facilement pu utiliser QStandardItemModel à la place, puisque le délégué personnalisé contrôle la saisie des données. Nous construisons une vue de table pour afficher le contenu du modèle, qui utilisera le délégué personnalisé pour l'édition.

Nous sous-classons le délégué de QStyledItemDelegate parce que nous ne voulons pas écrire de fonctions d'affichage personnalisées. Cependant, nous devons toujours fournir des fonctions pour gérer le widget de l'éditeur :
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'éditeur n'est mis en place lorsque le délégué est construit. Nous ne construisons un widget d'éditeur que lorsqu'il 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'éditeur approprié à l'élément en cours de modification. La fonction createEditor() est fournie avec tout ce dont le délégué a besoin pour mettre en place 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 sur le widget de l'éditeur 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 pour nous assurer qu'il fournit les raccourcis d'édition standard attendus par les utilisateurs. Des raccourcis supplémentaires peuvent être ajoutés à l'éditeur pour permettre un comportement plus sophistiqué ; ils sont abordés dans la section sur les astuces 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 plus tard à cet effet. Nous pouvons créer différents éditeurs en fonction de l'index de modèle fourni par la vue. Par exemple, si nous avons une colonne d'entiers et une colonne de chaînes, nous pourrions renvoyer un QSpinBox ou un QLineEdit, en fonction de 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 display role et définissons la valeur de la boîte à outils 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 de l'éditeur est une boîte à boutons, mais nous aurions pu fournir différents éditeurs pour différents types de données dans le modèle, auquel cas nous aurions dû convertir le widget au type approprié avant d'accéder à ses fonctions membres.
Soumettre des données au modèle
Lorsque l'utilisateur a fini de modifier la valeur dans la boîte à boutons, la vue demande au délégué de stocker 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); }
Comme 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 de l'éditeur fourni. Dans ce cas, nous nous assurons que la boîte à boutons est à jour et 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 de la fin de l'édition en émettant le signal closeEditor(). La vue s'assure que le widget de l'éditeur est fermé et détruit. Dans cet exemple, nous ne fournissons que des fonctions d'édition simples et 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 formulées afin d'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 toujours 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
Il incombe au délégué 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 et 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 dans un objet view option.
void SpinBoxDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &/* index */) const { editor->setGeometry(option.rect); }
Dans ce cas, 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 effectue le rendu d'éléments comportant plusieurs éléments n'utiliserait pas directement le rectangle de l'élément. Il positionnerait l'éditeur par rapport aux autres éléments de l'élément.
Conseils d'édition
Après l'édition, les délégués doivent fournir des indications aux autres composants sur le résultat du processus d'édition, ainsi que des indications qui faciliteront les opérations d'édition ultérieures. Pour ce faire, ils émettent le signal closeEditor() avec un indice approprié. Ceci est pris en charge par le filtre d'événement par défaut QStyledItemDelegate que nous avons installé sur la spin box lors de sa construction.
Le comportement de la spin box peut être modifié pour la rendre plus conviviale. Dans le filtre d'événements par défaut fourni par QStyledItemDelegate, si l'utilisateur appuie sur Return pour confirmer son choix dans la boîte à outils, le délégué enregistre la valeur dans le modèle et ferme la boîte à outils. Nous pouvons modifier ce comportement en installant notre propre filtre d'événements sur la boîte tournante et en fournissant des indices d'édition adaptés à nos besoins ; par exemple, nous pourrions émettre closeEditor() avec l'indice EditNextItem pour commencer automatiquement à éditer l'élément suivant dans la vue.
Une autre approche qui ne nécessite pas l'utilisation d'un filtre d'événement consiste à fournir notre propre widget d'édition, en sous-classant éventuellement QSpinBox pour plus de commodité. Cette approche alternative nous donnerait plus de contrôle sur le comportement du widget de l'éditeur au prix de l'écriture d'un code supplémentaire. Il est généralement plus facile d'installer un filtre d'événement dans le délégué si vous devez personnaliser le comportement d'un widget d'éditeur Qt Widgets standard.
Les délégués ne sont pas obligés d'émettre ces indications, mais ceux qui ne le font pas seront moins intégrés dans les applications et moins utilisables 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 basée 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 pour répondre aux exigences de vos propres modèles et vues d'éléments.
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 de modèle pour les éléments d'un modèle unique et est indépendante de toute vue. Comme il peut y avoir plusieurs vues sur un modèle, il est possible de partager les sélections entre les 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 conservent efficacement les informations relatives aux grandes sélections d'éléments en enregistrant uniquement les index de début et de fin de modèle pour chaque plage d'éléments sélectionnés. Les sélections d'éléments non contigus sont construites en utilisant plus d'une plage de sélection pour décrire la sélection.
Les sélections sont appliquées à une collection d'index de modèles détenus par un modèle de sélection. La dernière sélection d'éléments appliquée est appelée sélection courante. 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 courant et éléments sélectionnés
Dans une vue, il y a toujours un élément courant et un élément sélectionné - deux états indépendants. Un élément peut être à la fois l'élément courant et sélectionné. La vue est chargée de veiller à ce qu'il y ait toujours un élément courant, car la navigation au clavier, par exemple, nécessite un élément courant.
Le tableau ci-dessous met en évidence les différences entre l'élément courant et les éléments sélectionnés.
| Élément courant | Éléments sélectionnés |
|---|---|
| Il ne peut y avoir qu'un seul élément courant. | Il peut y avoir plusieurs éléments sélectionnés. |
| L'élément en cours est modifié par la navigation sur les touches ou les clics de souris. | L'état sélectionné des éléments est activé ou désactivé, en fonction de 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 en cours sera édité si la touche d'édition, F2, est pressée ou si l'élément est double-cliqué (à condition que l'édition soit activée). | L'élément courant peut être utilisé avec une ancre pour spécifier une plage qui doit être sélectionnée ou désélectionnée (ou une combinaison des deux). |
| L'élément courant est indiqué par le rectangle de mise au point. | 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ément. Une fois le modèle de sélection mis en place, les collections d'éléments peuvent être sélectionnées, désélectionnées ou leur état de sélection peut être modifié sans qu'il soit nécessaire de savoir quels éléments sont déjà sélectionnés. Les index de tous les éléments sélectionnés peuvent être récupérés à tout moment et d'autres composants peuvent être informés des modifications apportées au modèle de sélection par le biais du mécanisme de signaux et de créneaux.
Utilisation d'un modèle de sélection
Les classes de vues standard fournissent des modèles de sélection par défaut qui peuvent ê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 avec setSelectionModel(), de sorte que la construction de nouveaux modèles de sélection n'est généralement pas nécessaire.
Une sélection est créée en spécifiant un modèle et une paire d'index de modèle vers un QItemSelection. Celui-ci utilise les index pour se référer aux éléments du modèle donné et les interprète comme les éléments 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 soumettre la sélection à un modèle de sélection ; cela peut se faire de plusieurs manières, 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 démontrer certaines des principales caractéristiques des sélections, nous construisons une instance d'un modèle de table personnalisé comportant 32 éléments au total et 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 nous sélectionnons 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 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 dans la vue du tableau, nous devons construire un objet de sélection et 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 de selection flags. Dans ce cas, les drapeaux utilisés font que les éléments enregistrés dans l'objet de sélection sont inclus dans le modèle de sélection, quel que soit leur état antérieur. La sélection résultante est affichée dans la vue.

La sélection des éléments peut être modifiée à l'aide de diverses opérations définies par les drapeaux de sélection. La sélection qui résulte de ces opérations peut avoir une structure complexe, mais elle est représentée efficacement par le modèle de sélection. L'utilisation des différents indicateurs de sélection pour manipuler les éléments sélectionnés est décrite lorsque nous examinons la mise à jour d'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(). Cette fonction renvoie une liste non triée d'index de modèles sur laquelle nous pouvons itérer tant que nous savons à quel modèle ils correspondent :
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 l'intervalle 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 indiquer les changements dans la sélection. Ces signaux informent les autres composants des modifications apportées à l'ensemble de la sélection et à l'élément actuellement ciblé dans le modèle d'élément. 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 fournissons 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 garder une trace de l'élément actuellement sélectionné en connectant le signal currentChanged() à un slot qui est appelé avec deux index de 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 fournissons 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 ¤t, 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())); }
Le suivi des sélections effectuées par l'utilisateur est simple avec ces signaux, mais nous pouvons également mettre à jour le modèle de sélection directement.
Mise à jour d'une sélection
Les commandes de sélection sont fournies par une combinaison d'indicateurs de sélection, définis par QItemSelectionModel::SelectionFlag. Chaque drapeau 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 demande au modèle de sélection d'enregistrer les éléments spécifiés comme étant sélectionnés. L'indicateur Toggle permet au modèle de sélection d'inverser l'état des éléments spécifiés, en sélectionnant tous les éléments désélectionnés et en désélectionnant tous 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 et 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 tableau illustré ci-dessus, en utilisant la commande Toggle pour inverser l'état de sélection des éléments donné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 sont affichés dans la vue tableau, ce qui permet de visualiser facilement ce que nous avons réalisé :

Par défaut, les commandes de sélection n'opèrent que sur les éléments individuels spécifiés par les index du modèle. Toutefois, l'indicateur utilisé pour décrire la commande de sélection peut être combiné avec d'autres indicateurs pour modifier des lignes et des colonnes entières. Par exemple, si vous appelez select() avec un seul index, mais avec une commande qui est une combinaison de Select et Rows, la ligne entière contenant l'élément auquel il est fait référence est sélectionnée. Le code suivant illustre l'utilisation des drapeaux 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 drapeaux de sélection Columns et Rows signifie que deux colonnes et deux lignes sont sélectionnées. L'image suivante montre le résultat de ces deux sélections :

Les commandes effectuées sur le modèle d'exemple ont toutes impliqué l'accumulation d'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, il faut combiner les autres indicateurs de sélection avec l'indicateur Current. Une commande utilisant cet indicateur demande au modèle de sélection de remplacer sa collection actuelle d'index de modèle par ceux spécifiés dans un appel à 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 la collection d'index de modèles 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, nous récupérons les index correspondant aux éléments en haut à gauche et en bas à droite avec 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 avec ces index et le 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, nous définissons l'index parent de la manière habituelle :
QModelIndex parent = QModelIndex();
Pour les modèles hiérarchiques, la fonction hasChildren() est utilisée pour 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/vue permet de créer des modèles qui peuvent tirer parti des vues existantes. Cette approche nous permet de présenter des données provenant de diverses sources à l'aide de composants d'interface utilisateur graphique standard, tels que QListView, QTableView et QTreeView.
La classe QAbstractItemModel fournit une interface suffisamment souple pour prendre en charge les sources de données qui organisent les informations dans des structures hiérarchiques, ce qui permet d'insérer, de supprimer, de modifier ou de trier les données d'une manière ou d'une autre. Elle prend également en charge les opérations de glisser-déposer.
Les classes QAbstractListModel et QAbstractTableModel prennent en charge les interfaces avec des structures de données non hiérarchiques plus simples et sont plus faciles à utiliser comme point de départ pour les modèles de listes et de tableaux simples.
Dans cette section, nous créons un modèle simple en lecture seule pour explorer les principes de base de l'architecture modèle/vue. Plus loin dans cette section, nous adaptons ce modèle simple pour que les éléments puissent être modifiés par l'utilisateur.
Pour un exemple de modèle plus complexe, voir l'exemple du modèle d'arbre simple.
Les exigences de QAbstractItemModel sous-classes sont décrites plus en détail dans le document Model Subclassing Reference.
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 avec les données. Si 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 sous-classer QAbstractListModel ou QAbstractTableModel, car ces classes fournissent des implémentations par défaut appropriées pour 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 sous-classer QAbstractItemModel. Cette approche est adoptée dans l'exemple du modèle d'arbre simple.
Dans cette section, nous mettons en œuvre un modèle simple basé sur une liste de chaînes de caractères, de sorte que 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 QAbstractItemModel standard dans les modèles spécialisés par une API qui permet un accès plus naturel à la structure de données sous-jacente. Il est ainsi plus facile d'alimenter le modèle en données, tout en permettant à d'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é à cette fin.
Un modèle d'exemple en lecture seule
Le modèle mis en œuvre ici est un modèle de données en lecture seule simple, non hiérarchique, basé sur la classe standard QStringListModel. Sa source de données interne est QStringList et il ne met en œuvre que ce qui est nécessaire pour que le modèle fonctionne. Pour faciliter l'implémentation, nous avons sous-classé QAbstractListModel parce qu'elle définit un comportement par défaut raisonnable pour les modèles de liste et qu'elle expose une interface plus simple que la classe QAbstractItemModel.
Lors de l'implémentation d'un modèle, il est important de se rappeler que QAbstractItemModel ne stocke aucune donnée elle-même, elle présente simplement une interface que les vues utilisent pour accéder aux données. Pour un modèle minimal en lecture seule, il n'est nécessaire d'implémenter que quelques fonctions car il existe des implémentations par défaut pour la plupart des interfaces. 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; };
Outre le constructeur du modèle, nous n'avons besoin d'implémenter que deux fonctions : rowCount() renvoie le nombre de lignes dans le modèle et data() renvoie un élément de données correspondant à un index de modèle spécifié.
Les modèles qui se comportent bien implémentent également headerData() pour donner aux vues d'arbre et de table quelque chose à afficher dans leurs en-têtes.
Notez qu'il s'agit d'un modèle non hiérarchique, nous n'avons donc pas à nous préoccuper des relations parent-enfant. Si notre modèle était hiérarchique, nous devrions également implémenter les fonctions index() et parent().
La liste des chaînes de caractères est stockée en interne dans la variable membre privée stringList.
Dimensions du modèle
Nous voulons que le nombre de lignes dans le modèle soit le même que le nombre de chaînes dans la liste de chaînes. Nous implémentons la fonction rowCount() dans cette optique :
int StringListModel::rowCount(const QModelIndex &parent) const { return stringList.count(); }
Le modèle n'étant pas hiérarchique, nous pouvons 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, il n'est donc pas nécessaire de réimplémenter la fonction columnCount().
En-têtes et données du modèle
Pour les éléments de la vue, nous voulons retourner les chaînes 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 un site QVariant valide que si l'index de 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 et si le rôle demandé est un rôle que nous prenons en charge.
Certaines vues, telles que QTreeView et QTableView, sont capables d'afficher les en-têtes en même temps que les données de l'élément. Si notre modèle est affiché dans une vue avec des en-têtes, nous voulons que les en-têtes affichent les numéros de ligne et de colonne. Nous pouvons fournir des informations sur les en-têtes en sous-classant 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 renvoyons une adresse QVariant valide uniquement si le rôle est l'un de ceux que nous prenons en charge. L'orientation de l'en-tête est également prise en compte pour déterminer les données exactes à renvoyer.
Toutes les vues n'affichent pas les en-têtes avec les données de l'élément, et celles qui le font peuvent être configurées pour les masquer. Néanmoins, il est recommandé d'implémenter la fonction headerData() pour fournir des informations pertinentes sur les données fournies par le modèle.
Un élément peut avoir plusieurs rôles et fournir des données différentes en fonction du rôle spécifié. Les éléments de notre modèle n'ont qu'un seul rôle, DisplayRole, et 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 DisplayRole dans d'autres rôles, tels que 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 est beaucoup plus utile. Nous pouvons modifier le modèle en lecture seule pour rendre les éléments modifiables en changeant la fonction data() que nous avons implémentée pour la 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 drapeaux 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 seulement fournir au délégué un moyen de définir les données dans le modèle. C'est ce que permet 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 qui correspond à l'index du modèle est remplacé par la valeur fournie. Toutefois, avant de pouvoir modifier la liste de chaînes, nous devons nous assurer que l'index est valide, que l'élément est du bon type et que le rôle est pris en charge. Par convention, nous insistons sur le fait que le rôle est EditRole, car c'est le rôle utilisé par le délégué d'élément standard. Pour les valeurs booléennes, cependant, vous pouvez 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 les mêmes pour tous les rôles, ce détail facilite donc l'intégration du modèle dans les composants standard.
Lorsque les données ont été définies, le modèle doit faire savoir aux vues que certaines données ont été modifiées. Pour ce faire, il émet le signal dataChanged(). Étant donné qu'un seul élément de données a changé, l'éventail des éléments spécifiés dans le signal est limité à un seul index de modèle.
La fonction data() doit également ê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'y a de sens qu'à changer le nombre de lignes, donc nous ne réimplémentons 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;
Étant donné que, dans ce modèle, les lignes 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 de caractères 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 est normalement utilisé pour déterminer à quel endroit du modèle les lignes doivent être ajoutées. Dans le cas présent, nous ne disposons que d'une seule liste de chaînes de niveau supérieur, et nous nous contentons donc d'insérer des chaînes vides dans cette liste.
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 spécifie les numéros de ligne de la première et de la dernière nouvelle ligne à insérer, ainsi que l'index du modèle pour leur élément parent. Après avoir modifié la liste de chaînes, elle appelle endInsertRows() pour terminer l'opération et informer les autres composants que les dimensions du modèle ont changé, en renvoyant true pour indiquer le succès.
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 la position et le nombre de lignes donnés. Nous ignorons l'index parent pour simplifier notre implémentation et nous nous contentons de supprimer les éléments correspondants de la liste de chaînes.
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 des données sous-jacentes et spécifie la première et la dernière ligne à supprimer. Cela permet à d'autres composants d'accéder aux données avant qu'elles ne deviennent indisponibles. Une fois les lignes supprimées, le modèle émet la fonction endRemoveRows() pour terminer l'opération et indiquer aux autres composants que les dimensions du modèle ont changé.
Prochaines étapes
Nous pouvons afficher les données fournies par ce modèle, ou 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, 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 vues standard dans Classes de vues.
Le document Model Subclassing Reference (Référence pour le sous-classement de modèles) aborde plus en détail les exigences des sous-classes de QAbstractItemModel et fournit un guide des fonctions virtuelles qui doivent être mises en œuvre pour activer diverses fonctionnalités dans différents types de modèles.
Classes de commodité pour l'affichage des éléments
Les widgets basés sur les éléments portent des noms qui reflètent leur utilisation : QListWidget fournit une liste d'éléments, QTreeWidget affiche une structure arborescente à plusieurs niveaux et QTableWidget fournit un tableau d'éléments de cellules. Chaque classe hérite du comportement de la classe QAbstractItemView qui implémente le 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'une page QListWidget et d'un certain nombre de pages 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 la liste peuvent être ajoutés directement au widget de liste lorsqu'ils sont construits :
new QListWidgetItem(tr("Sycamore"), listWidget); new QListWidgetItem(tr("Chestnut"), listWidget); new QListWidgetItem(tr("Mahogany"), listWidget);
Ils peuvent également être construits sans widget de liste parent et être 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 info-bulles, les astuces d'état et l'aide "Qu'est-ce que c'est ?" sont toutes facilement configurables pour garantir que la liste est correctement intégrée 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 à l'adresse Qt::SortOrder pour produire une liste d'éléments triés par ordre alphabétique avant ou arrière :
Widgets d'arborescence
Les classes QTreeWidget et QTreeWidgetItem fournissent des arbres ou des listes hiérarchiques d'éléments. Chaque élément d'un widget arbre peut avoir ses propres éléments enfants et peut afficher un certain nombre de colonnes d'informations. Les widgets d'arbre sont créés comme n'importe quel autre widget :
QTreeWidget *treeWidget = new QTreeWidget(this);
Avant de pouvoir ajouter des éléments au widget arbre, le nombre de colonnes doit être défini. Par exemple, nous pourrions définir deux colonnes et créer un en-tête pour fournir des étiquettes 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 définir les étiquettes de chaque section est de fournir une liste de chaînes. Pour des en-têtes plus sophistiqués, vous pouvez construire un élément d'arbre, le décorer comme vous le souhaitez et l'utiliser comme en-tête du widget d'arbre.
Les éléments de premier niveau du widget d'arbre sont construits avec le widget d'arbre comme widget parent. Ils peuvent être insérés dans un ordre arbitraire, ou vous pouvez vous assurer qu'ils sont listés dans un ordre particulier en spécifiant l'élément précédent lors de la construction 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 d'arborescence traitent les éléments de niveau supérieur légèrement différemment des autres éléments situés plus loin dans l'arborescence. Les éléments peuvent être supprimés du niveau supérieur de l'arbre en appelant la fonction takeTopLevelItem() du widget d'arbre, 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'arbre à l'aide de la fonction insertTopLevelItem(). Aux niveaux inférieurs de l'arbre, la fonction insertChild() de l'élément parent est utilisée.
Il est facile de déplacer des éléments entre le niveau supérieur et les niveaux inférieurs de l'arbre. Il suffit de vérifier si les éléments sont de niveau supérieur ou non, et cette information est fournie par la fonction parent() de chaque élément. Par exemple, nous pouvons supprimer l'élément actuel dans le widget de l'arbre, 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 de l'arbre suit le même schéma :
QTreeWidgetItem *parent = currentItem->parent(); QTreeWidgetItem *newItem; if (parent) newItem = new QTreeWidgetItem(parent, treeWidget->currentItem()); else newItem = new QTreeWidgetItem(treeWidget, treeWidget->currentItem());
Widgets de tableau
Les tableaux d'éléments similaires à ceux que l'on trouve dans les tableurs sont construits à l'aide des fonctions QTableWidget et QTableWidgetItem. Ceux-ci fournissent un widget de tableau défilant avec des en-têtes et des éléments à utiliser à l'intérieur de celui-ci.
Les tableaux peuvent être créés avec un nombre déterminé de lignes et de colonnes, ou ces dernières peuvent être ajoutées à un tableau non dimensionné au fur et à mesure des besoins.
QTableWidget *tableWidget; tableWidget = new QTableWidget(12, 3, this);
Les éléments sont construits en dehors du tableau avant d'être ajoutés au tableau à l'endroit voulu :
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 construisant des éléments en dehors du tableau 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.
Caractéristiques communes
Il existe un certain nombre de fonctionnalités basées sur les éléments qui sont communes à chacune des classes de commodité et qui sont disponibles par le biais des mêmes interfaces dans chaque classe. Nous les présentons dans les sections suivantes avec quelques exemples pour différents widgets. Consultez la liste des classes de modèles/vues pour chacun des widgets pour plus de détails sur l'utilisation de chaque fonction utilisée.
Éléments cachés
Il est parfois utile de pouvoir masquer des éléments dans un widget de vue d'élément plutôt que de les supprimer. Les éléments de tous les widgets ci-dessus peuvent être cachés et réaffichés par la suite. Vous pouvez déterminer si un élément est caché en appelant la fonction isItemHidden(), et les éléments peuvent être cachés à l'aide de setItemHidden().
Cette opération étant basée sur les éléments, la même fonction est disponible pour les trois classes de commodité.
Sélection
Le mode de sélection des éléments est contrôlé 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 de sélections portant sur plusieurs éléments, si la sélection doit porter sur une plage continue d'éléments. Le mode de sélection fonctionne de la même manière pour tous les widgets ci-dessus.
| Sélections d'un seul élément : Lorsque l'utilisateur doit choisir un seul élément dans un widget, le mode par défaut SingleSelection est le plus approprié. Dans ce mode, l'élément courant et l'élément sélectionné sont identiques. |
| Sélections multi-éléments : Dans ce mode, l'utilisateur peut basculer l'état de 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 basculées indépendamment. |
| 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 du widget peuvent être sélectionnées à la fois à l'aide de la souris et du 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 si des touches de modification sont utilisées.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(), ce qui permet d'obtenir une liste d'éléments pertinents sur laquelle il est possible d'itérer. Par exemple, nous pouvons trouver la somme de toutes les valeurs numériques dans une liste d'éléments sélectionnés avec le 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 qu'en mode de sélection unique, l'élément courant fait partie de la sélection. Dans les modes multi-sélection et sélection étendue, l'élément courant peut ne pas se trouver dans la sélection, en fonction de la manière dont l'utilisateur a formé la sélection.
Recherche
Il est souvent utile de pouvoir trouver des éléments dans un widget de vue d'élément, que ce soit en tant que développeur ou en tant que service à présenter aux utilisateurs. Les trois classes de commodité d'affichage des éléments fournissent une fonction commune findItems() pour rendre cette opération aussi cohérente et simple que possible.
Les éléments sont recherchés par le texte qu'ils contiennent selon des critères spécifiés par une sélection de valeurs provenant de Qt::MatchFlags. Nous pouvons obtenir une liste d'éléments correspondants avec 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 liste et 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 cadre modèle/vue. Les éléments des listes, des tableaux et des arbres peuvent être glissés dans les 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 des éléments pour modifier l'ordre dans lequel ils sont affichés. Par défaut, la fonction glisser-déposer n'est pas activée pour ces vues, car elles sont configurées pour les utilisations les plus simples et les plus courantes. Pour permettre le déplacement des éléments, certaines propriétés de la vue doivent être activées et les éléments eux-mêmes doivent permettre le déplacement.
Les exigences relatives à un modèle qui permet uniquement d'exporter des éléments à partir d'une vue et qui ne permet pas d'y déposer des données sont moindres que celles relatives à un modèle de glisser-déposer entièrement activé.
Voir également la référence de sous-classement 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é pour utiliser un ensemble différent de drapeaux par défaut. Par exemple, chaque élément QListWidgetItem ou QTreeWidgetItem est initialement activé, vérifiable, sélectionnable et peut être utilisé comme source d'une opération de glisser-déposer ; chaque élément QTableWidgetItem peut également être modifié et utilisé comme cible d'une opération de glisser-déposer.
Bien que tous les éléments standard aient un ou deux drapeaux définis 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 permettre 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 dans la vue, définissez la propriété viewport() de la vue acceptDrops sur
true. - Pour montrer à l'utilisateur où l'élément en cours de déplacement sera placé s'il est déposé, définissez la propriété showDropIndicator de la vue. L'utilisateur dispose ainsi d'informations actualisées en permanence sur l'emplacement de l'élément dans la vue.
Par exemple, nous pouvons activer le glisser-déposer dans un widget de liste avec les lignes de code suivantes :
QListWidget *listWidget = new QListWidget(this); listWidget->setSelectionMode(QAbstractItemView::SingleSelection); listWidget->setDragEnabled(true); listWidget->viewport()->setAcceptDrops(true); listWidget->setDropIndicatorShown(true);
Le résultat est un widget de liste qui permet de copier les éléments à l'intérieur de la vue, et qui permet même à l'utilisateur 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 dans la vue, nous devons définir l'option dragDropMode du widget de liste :
listWidget->setDragDropMode(QAbstractItemView::InternalMove);
Utilisation des classes modèle/vue
La configuration d'une vue pour le glisser-déposer suit le même schéma que celui utilisé pour les vues de commodité. Par exemple, une vue QListView peut être configurée de la même manière qu'une vue QListWidget:
QListView *listView = new QListView(this); listView->setSelectionMode(QAbstractItemView::ExtendedSelection); listView->setDragEnabled(true); listView->setAcceptDrops(true); listView->setDropIndicatorShown(true);
Étant donné que l'accès aux données affichées par la vue est contrôlé par un modèle, le modèle utilisé 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 toute combinaison de valeurs de Qt::DropActions puisse être donnée, le modèle doit être écrit pour les prendre en charge. Par exemple, pour que Qt::MoveAction puisse être utilisé 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 les éléments qui peuvent être glissés et ceux qui acceptent d'être déposés, en réimplémentant la fonction QAbstractItemModel::flags() pour fournir les drapeaux appropriés.
Par exemple, un modèle qui fournit une liste simple basée sur QAbstractListModel peut activer le glisser-déposer pour chacun des éléments en s'assurant que les drapeaux 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 possible que pour les éléments valides.
Dans le code ci-dessus, puisque le modèle est dérivé de QStringListModel, nous obtenons un ensemble de drapeaux par défaut 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 d'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 qui ne fournit que du texte brut fournira l'implémentation suivante :
QStringList DragDropListModel::mimeTypes() const { QStringList types; types << "application/vnd.text.list"; return types; }
Le modèle doit également fournir un code permettant d'encoder les données dans le format annoncé. Pour ce faire, la fonction QAbstractItemModel::mimeData() est réimplémentée afin de fournir un objet QMimeData, comme pour 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 clair 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; }
Étant donné qu'une liste d'index de modèle est fournie à la fonction, cette approche est suffisamment générale pour être utilisée dans les modèles hiérarchiques et non hiérarchiques.
Notez que les types de données personnalisés doivent être déclarés comme meta objects et que les opérateurs de flux doivent être implémentés pour eux. Voir la description de la classe QMetaObject pour plus de détails.
Insertion de données abandonné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, tableau ou arbre) et de la manière dont son contenu est susceptible d'être présenté à l'utilisateur. En règle générale, l'approche adoptée pour gérer 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 supprimées de différentes manières. Les modèles de liste et de table 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 ils peuvent écraser le contenu de l'élément dans le modèle en utilisant certaines des données fournies. Les modèles arborescents sont souvent capables d'ajouter des éléments enfants contenant de nouvelles données à leurs magasins de données sous-jacents et se comportent donc de manière plus prévisible pour l'utilisateur.
Les données abandonnées sont gérées par la réimplémentation de QAbstractItemModel::dropMimeData() d'un modèle. Par exemple, un modèle qui gère une simple liste de chaînes de caractères peut fournir une implémentation qui traite les données déposées sur des éléments existants séparément des données 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 QAbstractItemModel::canDropMimeData().
Le modèle doit d'abord s'assurer que l'opération doit être exécutée, que les données fournies sont dans un format utilisable et que leur destination dans le 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 de liste de chaînes à une colonne peut indiquer un échec si les données fournies ne sont pas du texte brut ou si le numéro de colonne donné 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 voulons autoriser les dépôts entre les éléments existants, avant le premier élément de la liste et après le dernier élément.
Lorsqu'un dépôt se produit, l'index du modèle correspondant à l'élément parent sera soit valide, indiquant que le dépôt s'est produit sur un élément, soit invalide, indiquant que le dépôt s'est produit quelque part dans la vue qui correspond 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, le dépôt s'est produit sur un élément. Dans ce modèle de liste simple, nous trouvons le numéro de ligne de l'élément et utilisons cette valeur pour insérer les éléments déposés au niveau supérieur du modèle.
else beginRow = rowCount(QModelIndex());
Lorsqu'un abandon se produit ailleurs dans la vue et que le numéro de ligne est inutilisable, nous ajoutons des éléments au niveau supérieur du modèle.
Dans les modèles hiérarchiques, lorsqu'un élément est abandonné, il est préférable d'insérer de 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 n'a qu'un seul niveau, cette approche n'est donc pas appropriée.
Décodage des données importées
Chaque implémentation de 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 de liste de chaînes simple, les éléments encodés peuvent être décodés et transmis en continu à 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 peuvent ensuite être insérées dans le magasin de données sous-jacent. Pour des raisons de cohérence, cette opération peut être effectuée par l'intermédiaire de 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 mandataires
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 quelconque de vues, et chacune d'entre elles peut éventuellement représenter la même information de manière complètement différente. Les vues personnalisées et les délégués sont 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 des mêmes données, telles que des vues triées différemment sur une liste d'éléments.
Bien qu'il semble approprié d'effectuer des 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'autre approche, qui consiste à effectuer le tri dans le modèle lui-même, pose le même problème : chaque vue doit afficher des éléments de données organisés en fonction de l'opération de traitement la plus récente.
Pour résoudre ce problème, le cadre modèle/vue utilise des modèles proxy pour gérer les informations fournies entre les modèles et les vues individuels. Les modèles mandataires 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 emplacements utilisés par le cadre modèle/vue garantissent que chaque vue est mise à jour de manière appropriée, quel que soit le nombre de modèles mandataires placés entre elle et le modèle source.
Utilisation de modèles proxy
Les modèles proxy peuvent être insérés entre un modèle existant et n'importe quel nombre de vues. Qt XML 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 fournir un comportement de filtrage et de tri personnalisé. 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);
Puisque les modèles proxy héritent de QAbstractItemModel, ils peuvent être connectés à n'importe quel type de vue et peuvent être partagés entre les vues. Ils peuvent également être utilisés pour traiter les informations obtenues à partir d'autres modèles mandataires dans le cadre d'un 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 nécessaires.
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 filtrants. Les vues accèdent aux éléments à l'aide d'index de modèle fournis par le modèle proxy, et ces index ne contiennent aucune information sur le modèle source ou sur les emplacements des éléments originaux dans ce modèle.
QSortFilterProxyModel permet de filtrer les données d'un modèle source avant de les fournir aux vues, et permet également de fournir le contenu d'un modèle source aux vues en tant que 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, la classe QSortFilterProxyModel peut être sous-classée, ce qui permet 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 du modèle mandataire est demandé ou utilisé :
- filterAcceptsColumn() est utilisée pour filtrer des colonnes spécifiques d'une partie du modèle source.
- filterAcceptsRow() est utilisée pour 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 s'assurer 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 Les instances 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() pour effectuer des comparaisons personnalisées.
Référence de sous-classement de modèle
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 - qu'il fournisse des vues avec une simple liste, un tableau ou une hiérarchie complexe d'éléments. Les modèles qui héritent de QAbstractListModel et QAbstractTableModel peuvent profiter des implémentations par défaut des fonctions fournies par ces classes. Les modèles qui exposent des éléments de données dans des structures arborescentes doivent fournir des implémentations pour de nombreuses fonctions virtuelles dans QAbstractItemModel.
Les fonctions qui doivent être implémentées dans une sous-classe de modèle peuvent être divisées en trois groupes :
- Traitement des données des éléments : Tous les modèles doivent mettre en œuvre des fonctions permettant aux vues et aux délégués d'interroger les dimensions du modèle, d'examiner les éléments et d'extraire 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'elles 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 que d'autres composants et applications peuvent comprendre.
Traitement des données des éléments
Les modèles peuvent offrir différents niveaux d'accès aux données qu'ils fournissent : Ils peuvent être de simples composants en lecture seule, certains modèles peuvent prendre en charge des opérations de redimensionnement et d'autres peuvent permettre la modification des éléments.
Accès en lecture seule
Pour permettre 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 de drapeaux doit inclure Qt::ItemIsEnabled et Qt::ItemIsSelectable. |
| data() | Utilisé pour fournir des données sur les éléments aux vues et aux délégués. En général, les modèles ne doivent fournir des données que pour Qt::DisplayRole et tout rôle d'utilisateur spécifique à l'application, mais c'est aussi une bonne pratique de fournir des données pour Qt::ToolTipRole, Qt::AccessibleTextRole, et Qt::AccessibleDescriptionRole. Voir la documentation de l'énumération Qt::ItemDataRole pour plus d'informations sur les types associés à chaque rôle. |
| headerData() | Fournit aux vues des informations à afficher dans leurs en-têtes. Ces informations ne sont récupérées que par les vues qui peuvent 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-classesQAbstractListModel ) et les modèles de table (sous-classesQAbstractTableModel ).
En outre, les fonctions suivantes doivent être mises en œuvre 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 permettre l'édition, les fonctions suivantes doivent être mises en œuvre correctement :
| 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 dans 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 de l'utilisateur, fournies par les éléments de l'interface utilisateur, cette fonction doit traiter les données associées à Qt::EditRole. La mise en œuvre 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() | Utilisé pour 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 avant et après qu' elles se soient produites. Par conséquent, les fonctions suivantes peuvent être mises en œuvre pour permettre au modèle d'être redimensionné, 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 attaché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 les structures de données sous-jacentes, et appeler endInsertRows() immédiatement après. |
| removeRows() | Utilisé pour 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, et appeler endRemoveRows() immédiatement après. |
| insertColumns() | Utilisé pour 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 les structures de données sous-jacentes et appeler endInsertColumns() immédiatement après. |
| removeColumns() | Utilisé pour 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 que les colonnes ne soient supprimées des structures de données sous-jacentes, et appeler endRemoveColumns() immédiatement après. |
En règle générale, ces fonctions doivent renvoyer la valeur "true" si l'opération est réussie. Toutefois, il peut arriver que l'opération ne réussisse qu'en partie, par exemple si le nombre de lignes insérées est inférieur au nombre spécifié. Dans ce cas, le modèle doit renvoyer false pour indiquer l'échec afin de permettre aux composants attachés de gérer la situation.
Les signaux émis par les fonctions appelées dans les implémentations de l'API de redimensionnement donnent aux composants attachés la possibilité d'agir avant que les données ne deviennent indisponibles. L'encapsulation des opérations d'insertion et de suppression dans des fonctions de début et de fin permet également au modèle de gérer correctement 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 par exemple une réorganisation interne, un tri des données ou toute autre modification structurelle, il est nécessaire d'exécuter la séquence suivante :
- émission du signal layoutAboutToBeChanged()
- Mettre à jour les données internes qui représentent la structure du modèle.
- Mise à jour des index persistants à l'aide de changePersistentIndexList()
- Émettre le signal layoutChanged().
Cette séquence peut être utilisée pour toute mise à jour structurelle au lieu des méthodes protégées plus pratiques et de haut niveau. Par exemple, si un modèle de deux millions de lignes doit être débarrassé de toutes les lignes impaires, cela représente 1 million de plages discountigues de 1 élément chacune. Il serait possible d'utiliser beginRemoveRows et endRemoveRows un million de fois, mais ce serait évidemment inefficace. Au lieu de cela, cette opération peut être signalée comme une modification unique de la disposition qui met à jour tous les index persistants nécessaires en une seule fois.
Population paresseuse des données de modèle
La population paresseuse des données du modèle permet de différer les demandes d'informations sur le modèle jusqu'à ce que les vues en aient réellement besoin.
Certains modèles doivent obtenir des données à partir de sources distantes ou effectuer des opérations fastidieuses pour obtenir des informations sur la manière dont les données sont organisé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 sont renvoyées afin de réduire les demandes de données ultérieures inutiles.
Dans les modèles hiérarchiques où la recherche 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 rowCount() du modèle n'est appelée qu'en cas de nécessité. Dans ce cas, la fonction hasChildren() peut être réimplémentée pour fournir un moyen peu coûteux aux vues de vérifier la présence d'enfants et, dans le cas de QTreeView, de dessiner 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 montrer.
Si l'on sait que de nombreux éléments auront des enfants, il est parfois utile de réimplémenter hasChildren() pour qu'il renvoie inconditionnellement true. Cela permet de s'assurer que chaque élément peut être examiné ultérieurement à la recherche d'enfants, tout en rendant la population initiale des données du modèle aussi rapide que possible. Le seul inconvénient est que les éléments sans enfants peuvent être affichés de manière incorrecte dans certaines vues jusqu'à ce que l'utilisateur tente de voir les éléments enfants inexistants.
Navigation et création d'index de modèle
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.
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() | Étant donné 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 invalide. |
| parent() | Fournit un index de modèle correspondant au parent d'un é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'y a pas d'élément parent valide dans le modèle, la fonction doit renvoyer un index de modèle invalide, créé avec le constructeur QModelIndex() vide. |
Les deux fonctions ci-dessus utilisent la fonction d'usine createIndex() pour générer des index utilisables par d'autres composants. Il est normal que les modèles fournissent un identifiant unique à cette fonction afin de garantir que l'index du modèle puisse être réassocié à l'élément correspondant ultérieurement.
Prise en charge du glisser-déposer et gestion des types MIME
Les classes de modèles et de vues prennent en charge les opérations de glisser-déposer, en fournissant un comportement par défaut qui est 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 comment ils sont insérés dans les modèles existants.
En outre, les classes de vues de commodité implémentent un comportement spécialisé qui doit être proche de celui attendu par les développeurs existants. La section " Convenience Views" fournit une vue d'ensemble de ce comportement.
Données MIME
Par défaut, les modèles et les vues intégrés utilisent un type MIME interne (application/x-qabstractitemmodeldatalist) pour transmettre des informations sur les index de modèle. Ce type spécifie des données pour 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 que chaque élément prend en charge.
Les données encodées à l'aide de ce type MIME peuvent être obtenues en appelant QAbstractItemModel::mimeData() avec un QModelIndexList contenant les éléments à sérialiser.
Lors de l'implémentation 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 application/x-qabstractitemmodeldatalist.Les sous-classes peuvent obtenir l'objet QMimeData par défaut 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 les images, les couleurs et les documents HTML peuvent facilement être ajoutés à un objet QMimeData à l'aide des fonctions QMimeData::setImageData(), QMimeData::setColorData() et QMimeData::setHtml().
Acceptation 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é pour 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 remplacent 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 les données sérialisées d'un élément 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'écrase jamais aucune donnée dans le modèle ; au lieu de cela, elle tente d'insérer les éléments de données soit en tant que 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 permet de remplir plus efficacement les nouveaux éléments. |
Pour accepter d'autres formes de données, ces fonctions doivent être réimplémentées :
| supportedDropActions() | Utilisée pour renvoyer une combinaison de drop actions, indiquant les types d'opérations de glisser-déposer que le modèle accepte. |
| mimeTypes() | Utilisée pour renvoyer une liste de types MIME qui peuvent être décodés et traités par le modèle. En général, les types MIME pris en charge pour l'entrée dans le modèle sont les mêmes que ceux qu'il peut utiliser lors de l'encodage des données à l'intention des composants externes. |
| dropMimeData() | Effectue le décodage réel des données transférées par les opérations de glisser-déposer, détermine l'endroit du modèle où elles seront placées et insère de nouvelles lignes et colonnes si nécessaire. La manière dont cette fonction est mise en œuvre dans les sous-classes dépend des exigences des 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 des réimplémentations d'autres fonctions de la sous-classe, telles que setData(), insertRows() et insertColumns(), pour s'assurer que le modèle se comporte de manière cohérente.
Afin de s'assurer que les opérations de glissement fonctionnent correctement, il est important de réimplémenter les fonctions suivantes qui suppriment des données du modèle :
- removeRows()
- removeRow()
- removeColumns()
- removeColumn()
Pour plus d'informations sur le glisser-déposer avec les vues d'éléments, voir Utilisation du glisser-déposer avec les vues d'éléments.
Vues pratiques
Les vues de commodité (QListWidget, QTableWidget, et QTreeWidget) remplacent la fonctionnalité de glisser-déposer par défaut pour offrir un comportement moins souple, mais plus naturel, qui convient à de nombreuses applications. Par exemple, comme il est plus courant de déposer des données dans les cellules d'un site 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 de commodité, voir Utilisation du glisser-déposer avec les vues d'éléments.
Optimisation des performances pour les grandes quantités de données
La fonction canFetchMore() vérifie si le parent dispose de plus de données 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 un modèle QAbstractItemModel. Nous réimplémentons canFetchMore() pour indiquer s'il y a d'autres données à récupérer et fetchMore() pour remplir le modèle en fonction des besoins.
Un autre exemple serait celui des modèles d'arbres alimentés dynamiquement, pour lesquels nous réimplémentons fetchMore() lorsqu'une branche du modèle d'arbre est développée.
Si votre réimplémentation de fetchMore() ajoute des lignes au modèle, vous devez appeler beginInsertRows() et endInsertRows(). De même, canFetchMore() et fetchMore() doivent être réimplémentés car leur implémentation par défaut renvoie false et ne fait rien.
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 séparées de la manière dont les données sont présentées et manipulées par l'utilisateur (dans la vue).
Elles sont utilisées pour afficher et modifier les éléments de données d'un modèle. | |
L'interface abstraite pour les classes de modèles d'éléments | |
Fonctionnalité de base des classes de vues d'éléments | |
Modèle abstrait qui peut être sous-classé pour créer des modèles de liste unidimensionnels. | |
Classe de base pour les modèles d'éléments proxy qui peuvent effectuer des opérations de tri, de filtrage ou d'autres tâches de traitement des données | |
Modèle abstrait pouvant être sous-classé pour créer des modèles de table | |
Mise en œuvre d'un modèle/vue d'une vue en colonnes | |
Proxy de plusieurs modèles sources, concaténant leurs lignes | |
Correspondance entre une section d'un modèle de données et des widgets | |
Modèle de données pour le système de fichiers local | |
Ligne ou colonne d'en-tête pour les vues d'éléments | |
Propose son modèle source sans modification | |
Facilités d'affichage et d'édition pour les éléments de données d'un modèle | |
Permet de créer des bases de créateurs d'éditeurs d'éléments sans sous-classer 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. | |
Widgets permettant de modifier les données relatives aux éléments dans les vues et les délégués | |
Gère les informations sur les éléments sélectionnés dans un modèle | |
Garde la trace des éléments sélectionnés d'une vue | |
Gère les informations relatives à une série d'éléments sélectionnés dans un modèle | |
Vue en liste ou en icône sur un modèle | |
Widget de liste basé sur des éléments | |
Élément à utiliser avec la classe de vue d'élément QListWidget | |
Utilisé pour localiser des données dans un modèle de données | |
Contient un rôle et les données associées à ce rôle | |
Portée sur les objets QModelRoleData | |
Utilisé pour localiser des données dans un modèle de données | |
Implémente QAbstractItemModel pour n'importe quelle gamme C++. | |
Le modèle fournit un point de personnalisation pour contrôler la façon dont QRangeModel accède aux données de rôle des éléments individuels. | |
Le modèle fournit un point de personnalisation pour contrôler la façon dont QRangeModel représente les types utilisés comme lignes | |
Accès conforme au modèle QAbstractItemModel à n'importe quel intervalle C++. | |
Prise en charge du tri et du filtrage des données transmises entre un autre modèle et une vue | |
Élément à utiliser avec la classe QStandardItemModel | |
Possibilité d'enregistrer des widgets sans avoir à sous-classer QItemEditorCreatorBase | |
Modèle générique pour le stockage de données personnalisées | |
Modèle fournissant des chaînes de caractères aux vues | |
Possibilités d'affichage et d'édition pour les éléments de données d'un modèle | |
Mise en œuvre du modèle/de la vue par défaut d'une vue de tableau | |
Vue de tableau basée sur les éléments avec un modèle par défaut | |
Élément à utiliser avec la classe QTableWidget | |
Moyen d'interagir avec la sélection dans un modèle sans utiliser d'index de modèle ni de modèle de sélection | |
Implémentation d'un modèle/vue par défaut d'une vue arborescente | |
Vue d'arbre qui utilise un modèle d'arbre prédéfini | |
Élément à utiliser avec la classe de commodité QTreeWidget | |
Moyen d'itérer sur les éléments d'une instance de QTreeWidget |
Exemples associés
© 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.



