IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Viadeo Twitter Facebook Share on Google+   
Logo Documentation Qt ·  Page d'accueil  ·  Toutes les classes  ·  Toutes les fonctions  ·  Vues d'ensemble  · 

Programmation modèle/vue

Introduction à la programmation modèle/vue

Qt 4 a introduit un nouvel ensemble de classes de vues d?éléments qui utilisent l?architecture modèle/vue pour gérer la relation entre les données et la façon de les présenter à l?utilisateur. La séparation des fonctionnalités introduite par cette architecture donne au développeur une plus grande flexibilité pour personnaliser la présentation des éléments et fournit une interface de modèle standard autorisant l'utilisation d'un large éventail de sources de données avec les vues existantes. Ce document comprend une brève introduction au paradigme modèle/vue, un aperçu des concepts impliqués, et une description de l?architecture du système de vues d?éléments. Chacun des composants de l?architecture est expliqué et des exemples présentent la façon d?utiliser les classes fournies.

L?architecture modèle/vue

MVC (modèle-vue-contrôleur) est un design pattern provenant de Smalltalk souvent utilisé pour la construction d?interfaces utilisateur. Dans le livre Design Patterns, les auteurs écrivent :

Le MVC se compose de trois types d?objets. Le modèle est l?objet application, la vue en est sa représentation à l?écran, et le contrôleur définit la façon dont l?interface utilisateur réagit aux actions de l?utilisateur. Avant le MVC, la conception des interfaces utilisateur tendait à réunir ces objets. Le MVC les découple pour augmenter la flexibilité et la réutilisation.

Si les objets vue et contrôleur sont combinés, le résultat est une architecture modèle/vue. Ceci permet toujours la séparation de la façon dont les données sont stockées et de la façon dont elles sont présentées à l?utilisateur, mais fournit un framework plus simple basé sur les mêmes principes. Cette séparation rend possibles l?affichage des mêmes données dans différentes vues et l?implémentation de nouveaux types de vue, sans modification des structures de données sous-jacentes. Pour permettre une gestion flexible des actions utilisateur, le concept de délégué (delegate) est introduit. L?avantage d?avoir un délégué dans ce framework est que la façon de présenter et d?éditer les données peut être personnalisée.

 L?architecture modèle/vue Le modèle communique avec une source de données, en fournissant une interface pour les autres composants de l?architecture. La nature des communications dépend du type de source de données et de la façon dont le modèle est implémenté. La vue obtient du modèle des index de modèle ; ce sont des références aux données des éléments. En fournissant des index de modèle au modèle, la vue peut retrouver les données des éléments depuis une source de données. Dans les vues standard, un délégué représente les données des éléments. Lorsqu?un élément est édité, le délégué communique directement avec le modèle en utilisant les index de modèle.

En général, les classes modèle/vue peuvent être découpées en trois groupes décrits ci-dessus : modèles, vues et 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 fonctions. Les classes abstraites doivent être dérivées afin de fournir l?ensemble des fonctionnalités attendues par les autres composants ; ceci permet également l?écriture de composants spécialisés.

Les modèles, vues et délégués communiquent entre eux en utilisant des signaux et des slots :

  • les signaux du modèle informent la vue sur les changements de données de la source de données ;
  • les signaux de la vue fournissent des informations sur les actions de l?utilisateur sur les éléments affichés ;
  • les signaux du délégué sont utilisés durant l?édition afin d?informer le modèle et la vue sur 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 qui est utilisée par les vues et les délégués pour accéder aux données. Les données elles-mêmes n?ont pas besoin d?être stockées dans le modèle ; elles peuvent être stockées dans une structure de données ou dans un dépôt fourni par une classe séparée, un fichier, une base de données, ou tout autre composant applicatif.

Les concepts de base entourant les modèles sont présentés dans la section sur Les classes Modèles.

QAbstractItemModel fournit une interface aux données, suffisamment flexible pour manipuler les vues représentant les données sous forme de tables, de listes ou d?arbres. Cependant, lors de l?implémentation de modèles pour des structures de données de type liste ou arbre, les classes QAbstractListModel et QAbstractTableModel sont de meilleurs points de départ car elles fournissent des implémentations par défaut de fonctions communes appropriées. Chacune de ces classes peut être dérivée pour fournir des modèles qui supportent des genres spécialisés de listes et de tables.

Le procédé de dérivation de modèles est discuté dans la section sur La création de nouveaux modèles.

Qt fournit certains modèles prêts à l?emploi qui peuvent être utilisés pour gérer les données des éléments :

Si ces modèles standard ne répondent pas aux besoins, il est possible de dériver QAbstractItemModel, QAbstractListModel ou QAbstractTableModel pour créer ses 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 une table et QTreeView présente les données des éléments dans une liste hiérarchique. Chacune de ces classes est basée sur la classe abstraite de base QAbstractItemView. Bien que ces classes soient des implémentations prêtes à l?emploi, elles peuvent également être dérivées pour fournir des vues personnalisées.

Les vues disponibles sont examinées dans la section Les classes de vues.

Délégués

QAbstractItemDelegate est la classe abstraite de base pour les délégués dans le framework modèle/vue. Depuis Qt 4.4, l?implémentation par défaut du délégué est fournie par QStyledItemDelegate et est utilisée en tant que délégué par défaut des vues standard de Qt. Cependant, QStyledItemDelegate et QItemDelegate sont des alternatives indépendantes pour dessiner et fournir des éditeurs pour les éléments dans les vues. La différence entre eux est que QStyledItemDelegate utilise le style courant pour dessiner ses éléments. Il est donc recommandé d?utiliser QStyledItemDelegate en tant que classe de base lors de l?implémentation de délégués personnalisés ou lorsque l?on travaille avec les feuilles de style Qt.

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

Tri

Il y a deux approches au tri dans l?architecture modèle/vue ; l?approche à choisir dépend du système sous-jacent.

Si le modèle est triable, c?est-à-dire s?il réimplémente la fonction QAbstractItemModel::sort(), QTableView et QTreeView proposent tous les deux une API permettant le tri du modèle par programmation. De plus, il est possible d?autoriser le tri interactif (c?est-à-dire autoriser les utilisateurs à trier les données en cliquant sur les en-têtes de la vue), en connectant le signal QHeaderView::sortIndicatorChanged() respectivement au slot QTableView::sortByColumn() ou au slot QTreeView::sortByColumn().

L?approche alternative, si le modèle ne possède pas l?interface requise ou si on souhaite utiliser une vue de type liste pour présenter les données, est d?utiliser un modèle proxy pour transformer la structure du modèle avant de présenter les données dans la vue. Ceci est couvert en détail dans la section sur Les Modèles Proxy.

Classes de commodité

Un certain nombre de classes de commodité sont dérivées des classes de vue standard, pour servir aux applications reposant sur des classes de vues et de tables basées sur les éléments Qt. Elles n?ont pas vocation à être dérivées mais sont là pour fournir une interface familière aux classes équivalentes dans Qt 3. QListWidget, QTreeWidget et QTableWidget sont des exemples de telles classes ; elles fournissent un comportement similaire aux classes QListBox, QListView et QTable dans Qt 3.

Ces classes sont moins flexibles que les classes de vues, et ne peuvent pas être utilisées avec des modèles arbitraires. Il est recommandé d?utiliser une approche modèle/vue pour manipuler les données dans les vues, à moins qu?un ensemble de classes basées sur les éléments soit fortement requis.

Si l?on souhaite tirer avantage des fonctionnalités fournies par l?approche modèle/vue tout en utilisant une interface basée sur les éléments, on peut utiliser les classes de vues telles que QListView, QTableView et QTreeView avec QStandardItemModel.

Utilisation des modèles et des vues

Les sections suivantes expliquent comment utiliser le design pattern modèle/vue dans Qt. Chaque section comprend un exemple, suivi d'une section montrant comment créer de nouveaux composants.

Deux modèles inclus dans Qt

QStandardItemModel et QFileSystemModel sont deux des modèles standard fournis par Qt. QStandardItemModel est un modèle multi-usage qui peut être utilisé pour représenter différentes structures de données utiles aux vues de type liste, table et arbre. Ce modèle contient également les données des éléments. QFileSystemModel est un modèle qui contient les informations sur le contenu d?un répertoire. En conséquence, il ne contient aucune donnée à proprement parler, mais représente simplement les fichiers et répertoires du système de fichiers local.

QFileSystemModel fournit un modèle prêt à l?emploi et peut être facilement configuré pour utiliser des données existantes. En utilisant ce modèle, on peut voir comment configurer un modèle pour l?utiliser avec des modèles tout prêts et explorer les façons de manipuler des données en utilisant des index de modèle.

Utilisation des vues avec un modèle existant

Les classes QListView et QTreeView sont les vues les plus adaptées au QFileSystemModel. L?exemple présenté ci-dessous affiche le contenu d?un répertoire dans une vue de type arbre, à côté de la même information dans une vue de type liste. Les vues partagent la sélection de l?utilisateur de sorte que les éléments sélectionnés soient mis en évidence dans les deux vues.

image

On configure un QFileSystemModel pour qu?il soit prêt à l?emploi et on crée des vues pour afficher le contenu d?un répertoire. Ceci montre la façon la plus simple d?utiliser un modèle. La construction et l?utilisation du modèle sont réalisées depuis une simple 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 depuis un certain système de fichiers. L?appel à setRootPath() indique au modèle quel lecteur du système de fichiers doit être exposé aux vues.

On crée 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 façon que les autres widgets. La configuration d?une vue pour afficher les éléments du modèle est faite simplement en appelant sa fonction setModel() avec le répertoire modèle comme argument. On filtre les données fournies par le modèle en appelant la fonction setRootIndex() pour chaque vue, en passant un index de modèle adapté depuis le modèle du système de fichiers pour le répertoire courant.

La fonction index() utilisée dans ce cas est unique au QFileSystemModel ; on la renseigne avec un répertoire et elle retourne un index de modèle. Les index de modèle sont présentés dans Les classes modèles.

Le reste de la fonction affiche simplement les vues dans un widget séparateur (splitter) et exécute la boucle d?évènements de l?application :

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

Dans le code ci-dessus, on a négligé de mentionner comment gérer la sélection des éléments. Ce sujet est couvert de manière détaillée dans la section sur La gestion des sélections dans les Vues d?éléments.

Classes modèles

Avant d'examiner la façon dont les sélections sont gérées, il est utile d'examiner les concepts utilisés dans le framework modèle/vue.

Concepts de base

Dans l?architecture modèle/vue, le modèle fournit une interface standard que les vues et les délégués utilisent pour accéder aux données. Dans Qt, l?interface standard est définie dans la classe QAbstractItemModel. Peu importe la manière dont les données des éléments sont stockées dans la structure de données sous-jacente, toutes les sous-classes de QAbstractItemModel représentent les données comme des structures hiérarchiques contenant des tables d?éléments. Les vues utilisent cette convention pour accéder aux données des éléments du modèle, mais elles ne sont pas restreintes dans la façon de présenter ces informations à l?utilisateur.

image

Les modèles informent également toutes les vues attachées sur les changements des données grâce au mécanisme de signaux et de slots.

Cette section décrit certains concepts de base qui sont fondamentaux dans la façon dont les données des éléments sont accessibles aux autres composants via une classe modèle. Des concepts plus avancés sont présentés dans les sections suivantes.

Index de modèle

Pour s?assurer que la représentation des données soit séparée de la façon dont on y accède, le concept d?index de modèle est introduit. Chaque information qui peut ê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 données des éléments à afficher.

En conséquence, seul le modèle a besoin de savoir comment obtenir les données, et le type de données géré 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 afin d?empêcher la confusion lorsque l?on travaille avec plusieurs modèles.

 QAbstractItemModel *model = index.model();

Les index de modèle fournissent des références temporaires vers les informations et peuvent être utilisés pour retrouver ou modifier les données via le modèle. Étant donné que les modèles ont la possibilité de réorganiser leurs structures internes de temps en temps, les index de modèle peuvent devenir invalides et ne devraient pas être stockés. Si une référence à long terme vers une information est requise, un index de modèle persistant doit être créé. Ceci fournit une référence vers l?information que le modèle garde à 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 fournies au modèle : un numéro de ligne, un numéro de colonne et l?index de modèle de l?é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, on peut accéder à un modèle comme à une simple table dans laquelle les éléments sont localisés par leurs numéros de ligne et de colonne. Ceci ne signifie pas que les données sous-jacentes sont stockées dans une structure tableau ; l?utilisation des numéros de ligne et de colonne est simplement une convention permettant aux composants de communiquer entre eux. On peut retrouver l?information de n?importe quel élément donné en spécifiant ses numéros de ligne et de colonne au modèle, et on reçoit en retour un index qui représente l?élément :

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

Les modèles qui fournissent des interfaces pour des structures de données simples et à un seul niveau, comme des listes ou des tables, n?ont pas besoin d?informations supplémentaires, mais, comme le code ci-dessus le montre, on a besoin de fournir des informations supplémentaires pour obtenir un index de modèle.

image Lignes et colonnes Ce diagramme montre une représentation d?un modèle de table basique dans lequel chaque élément est localisé par une paire de numéros de ligne et de colonne. On obtient un index de modèle qui se réfère à un élément de données en fournissant les numéros de ligne et de colonne adéquats 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 dans un modèle sont toujours référencés en spécifiant QModelIndex() comme étant leur élément parent.\\Ceci est abordé dans la section suivante.

Parents des éléments

L?interface, de type table, vers les données des éléments fournis par les modèles est idéale lors de l?utilisation de données dans une vue table ou liste ; le système de numéro de ligne et de colonne correspond exactement à la façon dont les vues affichent les éléments. Cependant, les structures telles que les vues arbre exigent que le modèle présente une interface plus flexible pour les éléments qu?il contient. En conséquence, 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 niveau supérieur dans une vue arbre peut contenir une autre liste d?éléments.

Lorsqu?on demande un index pour un élément du modèle, on doit fournir certaines informations sur le parent de l?élément. À l?extérieur du modèle, la seule façon de se référer à un élément est via un index de modèle, donc un index de modèle parent doit également être fourni :

 QModelIndex index = model->index(row, column, parent);
image Parents, lignes et colonnes Ce diagramme est une représentation d?un modèle arbre dans lequel chaque élément est référencé 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 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 » possède 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 d?éléments

Les éléments dans un modèle peuvent remplir plusieurs rôles pour les autres composants, autorisant différents types de données à être fournis dans différentes situations. Par exemple, Qt::DisplayRole est utilisé pour accéder à une chaîne qui peut être affichée en tant que texte dans une vue. Typiquement, les éléments contiennent des données pour un certain nombre de rôles, et les rôles standard sont définis par Qt::ItemDataRole.

On peut demander au modèle la donnée d?un élément en lui fournissant l?index de modèle correspondant à l?élément et en spécifiant un rôle, afin d?obtenir le type de donnée souhaitée :

 QVariant value = model->data(index, role);
image Les rôles d?éléments Le rôle indique au modèle à quel type de donnée l?on se réfère. Les vues peuvent afficher les rôles de différentes façons, il est donc important de fournir l?information appropriée pour chaque rôle. La section Création de nouveaux modèles couvre des exemples spécifiques plus en détail.

Les utilisations les plus communes pour les 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 fournir des indications aux vues et aux délégués sur la façon dont elles devraient être présentées à l?utilisateur. Les différents types de vues ont la liberté d?interpréter ou d?ignorer cette information suivant les besoins. Il est également possible de définir des rôles additionnels pour des objectifs spécifiques à l?application.

Résumé
  • Les index de modèle fournissent aux vues et aux délégués des informations sur la localisation des éléments fournis par les modèles, d?une façon indépendante des structures de données sous-jacentes.
  • Les références aux éléments se font par leurs numéros de ligne et de colonne et par l?index de modèle de leur élément parent.
  • Les index de modèle sont construits par les modèles à la demande d?autres composants, comme les vues ou les délégués.
  • Si un index de modèle valide est spécifié en tant qu?élément parent lors de la demande d?un index en utilisant index(), l?index retourné se réfère à un élément sous cet élément parent dans le modèle. L?index obtenu se réfère à un enfant de cet élément.
  • Si un index de modèle invalide est spécifié en tant qu?élément parent lors de la demande d?un index en utilisant index(), l?index retourné se réfère à un élément de premier niveau dans le modèle.
  • Le rôle permet la distinction entre les différents types de données associés à un élément.

Utilisation des index de modèle

Pour montrer comment les données peuvent être retrouvées depuis un modèle en utilisant les index de modèle, on configure un QFileSystemModel sans vue associée, et on affiche les noms des fichiers et des répertoires dans un widget. Bien que ce ne soit pas la façon normale d?utiliser un modèle, cela montre les conventions utilisées par les modèles dans le cas de l'utilisation des index de modèle.

On construit un modèle de système de fichiers de la façon suivante :

     QFileSystemModel *model = new QFileSystemModel;
     QModelIndex parentIndex = model->index(QDir::currentPath());
     int numRows = model->rowCount(parentIndex);

Dans ce cas, on configure un QFileSystemModel par défaut, on obtient un index de parent en utilisant l?implémentation spécifique de index() fournie par ce modèle, et on compte le nombre de lignes du modèle en utilisant la fonction rowCount().

Par souci de simplicité, on s?intéresse uniquement aux éléments de la première colonne du modèle. On examine chaque ligne tour à tour, pour obtenir un index de modèle pour le premier élément de chaque ligne, et on lit les données stockées dans cet élément du modèle.

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

Pour obtenir un index de modèle, on spécifie 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, et ce pour tous les éléments désirés. Le texte stocké dans chaque élément est retrouvé en utilisant la fonction data() du modèle. On spécifie l?index de modèle et le DisplayRole afin d?obtenir les données de l?élément sous forme de chaîne.

         QString text = model->data(index, Qt::DisplayRole).toString();
         // Affiche le texte dans un widget.
 
     }

L?exemple ci-dessus explique les principes de base utilisés pour retrouver les données depuis un modèle :

  • les dimensions d?un modèle peuvent être trouvées en utilisant les fonctions rowCount() et columnCount() ;
  • les index de modèle sont utilisés pour accéder aux éléments dans le modèle ;
  • pour accéder aux éléments de premier niveau dans le modèle, on spécifie un index de modèle nul en tant qu?index de modèle parent, avec QModelIndex() ;
  • les éléments contiennent des données pour différents rôles. Pour obtenir les données pour un rôle particulier, l?index de modèle et le rôle doivent tous les 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 La création de nouveaux Modèles, on explique comment faire en créant un modèle pratique et prêt à l?emploi pour stocker des listes de chaînes.

Classes de vues

Concepts

Dans l?architecture modèle/vue, la vue obtient les données depuis le modèle et les présente à l?utilisateur. La façon dont sont représentées les données n?a pas besoin de ressembler à la représentation des données fournies par le modèle, et peut être complètement différente de la structure de données sous-jacente utilisée pour stocker les données.

La séparation entre le contenu et la représentation est obtenue par l?utilisation d?une interface de modèle standard fournie par QAbstractItemModel, une interface de vue standard fournie par QAbstractItemView, et l?utilisation d?index de modèle qui représentent les données des éléments d?une manière générique. Typiquement, les vues s?occupent de la disposition globale des données obtenues du modèle. Elles peuvent représenter les éléments individuels de données directement, ou utiliser des délégués pour gérer à la fois les fonctionnalités de rendu et d?édition.

En plus de présenter les données, les vues gèrent la navigation entre éléments et certains aspects de la sélection d?éléments. Les vues implémentent également des fonctionnalités d?interface utilisateur de base, telles que les menus contextuels et le glisser-déposer. Une vue peut fournir des fonctionnalités d?édition par défaut pour les éléments, ou elle peut travailler avec un délégué qui fournit 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 une trace des éléments que l?utilisateur a sélectionnés, à travers l?utilisation de sélections qui peuvent être gérées séparément pour chaque vue, ou partagées entre plusieurs vues.

Certaines vues, telles que QTableView et QTreeView, affichent des en-têtes en plus des données. Ceux-ci sont également implémentés par une classe de vue : QHeaderView. Généralement, les en-têtes accèdent au même modèle que la vue qui les contient. Ils retrouvent les données depuis le modèle en utilisant la fonction QAbstractItemModel::headerData() et affichent généralement les informations d?en-tête sous forme de libellés. De nouveaux en-têtes peuvent être dérivés de la classe QHeaderView pour obtenir des libellés plus spécialisés pour les vues.

Utilisation d?une vue existante

Qt fournit trois classes de vues prêtes à l?emploi qui présentent les données de modèles d?une façon familière à la plupart des utilisateurs. QListView peut afficher les éléments d?un modèle sous forme d?une simple liste ou sous forme d?une vue en icônes classique. QTreeView affiche les éléments de modèles sous forme d?une hiérarchie de listes, permettant de représenter des structures profondément imbriquées de façon compacte. QTableView présente les éléments de modèles sous forme de table, de façon similaire à un tableur.

image

Le comportement par défaut des vues standard montrées ci-dessus est suffisant dans la plupart des cas. Elles fournissent des fonctionnalités d?édition de base et peuvent être personnalisées pour répondre aux besoins d?interfaces utilisateurs plus spécialisées.

Utilisation d?un modèle

On souhaite récupérer le modèle de liste de chaînes que nous avons créé en tant que modèle exemple, le renseigner avec quelques données et construire une vue pour afficher le contenu du modèle. Tout ceci peut être réalisé à l?intérieur d?une seule fonction :

 int main(int argc, char *argv[])
 {
     QApplication app(argc, argv);
 
 // Non indenté à des fins de citation
 QStringList numbers;
 numbers << "One" << "Two" << "Three" << "Four" << "Five";
 
 QAbstractItemModel *model = new StringListModel(numbers);

Il faut noter que la StringListModel est déclarée en tant que QAbstractItemModel. Ceci autorise l?utilisation de l?interface abstraite du modèle et assure que le code fonctionnera toujours même si le modèle liste de chaînes est remplacé par un autre modèle.

La vue liste fournie par QListView est suffisante pour présenter les éléments dans le modèle liste de chaînes. On construit la vue et on renseigne le modèle en utilisant les lignes de code suivantes :

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

La vue est affichée de la manière normale :

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

La vue présente le contenu du modèle, en accédant aux données via l?interface du modèle. Lorsque l?utilisateur essaye d?éditer un élément, la vue utilise un délégué par défaut pour fournir un widget éditeur.

image

L?image ci-dessus montre comment une QListView représente les données dans un modèle liste de chaînes. Étant donné que le modèle est éditable, la vue autorise automatiquement chaque élément de la liste à être édité par le délégué par défaut.

Vues multiples d'un modèle

Fournir plusieurs vues sur le même modèle se fait simplement en définissant le même modèle pour chaque vue. Dans le code suivant, on crée deux vues table, chacune utilisant le même modèle table simple créé pour cet exemple :

     QTableView *firstTableView = new QTableView;
     QTableView *secondTableView = new QTableView;
 
     firstTableView->setModel(model);
     secondTableView->setModel(model);

L?utilisation des signaux et de slots dans l?architecture modèle/vue implique que les changements sur le modèle peuvent être propagés à toutes les vues attachées, garantissant que l'accès se fait bien sur les mêmes données quelle que soit la vue utilisée.

image

L?image ci-dessus montre deux vues différentes sur le même modèle, chacune contenant un certain nombre d?éléments sélectionnés. Bien que les données du modèle soient cohérentes dans les deux vues, chaque vue maintient son propre modèle de sélection interne. Ceci peut être utile dans certaines situations, mais, pour la plupart des applications, une sélection partagée est préférable.

Gestion des sélections d?éléments

Le mécanisme de gestion des sélections d?éléments à l?intérieur des vues est fourni par la classe QItemSelectionModel. Toutes les vues standard construisent leurs propres modèles de sélection par défaut et interagissent avec eux de la manière normale. 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 la fonction setSelectionModel(). La possibilité de contrôler le modèle de sélection utilisé par une vue peut être utile lorsque l?on souhaite fournir de multiples vues cohérentes sur le même modèle de donnée.

En général, à moins que l?on souhaite dériver un modèle ou une vue, il n?est pas nécessaire de manipuler le contenu de la sélection directement. Toutefois, l?interface du modèle de sélection est accessible si nécessaire, ceci est expliqué dans la section Gestion des sélections dans une vue d?éléments.

Partage de sélections entre plusieurs vues

Bien qu?il soit pratique que les classes de vues fournissent leurs propres modèles de sélection par défaut, lorsque l?on utilise plusieurs vues sur le même modèle il est souvent préférable qu'à la fois les données du modèle et la sélection de l?utilisateur soient cohérentes dans toutes les vues. Étant donné que les classes de vues autorisent le remplacement de leur modèle de sélection interne, on peut obtenir une sélection unifiée entre les vues avec la ligne suivante :

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

On fournit à la seconde vue le modèle de sélection de la première vue. Les deux vues opèrent maintenant sur le même modèle de sélection, conservant la synchronisation à la fois des données et des éléments sélectionnés.

image

Dans l?exemple ci-dessus, deux vues du même type sont utilisées pour afficher les données du même modèle. Toutefois, si deux types de vues étaient utilisés, les éléments sélectionnés pourraient être représentés de manières très différentes dans chacune des vues ; par exemple, une sélection contiguë dans une vue table peut être représentée comme un ensemble fragmenté d?éléments soulignés dans une vue arbre.

Classes de délégués

Concepts

Contrairement au design pattern modèle-vue-contrôleur, le design pattern modèle/vue n?inclut pas de composant complètement séparé pour la gestion des interactions avec l?utilisateur. En général, la vue est responsable de la présentation des données du modèle et du traitement des saisies utilisateur. Pour permettre plus de flexibilité dans la façon dont ces saisies sont obtenues, l?interaction est réalisée par des délégués. Ces composants fournissent des fonctionnalités de saisie et sont aussi responsables du rendu des éléments individuels dans certaines vues. L?interface standard pour le contrôle des délégués est définie dans la classe QAbstractItemDelegate.

Les délégués doivent être capables de présenter leur contenu par eux-mêmes en implémentant les fonctions paint() et sizeHint(). Toutefois, les délégués simples basés sur des widgets peuvent dériver de QItemDelegate à la place de QAbstractItemDelegate et tirer avantage des implémentations par défaut de ces fonctions.

Les éditeurs pour délégués peuvent être implémentés soit en utilisant des widgets pour gérer le procédé d?édition, soit en gérant directement les événements. La première approche est couverte plus loin dans cette section et est aussi présentée dans l?exemple Délégué pour une spinbox.

L?exemple Pixelator montre comment créer un délégué personnalisé qui réalise un rendu spécialisé pour une vue table.

Utilisation d?un délégué existant

Les vues standard fournies par Qt utilisent des instances de QItemDelegate pour fournir des fonctionnalités d?édition. Cette implémentation par défaut de l?interface du délégué représente 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 façon dont ils sont interprétés est décrite dans la documentation de QItemDelegate.

Le délégué utilisé par une vue est retourné par la fonction itemDelegate(). La fonction setItemDelegate() permet la configuration d?un délégué personnalisé pour une vue standard, et il est nécessaire d?utiliser cette fonction pour renseigner le délégué sur une vue personnalisée.

Un délégué simple

Le délégué implémenté ici utilise une QSpinBox pour fournir des fonctionnalités d?édition et est surtout conçu pour une utilisation avec des modèles affichant des entiers. Bien que l?on ait configuré un modèle table personnalisé basé sur des entiers dans ce but, on aurait pu aisément utiliser un QStandardItemModel à la place, étant donné que le délégué personnalisé effectue un contrôle sur les saisies. On construit une vue table pour afficher le contenu du modèle et elle va utiliser le délégué personnalisé pour l?édition.

image

On dérive le délégué de QItemDelegate car on ne souhaite pas écrire de fonctionnalités d?affichage personnalisées. Toutefois, on doit quand même fournir des fonctions pour gérer le widget éditeur :

 class SpinBoxDelegate : public QItemDelegate
 {
     Q_OBJECT
 
 public:
     SpinBoxDelegate(QObject *parent = 0);
 
     QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
                           const QModelIndex &index) const;
 
     void setEditorData(QWidget *editor, const QModelIndex &index) const;
     void setModelData(QWidget *editor, QAbstractItemModel *model,
                       const QModelIndex &index) const;
 
     void updateEditorGeometry(QWidget *editor,
         const QStyleOptionViewItem &option, const QModelIndex &index) const;
 };

Il faut noter qu?aucun widget éditeur n?est renseigné lors de la construction du délégué. On ne construit le widget éditeur que lorsque c'est nécessaire.

Fourniture d?un éditeur

Dans cet exemple, lorsque la vue table a besoin de fournir un éditeur, on demande au délégué de fournir un widget é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 configurer un widget éditeur adapté :

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

Il faut noter que l?on n'a pas besoin de garder un pointeur vers le widget éditeur, car la vue prend la responsabilité de le détruire lorsqu?il n?est plus nécessaire.

On installe le filtre d?événements par défaut du délégué sur l?éditeur pour qu?il fournisse les raccourcis d?édition standard que l?utilisateur attend. Des raccourcis supplémentaires peuvent être ajoutés à l?éditeur pour autoriser un comportement plus élaboré ; ceci est présenté dans la section sur les Indications d?édition.

La vue s?assure que les données et la géométrie de l?éditeur sont correctement configurées en appelant des fonctions qui seront définies plus tard dans cet objectif. On peut créer différents éditeurs en fonction de l?index de modèle fourni par la vue. Par exemple, si on a une colonne d?entiers et une colonne de chaînes, on peut retourner soit un QSpinBox soit un QLineEdit, en fonction de la colonne en cours d?édition.

Le délégué doit fournir une fonction pour copier les données du modèle dans l?éditeur. Dans cet exemple, on lit les données stockées dans le rôle d'affichage et on affecte la valeur à la spinbox en conséquence.

 void SpinBoxDelegate::setEditorData(QWidget *editor,
                                     const QModelIndex &index) const
 {
     int value = index.model()->data(index, Qt::EditRole).toInt();
 
     QSpinBox *spinBox = static_cast<QSpinBox*>(editor);
     spinBox->setValeur(value);
 }

Dans cet exemple, on sait que le widget éditeur est une spinbox, mais on aurait pu fournir différents éditeurs pour les différents types de données du modèle ; dans ce cas on aurait eu besoin de forcer le type du widget vers le type approprié avant d?accéder à ses fonctions membres.

Soumettre des données au modèle

Lorsque l?utilisateur a fini d?éditer la valeur dans la spinbox, la vue demande au rôle de stocker la valeur éditée dans le modèle en appelant la fonction setModelData().

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

Étant donné que la vue gère le widget éditeur pour le délégué, on a seulement besoin de mettre à jour le modèle avec le contenu fourni par l?éditeur. Dans ce cas, on s?assure que la spin box est à jour, et on actualise le modèle avec la valeur qu?elle contient en utilisant l?index spécifié.

La classe QItemDelegate standard informe la vue lorsqu'elle a fini l?édition en émettant le signal closeEditor(). La vue s?assure que le widget éditeur est fermé et détruit. Dans cet exemple, on fournit uniquement des fonctionnalités d?édition simples, donc on n?a pas besoin d?émettre ce signal.

Toutes les opérations sur les données sont réalisées à travers l?interface fournie par QAbstractItemModel. Cela rend le délégué généralement indépendant du type de données qu?il manipule, mais certaines hypothèses doivent être faites afin d?utiliser certains types de widgets éditeurs. Dans cet exemple, on a supposé que le modèle contenait toujours des valeurs entières, mais on peut tout de même utiliser ce délégué avec différents types de modèles car QVariant fournit des valeurs par défauts raisonnables pour les données inattendues.

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

Le délégué a la responsabilité de gérer la géométrie de l?éditeur. La géométrie doit être configurée lors de la création de l?éditeur, et lorsque la taille ou la position des éléments de la vue sont modifiées. Heureusement, les vues fournissent toutes les informations géométriques nécessaires à l?intérieur de l?objet option de vue.

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

Dans ce cas, on a simplement utilisé l?information géométrique fournie par l?option de vue dans le rectangle de l?élément. Un délégué qui représente les éléments en plusieurs parties n'aurait pas directement utilisé le rectangle de l?élément. Il aurait positionné l?éditeur en relation avec les autres parties de l?élément.

Indications d?édition

Après l?édition, les délégués peuvent fournir des indications aux autres composants sur le résultat du procédé d?édition, et fournir des indications pour assister des opérations d?édition ultérieures. Ceci est réalisé en émettant le signal closeEditor() avec une information adaptée. Ceci est pris en charge par le filtre d?événements par défaut de QItemDelegate, qui a été installé sur la spinbox lors de sa construction.

Le comportement de la spinbox peut être ajusté pour la rendre plus conviviale. Dans le filtre d?événements par défaut fourni par QItemDelegate, si l?utilisateur appuie sur Return pour confirmer son choix dans la spinbox, le délégué publie la valeur vers le modèle et ferme la spinbox. On peut modifier ce comportement en installant notre propre filtre d?événements sur la spinbox et fournir des indications d?édition qui correspondent à nos besoins ; par exemple, on peut émettre le signal closeEditor() avec l?information EditNextItem pour démarrer automatiquement l?édition du prochain élément dans la vue.

Une autre approche qui ne nécessite pas l?utilisation d?un filtre d?événements est de fournir notre propre widget éditeur, par exemple en dérivant par commodité QSpinBox. Cette approche alternative donne un plus grand contrôle sur la façon d?agir du widget éditeur, au détriment de l?écriture de code supplémentaire. Il est généralement plus simple d?installer un filtre d?événements sur un délégué si on a besoin de personnaliser le comportement d?un widget éditeur standard de Qt.

Les délégués ne sont pas obligés d?émettre ces indications, mais ceux qui ne le font pas seront moins bien intégrés dans les applications et seront moins réutilisables que ceux qui émettent des indications qui prennent en charge les actions d?éditions courantes.

Gestion des sélections dans des vues d'éléments

Concepts

Le modèle de sélection utilisé dans les classes de vues d?éléments offre de nombreuses améliorations par rapport au modèle de sélection dans Qt 3. Il fournit une description plus générale des sélections, basée sur des fonctionnalités de l?architecture modèle/vue. Bien que les classes standard pour la manipulation des sélections soient suffisantes pour les vues d?éléments fournies, le modèle de sélection permet de créer des modèles de sélection personnalisés répondant aux besoins de nos propres modèles et vues d?éléments.

L?information sur les éléments sélectionnés dans une vue est stockée dans une instance de la classe QItemSelectionModel. Cela stocke les index de modèle des éléments dans un seul modèle, indépendamment de toute vue. Étant donné qu?il peut y avoir de nombreuses vues sur un même modèle, il est possible de partager des informations entre plusieurs vues, autorisant l?application à montrer plusieurs vues de façon cohérente.

Les sélections sont constituées de plages de sélection. Elles stockent efficacement l?information sur de grandes sélections d?éléments en n?enregistrant que les index de début et de fin pour chaque plage d?éléments sélectionnés. Les sélections non contiguës d?éléments sont construites en utilisant plusieurs plages de sélection qui décrivent la sélection.

Les sélections sont appliquées à une collection d?index de modèle détenue par un modèle de sélection. La plus récente sélection d?éléments appliquée est connue comme étant la sélection courante. Les effets de cette sélection peuvent être modifiés même après leur application à travers l?utilisation de certains types de commandes de sélection. Ceci est présenté 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 courant et sélectionné en même temps. La vue a la responsabilité de garantir qu?il y a toujours un élément courant, car la navigation au clavier, par exemple, nécessite un élément courant.

La table ci-dessous souligne les différences entre élément courant et é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 courant sera modifié par la navigation au clavier ou par des clics de bouton 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 ces éléments.
L?élément courant sera édité si la touche d?édition, F2, est pressée, ou s'il est double-cliqué (à condition que l?édition soit autorisée). L?élément courant peut être utilisé avec un ancrage pour spécifier qu?une plage 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 focus. Les éléments sélectionnés sont indiqués avec le rectangle de sélection.

Lorsque l?on manipule des sélections, il est souvent utile de penser à QItemSelectionModel comme étant un enregistrement de l?état de sélection de tous les éléments dans un modèle. Une fois que le modèle de sélection est configuré, les collections d?éléments peuvent être sélectionnées, désélectionnées, ou leur état de sélection peut être modifié sans besoin de savoir quel élément était déjà sélectionné. Les index de tous les éléments sélectionnés peuvent être retrouvés à tout moment, et les autres composants peuvent être informés des changements du modèle de sélection via le mécanisme de signaux et de slots.

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 seule vue peut être obtenu en utilisant la fonction selectionModel() de la vue, et être partagé entre plusieurs vues avec la fonction setSelectionModel(), ce qui fait que la construction de nouveaux modèles de sélection n'est en général pas requise.

Une sélection est créée en spécifiant un modèle et une paire d?index de modèle à une sélection QItemSelection. Celle-ci utilise les index pour se référer aux éléments du modèle donné, et les interprète comme étant les éléments en haut à gauche et en bas à droite du bloc d?éléments sélectionnés. Appliquer la sélection à des éléments du modèle nécessite de soumettre la sélection à un modèle de sélection ; ceci peut être réalisé de plusieurs façons, chacune ayant un effet différent sur les sélections déjà présentes dans le modèle de sélection.

Sélection d?éléments

Pour présenter certaines des principales fonctionnalités des sélections, nous construisons une instance d?un modèle table personnalisé avec 32 éléments, et ouvrons une vue 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 table est récupéré pour un usage ultérieur. On ne modifie aucun élément dans le modèle, mais à la place on sélectionne quelques éléments que la vue va afficher en haut à gauche de la table. Pour faire ceci, on a besoin de retrouver l?index de 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 les changements correspondants dans la table vue, on a besoin de construire un objet sélection et de 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 en utilisant la commande définie par une combinaison de drapeaux de sélection. Dans ce cas, les drapeaux utilisés font que les éléments enregistrés dans l?objet sélection sont inclus dans le modèle de sélection, indépendamment de leur état précédent. La sélection résultante est montrée par la vue.

image

La sélection d?éléments peut être modifiée en utilisant diverses opérations qui sont définies par les indicateurs 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 de différents indicateurs de sélection pour manipuler les éléments sélectionnés sera décrite lorsque nous étudierons la manière de mettre à jour une sélection.

Lecture de l?état de sélection

Les index de modèle stockés dans le modèle de sélection peuvent être lus en utilisant la fonction selectedIndexes(). Elle retourne une liste non triée des index de modèle que l?on peut parcourir du moment que l?on sait à quel modèle elles appartiennent :

     QModelIndexList indexes = selectionModel->selectedIndexes();
     QModelIndex index;
 
     foreach(index, indexes) {
         QString text = QString("(%1,%2)").arg(index.row()).arg(index.column());
         model->setData(index, text);
     }

Le code ci-dessus utilise le mot-clé de commodité Qt foreach pour parcourir et modifier les éléments correspondant aux index retourné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. Ceux-ci informent les autres composants sur les changements de la sélection dans son ensemble et de l?élément ayant actuellement le focus dans le modèle. On peut 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 : un contient une liste d?index qui correspond aux éléments nouvellement sélectionnés ; l?autre contient une liste d?index qui correspond aux éléments nouvellement désélectionnés.

Dans le code suivant, on fournit un slot qui reçoit le signal selectionChanged(), remplit les éléments sélectionnés avec une chaîne et vide le contenu des éléments désélectionnés.

 void MainWindow::updateSelection(const QItemSelection &selected,
     const QItemSelection &deselected)
 {
     QModelIndex index;
     QModelIndexList items = selected.indexes();
 
     foreach (index, items) {
         QString text = QString("(%1,%2)").arg(index.row()).arg(index.column());
         model->setData(index, text);
     }
 
     items = deselected.indexes();
 
     foreach (index, items)
         model->setData(index, "");
 }

On peut garder la trace de l?élément ayant actuellement le focus en connectant le signal currentChanged() à un slot qui est appelé avec deux index de modèle. Ceux-ci correspondent à l?élément ayant précédemment le focus et à l?élément ayant actuellement le focus.

Dans le code suivant, on fournit un slot qui reçoit le signal currentChanged() et utilise l?information fournie pour mettre à jour la barre d?état de la QMainWindow :

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

La surveillance des sélections faites par l?utilisateur est simple avec ces signaux, mais on peut également mettre à jour directement le modèle de sélection.

Mise à jour d?une sélection

Les commandes de sélections sont fournies par une combinaison de drapeaux de sélection, définis par QItemSelectionModel::SelectionFlag. Chaque drapeau de sélection dit au modèle de sélection comment mettre à jour son enregistrement interne des éléments sélectionnés lorsqu'une des fonctions select() est appelée. Le drapeau le plus communément appelé est Select, qui demande au modèle de sélection d?enregistrer les éléments spécifiés comme étant sélectionnés. Le drapeau Toggle (bascule) demande au modèle de sélection d?inverser l?état des éléments spécifiés, en sélectionnant les éléments désélectionnés et en désélectionnant les éléments sélectionnés. Le drapeau Deselect désélectionne tous les éléments spécifiés.

Les éléments individuels dans le 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, on applique une seconde sélection d?éléments au modèle table montré 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);

Le résultat de cette opération est affiché dans la vue table, ce qui permet de visualiser facilement ce qui a été réalisé :

image

Par défaut, les commandes de sélection n?opèrent que sur les éléments individuels spécifiés par les index de modèle. Toutefois, le drapeau utilisé pour décrire la commande de sélection peut être combiné avec des drapeaux supplémentaires pour modifier des lignes et colonnes entières. Par exemple si on appelle select() avec un seul index mais avec une commande qui est une combinaison des drapeaux Select et Rows, la ligne entière contenant l?élément mentionné est sélectionnée. Le code suivant montre 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 seulement quatre index soient fournis au modèle de sélection, l?utilisation des drapeaux de sélection Columns et Rows implique que deux colonnes et deux lignes sont sélectionnées. L?image suivante montre le résultat de ces deux sélections :

image

Les commandes effectuées sur le modèle de l?exemple ont toutes impliqué une sélection d?éléments dans le modèle. Il est également possible de vider la sélection ou de remplacer la sélection courante par une nouvelle.

Pour remplacer la sélection courante par une nouvelle sélection, on combine les drapeaux de l?autre sélection avec le drapeau Current. Une commande utilisant ce drapeau demande au modèle de sélection de remplacer sa collection d?index de modèle courante par celle spécifiée dans l?appel à select(). Pour vider toutes les sélections avant d?en ajouter de nouvelles, on combine les drapeaux de l?autre sélection avec le drapeau Clear. Ceci a pour effet de remettre à zéro la collection d?index de modèle du modèle de sélection.

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

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

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

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

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

Ceci doit être effectué pour tous les niveaux du modèle. Pour les éléments de premier niveau, on définit 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 autorise les modèles à être créés en tirant profit des vues existantes. Cette approche permet de présenter les données depuis une grande variété de sources en utilisant les composants d?interface graphique standard, tels que QTableView et QTreeView.

La classe QAbstractItemModel fournit une interface dont la flexibilité permet de prendre en charge des sources de données organisant les informations dans des structures hiérarchiques, et donnant la possibilité d'insérer, supprimer, modifier ou trier les données. Elle fournit également la prise en charge des opérations de glisser-déposer.

Les classes QAbstractListModel et QAbstractTableModel fournissent la prise en charge des interfaces de structures de données non hiérarchiques plus simples, et sont plus simples à utiliser en tant que point de départ pour de simples modèles liste et table.

Dans cette section, on crée un modèle simple en lecture seule pour explorer les principes de base de l?architecture modèle/vue. Plus loin dans cette section, on adapte ce modèle simple afin que les éléments puissent être modifiés par l?utilisateur.

Pour un exemple de modèle plus complexe, voir l?exemple Modèle arbre simple.

Les exigences pour dériver de QAbstractItemModel sont décrites plus en détail dans le document Référence sur la dérivation de modèle.

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 considérer quel type de modèle devrait être utilisé pour fournir une interface vers les données. Si la structure de données peut être représentée comme une liste ou une table d?éléments, on peut dériver QAbstractListModel ou QAbstractTableModel étant donné que ces classes fournissent des implémentations par défaut convenables pour de nombreuses fonctions.

Toutefois, si la structure de données sous-jacente ne peut être représentée que par une structure d?arbre hiérarchique, il est nécessaire de dériver QAbstractItemModel. Cette approche est choisie dans l?exemple Un modèle arbre simple.

Dans cette section, on implémente un modèle simple basé sur une liste de chaînes, de sorte que QAbstractListModel fournisse une classe de base idéale sur laquelle bâtir.

Quelle que soit la forme prise par la structure de données sous-jacente, c?est généralement une bonne idée de compléter l?API QAbstractItemModel standard dans des modèles spécialisés pour permettre un accès plus naturel à la structure de données sous-jacente. Ceci rend plus simple de peupler le modèle avec des données, tout en autorisant toujours d?autres composants modèle/vues généraux à interagir avec elle en utilisant l?API standard. Le modèle décrit ci-dessous fournit un constructeur personnalisé dans ce seul objectif.

Un exemple de modèle en lecture seule

Le modèle implémenté ici est un modèle de données simple, non hiérarchique, en lecture seule, basé sur la classe standard QStringListModel. Il possède une QStringList faisant office de source de données interne, et n?implémente que le nécessaire à la création d?un modèle fonctionnel. Pour faciliter l?implémentation, on dérive de la classe QAbstractListModel car elle définit un comportement par défaut adapté aux modèles liste, et présente une interface plus simple que la classe QAbstractItemModel.

Lors de l?implémentation, il est important de se rappeler que la classe QAbstractItemModel ne stocke aucune donnée par elle-même, et présente seulement une interface que les vues peuvent utiliser pour accéder aux données. Pour un modèle en lecture seule minimaliste, il n'est nécessaire d?implémenter que quelques fonctions, car la plupart des fonctions de l?interface ont des implémentations par défaut. La déclaration de classe est la suivante :

 class StringListModel : public QAbstractListModel
 {
     Q_OBJECT
 
 public:
     StringListModel(const QStringList &strings, QObject *parent = 0)
         : QAbstractListModel(parent), stringList(strings) {}
 
     int rowCount(const QModelIndex &parent = QModelIndex()) const;
     QVariant data(const QModelIndex &index, int role) const;
     QVariant headerData(int section, Qt::Orientation orientation,
                         int role = Qt::DisplayRole) const;
 
 private:
     QStringList stringList;
 };

Mis à part le constructeur du modèle, on a besoin d?implémenter seulement deux fonctions : rowCount(), qui retourne le nombre de lignes du modèle, et data(), qui retourne les données correspondantes à l?index de modèle spécifié.

Les modèles voulant être complets implémentent également headerData() pour fournir aux arbres et aux tables quelque chose à afficher dans leurs en-têtes.

À noter que ce modèle n'est pas hiérarchique, donc on n?a pas à se soucier des relations parent-enfant. Si le modèle était hiérarchique, on aurait également dû implémenter les fonctions index() et parent().

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

Les dimensions du modèle

On veut 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. On implémente la fonction rowCount() de cette façon :

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

Étant donné que le modèle est non hiérarchique, on peut ignorer en toute sécurité l?index de modèle correspondant à l?élément parent. Par défaut, les modèles dérivés de QAbstractListModel ne contiennent qu?une colonne, on n?a donc pas besoin de réimplémenter la fonction columnCount().

Les en-têtes et les données du modèle

Pour les éléments dans la vue, on veut retourner les chaînes dans une liste de chaînes. La fonction data() a la responsabilité de retourner les données des éléments qui correspondent à l?index passé en argument :

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

On retourne un QVariant valide seulement si l?index de modèle fourni est valide, le numéro de ligne est dans l?intervalle d?éléments de la liste de chaînes, et que le rôle demandé est bien pris en charge.

Certaines vues, telles que QTreeView et QTableView, sont capables d?afficher des en-têtes en plus des données d?éléments. Si nos modèles sont affichés dans une vue avec en-têtes, on veut que les en-têtes montrent les numéros de lignes et de colonnes. On peut fournir des informations sur les en-têtes en réimplémentant 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 QString("Column %1").arg(section);
     else
         return QString("Row %1").arg(section);
 }

Encore une fois, on ne retourne un QVariant valide seulement si le rôle est pris en charge. L?orientation de l?en-tête est également prise en compte lorsque l?on décide de la donnée exacte à retourner.

Toutes les vues n'affichent pas d'en-têtes avec les données des éléments, et celles qui le font peuvent être configurées pour les cacher. Cependant, 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 posséder plusieurs rôles, fournissant différentes données en fonction du rôle demandé. Les éléments dans le modèle ne possèdent qu?un seul rôle, DisplayRole, on retourne donc les données des éléments indépendamment du rôle spécifié. Cependant, on peut réutiliser les données fournies pour DisplayRole pour d?autres rôles, par exemple pour le ToolTipRole utilisé par les vues 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 la plupart des applications, un modèle de liste modifiable est plus utile. On peut modifier le modèle en lecture seule pour rendre les éléments modifiables en changeant la fonction data() implémentée pour la lecture seule et en implémentant deux fonctions supplémentaires : flags() et setData(). Les déclarations des fonctions suivantes sont ajoutées à la définition de la classe :

     Qt::ItemFlags flags(const QModelIndex &index) const;
     bool setData(const QModelIndex &index, const QVariant &value,
                  int role = Qt::EditRole);
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 informer le délégué que ses éléments sont modifiables. Ceci est fait en retournant les indicateurs corrects pour chaque élément du modèle ; dans ce cas, on active tous les éléments et on les rend à 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;
 }

À noter que l?on n?a pas besoin de savoir comment le délégué exécute le procédé d?édition réel. On doit seulement fournir au délégué un moyen de modifier les valeurs dans le modèle. Ceci est réalisé grâce à 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);
         return true;
     }
     return false;
 }

Dans ce modèle, l?élément dans la liste de chaînes qui correspond à l?index de modèle est remplacé par la valeur fournie. Cependant, avant de pouvoir modifier la liste de chaînes, on doit s?assurer que l?index de modèle est valide, que l?élément est du type correct et que le rôle est pris en charge. Par convention, on insiste pour que le rôle soit EditRole étant donné qu?il s?agit du rôle utilisé par le délégué standard. Pour les valeurs booléennes, on peut utiliser Qt::CheckStateRole et fixer l?indicateur Qt::ItemIsUserCheckable ; une case à cocher est alors utilisée pour éditer la valeur. La donnée sous-jacente du modèle est la même pour tous les rôles, donc ce détail facilite simplement l?intégration du modèle avec les composants standard.

Lorsque la donnée a été mise à jour, le modèle doit informer les vues qu?une donnée a été modifiée. Ceci est réalisé en émettant le signal dataChanged(). Étant donné qu'un seul élément a été modifié, l?intervalle d?éléments spécifié dans le signal est limité à un seul index de modèle.

La fonction data() a également besoin d?être changée pour ajouter le test sur 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 du modèle. Dans le modèle liste de chaînes, seule la modification du nombre de lignes a un sens, on ne réimplémente donc que les fonctions pour insérer et supprimer des lignes. Ces fonctions sont déclarées dans la définition de la classe :

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

Étant donné que les lignes du modèle correspondent aux chaînes d'une liste, la fonction insertRows() insère un certain nombre de chaînes vides dans la liste de chaînes avant la position spécifiée. Le nombre de lignes insérées est équivalent au nombre de lignes spécifié.

L?index parent est normalement utilisé pour déterminer l?endroit du modèle où les lignes doivent être ajoutées. Dans le cas présent, on n'a qu'un seul niveau de liste, donc on insère simplement 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 et l?index de modèle de leur élément parent. Après avoir modifié la liste de chaînes, le modèle appelle endInsertRows() pour terminer l?opération et informer les autres composants que les dimensions du modèle ont été modifiées, retournant vrai en cas de succès.

La fonction pour 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 spécifiés. On ignore l?index parent pour simplifier notre implémentation, et on supprime simplement 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 que toute donnée sous-jacente soit supprimée, et spécifie la première et dernière ligne à supprimer. Ceci permet aux autres composants d?accéder aux données avant qu?elles ne deviennent indisponibles. Après que les lignes ont été supprimées, le modèle émet endRemoveRows() pour terminer l?opération et informer les autres composants que les dimensions du modèle ont été modifiées

Étapes suivantes

On peut afficher les données fournies par ce modèle ou n?importe quel 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 liste de chaînes, cette vue peut aussi fournir un éditeur par défaut pour que les éléments puissent être modifiés. Les possibilités offertes par les classes de vues standard sont examinées dans Les classes de vues.

Le document Référence sur la dérivation de modèle présente les exigences des classes dérivées de QAbstractItemModel plus en détail, et fournit un guide des fonctions virtuelles à implémenter pour activer différentes fonctionnalités dans différents types de modèles.

Classes de commodité des vues d?éléments

Qt 4 a aussi introduit certains widgets standard pour fournir des widgets conteneurs classiques basés sur les éléments. Ceux-ci se comportent de la même manière que les classes de vues d?éléments dans Qt 3, mais ont été réécrits pour utiliser le framework modèle/vue sous-jacent pour une meilleure performance et maintenabilité. Les anciennes classes de vues d?éléments sont toujours disponibles dans la bibliothèque de compatibilité (voir le Guide de portage pour plus d?informations).

Les widgets basés sur les éléments ont des noms qui reflètent leur usage : QListWidget fournit une liste d?éléments, QTreeWidget affiche une structure arbre à plusieurs niveaux, et QTableWidget fournit une table d?éléments cellules. Chaque classe hérite du comportement de la classe QAbstractItemView qui implémente le comportement commun pour la sélection d?éléments et la gestion des en-têtes.

Les widgets listes

Les listes d?éléments à un seul niveau sont typiquement affichées en utilisant un widget QListWidget et un certain nombre d'éléments QListWidgetItem. Un widget liste est construit comme n?importe quel autre widget :

     QListWidget *listWidget = new QListWidget(this);

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

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

Ils peuvent aussi être construits sans widget liste parent, et ajoutés à une liste plus tard :

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

Chaque élément de la liste peut afficher un libellé textuel et une icône. Les couleurs et la police utilisées peuvent être modifiées pour fournir une apparence personnalisée aux éléments. Les infobulles, les informations d?état et l?aide « Qu?est-ce que c?est ? » sont facilement configurables afin que la liste soit correctement intégrée à l?application.

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

Par défaut, les éléments de la 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 donnés dans Qt::SortOrder pour produire une liste d?éléments triée dans l?ordre alphabétique normal ou inversé :

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

Widgets arbres

Les arbres ou les listes hiérarchiques d?éléments sont fournis par les classes QTreeWidget et QTreeWidgetItem. Chaque élément dans le widget arbre peut avoir ses propres éléments enfants et peut afficher un certain nombre de colonnes d?informations. Les widgets arbres sont créés comme n?importe quel autre widget :

     QTreeWidget *treeWidget = new QTreeWidget(this);

Avant d'ajouter des éléments au widget arbre, le nombre de colonnes doit être fixé. Par exemple, on peut définir deux colonnes et créer un en-tête pour fournir un libellé au sommet de chaque colonne :

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

La façon la plus simple de configurer des libellés pour chaque section est de fournir une liste de chaînes. Pour des en-têtes plus sophistiqués, on peut construire un élément arbre, le décorer comme on veut, et l?utiliser en tant qu?en-tête du widget arbre.

Les éléments de premier niveau dans le widget arbre sont construits avec le widget arbre pour widget parent. Ils peuvent être insérés dans un ordre arbitraire, ou en imposant un ordre particulier en spécifiant l?élément précédent lors de la construction de l?é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);

La gestion des éléments de premier niveau d'un widget arbre est légèrement différente de celle des éléments plus profonds. Les éléments peuvent être supprimés du premier niveau de l?arbre en appelant la fonction takeTopLevelItem() du widget arbre, alors que les éléments de plus bas niveau sont supprimés en appelant la fonction takeChild() de leur élément parent. Les éléments sont insérés au premier niveau de l?arbre avec la fonction insertTopLevelItem(). La fonction insertChild() de l?élément parent est utilisée pour les niveaux inférieurs.

Il est facile de déplacer des éléments entre le premier niveau et les niveaux inférieurs. On a juste besoin de vérifier si les éléments sont de premier niveau ou pas, et cette information est fournie par la fonction parent() de chaque élément. Par exemple, on peut supprimer l?élément courant du widget arbre quelle que soit sa position :

     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 d?élément à un autre endroit dans le widget arbre suit le même modèle :

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

Widgets tables

Les tables d?éléments semblables à celles trouvées dans les applications tableurs sont construites avec les classes QTableWidget et QTableWidgetItem. Celles-ci fournissent un widget table avec défilement, en-têtes et éléments à utiliser en son sein.

Les tables peuvent être créées avec un nombre fixé de lignes et de colonnes, ou des lignes et des colonnes peuvent être ajoutées à une table non dimensionnée lorsqu?elles deviennent nécessaires.

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

Les éléments sont construits à l?extérieur de la table, avant d?être ajoutés à la table à l?endroit requis :

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

Les en-têtes verticaux et horizontaux peuvent être ajoutés à la table en construisant les éléments à l?extérieur de la table puis en les utilisant comme en-têtes :

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

À noter que les numéros de lignes et de colonnes dans la table commencent à zéro.

Fonctionnalités communes

Il existe un certain nombre de fonctionnalités communes pour chacune des classes de commodité qui sont disponibles à travers la même interface dans chaque classe. On présente celles-ci dans les sections suivantes avec quelques exemples pour différents widgets. On pourra jeter un ?il à la liste des Classes modèles/vues pour plus de détails sur l?utilisation de chacune des fonctions utilisées.

Éléments cachés

Il est parfois utile d?être capable de cacher des éléments dans un widget vue d?éléments, plutôt que de les supprimer. Les éléments pour tous les widgets ci-dessus peuvent être cachés puis montrés de nouveau plus tard. On peut déterminer si un élément est caché en appelant la fonction isItemHidden() ; les éléments peuvent être cachés avec la fonction setItemHidden().

Étant donné que cette opération est basée sur les éléments, la même fonction est disponible pour chacune des trois classes de commodité.

Sélections

La façon dont les éléments sont sélectionnés est contrôlée par le mode de sélection du widget (QAbstractItemView::SelectionMode). Cette propriété contrôle si l?utilisateur peut sélectionner un ou plusieurs éléments et, dans le cas d'une sélection de plusieurs éléments, si la sélection doit être un intervalle continu d?éléments. Le mode de sélection fonctionne de la même manière pour tous les widgets ci-dessus.

image Sélections d?élément seul : lorsque l?utilisateur a besoin de choisir un seul élément d?un widget, le mode par défaut SingleSelection est le plus adapté. Dans ce mode, l?élément courant et l?élément sélectionné sont les mêmes.
image Sélections de plusieurs é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, à la manière des checkbox non exclusives qui peuvent être basculées de manière indépendante.
image Sélections étendues : les widgets qui ont souvent besoin de sélectionner beaucoup d?éléments adjacents, tels que ceux trouvés dans les tableurs, ont besoin du mode ExtendedSelection. Dns ce mode, les intervalles continus d?éléments du widget peuvent être sélectionnés à la fois avec le clavier et la souris. Les sélections complexes, impliquant de nombreux éléments non adjacents aux 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 vidée.

Les éléments sélectionnés dans un widget sont retrouvés en utilisant la fonction selectedItems(), qui fournit une liste pouvant être parcourue. Par exemple, on peut trouver la somme de toutes les valeurs numériques dans une liste d?éléments sélectionnés avec le code suivant :

     QList<QTableWidgetItem *> selected = tableWidget->selectedItems();
     QTableWidgetItem *item;
     int number = 0;
     double total = 0;
 
     foreach (item, selected) {
         bool ok;
         double value = item->text().toDouble(&ok);
 
         if (ok && !item->text().isEmpty()) {
             total += value;
             number++;
         }
     }

À noter que pour le mode de sélection simple, l?élément courant sera toujours dans la sélection. Dans les modes de sélection multiple et de sélection étendue, l?élément courant peut ne pas faire partie de la sélection, en fonction de la manière dont l?utilisateur a créé la sélection.

Recherche

Il est souvent utile d?être capable de trouver des éléments dans le widget de vue d?éléments, soit en tant que développeur, soit en tant que service proposé à l?utilisateur. Les trois classes de commodité de vue d?éléments proposent une fonction commune findItems() pour rendre cela le plus cohérent et le plus simple possible.

Les éléments sont recherchés par le texte qu?ils contiennent en fonction de critères spécifiés par une sélection de valeurs de drapeaux Qt::MatchFlags. On peut obtenir une liste d?éléments correspondants avec la fonction findItems() :

     QTreeWidgetItem *item;
     QList<QTreeWidgetItem *> found = treeWidget->findItems(
         itemText, Qt::MatchWildcard);
 
     foreach (item, found) {
         treeWidget->setItemSelected(item, true);
         // Montre item->text(0) pour chaque élément
     }

Le code ci-dessus sélectionne des éléments dans un widget arbre si ceux-ci contiennent le texte donné dans la chaîne de recherche. Ce modèle peut également être utilisé dans des widgets liste ou table.

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

L?infrastructure du glisser-déposer de Qt est entièrement prise en charge par le framework modèle/vue. Les éléments de listes, tables et arbres peuvent être glissés dans les vues, et les données peuvent être importées et exportées en tant que données encodées en MIME.

Les vues standard prennent automatiquement en charge le glisser-déposer en interne, lorsque les éléments sont déplacés pour modifier l?ordre dans lequel ils sont affichés. Par défaut, le glisser-déposer n?est pas activé pour ces vues car elles sont configurées pour les utilisations les plus simples et courantes. Pour autoriser le déplacement d?éléments, certaines propriétés de la vue ont besoin d?être activées, et les éléments eux-mêmes doivent autoriser le déplacement.

Les exigences pour un modèle qui autorise uniquement l?export de données depuis une vue, et qui n?autorise pas les données à y être déposées, sont moins nombreuses que celles pour un modèle autorisant entièrement le glisser-déposer.

Voir aussi la Référence pour dériver un modèle pour plus d?informations sur l?activation de la prise en charge du glisser-déposer sur de nouveaux modèles.

Utilisation des vues de commodité

Chacun des types d?éléments utilisés avec les widgets 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é, cochable, sélectionnable, et peut être utilisé comme source d?une opération de glisser-déposer ; chaque QTableWidgetItem est également modifiable et être utilisé comme cible d?une opération de glisser-déposer.

Bien que tous les éléments standard possèdent un ou deux drapeaux actifs pour le glisser-déposer, on a généralement besoin d'activer plusieurs 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 autoriser le glissement d?élément, fixer la propriété dragEnabled de la vue à true ;
  • pour autoriser l?utilisateur à déposer des éléments internes ou externes dans les vues, fixer la propriété acceptDrops du viewport() de la vue à true ;
  • pour montrer à l?utilisateur l?endroit où l?élément en cours de glissement sera placé lors de la dépose, fixer la propriété showDropIndicator de la vue à true. Ceci fournit à l?utilisateur une information, mise à jour en continu, sur le placement des éléments à l?intérieur de la vue.

Par exemple, on peut activer le glisser-déposer dans un widget 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 liste qui autorise les éléments à être copiés à l?intérieur de la vue, et autorise même l?utilisateur à glisser des éléments entre les vues contenant le même type de données. Dans ces deux situations, les éléments sont copiés plutôt que déplacés.

Pour autoriser l?utilisateur à déplacer les éléments à l?intérieur de la vue, on doit activer le mode dragDropMode du widget liste :

 listWidget->setDragDropMode(QAbstractItemView::InternalMove);

Utilisation des classes modèle/vue

Configurer une classe pour le glisser-déposer suit le même modèle que celui utilisé par les vues de commodité. Par exemple, une QListView peut être configurée de la même façon qu?un 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 le modèle, le modèle utilisé doit aussi fournir la prise en charge des opérations de glisser-déposer. Les actions prises en charge par le 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 par le code suivant :

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

Bien que toutes les combinaisons de valeurs pour les actions de dépose Qt::DropActions soient autorisées, le modèle doit être écrit pour les prendre en charge. Par exemple, pour que l?action de déplacement Qt::MoveAction puisse être utilisée correctement avec un modèle liste, le modèle doit fournir une implémentation de QAbstractItemModel::removeRows(), soit directement soit en héritant l?implémentation de sa classe parente.

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

Les modèles indiquent aux vues quels éléments peuvent être glissés, et quels éléments acceptent la dépose, en réimplémentant la fonction QAbstractItemModel::flags() pour fournir des drapeaux adapté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 le drapeau retourné contient 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;
 }

À noter que les éléments peuvent être déposés au premier niveau du modèle, mais que le glissement n?est autorisé que pour les éléments valides.

Dans le code ci-dessus, étant donné que le modèle dérive de QStringListModel, on obtient un ensemble de drapeaux par défaut en appelant son implémentation de la fonction flags().

Encodage des données exportées

Lorsque les données des éléments sont exportées depuis un modèle avec 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 retourne une liste de types MIME standard.

Par exemple, un modèle qui ne fournit que du texte brut contiendra l?implémentation suivante :

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

Le modèle doit aussi fournir le code pour encoder les données dans le format annoncé. Ceci est réalisé en réimplémentant la fonction QAbstractItemModel::mimeData() pour fournir un objet QMimeData, comme dans n?importe quelle autre opération de glisser-déposer.

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

 QMimeData *DragDropListModel::mimeData(const QModelIndexList &indexes) const
 {
     QMimeData *mimeData = new QMimeData();
     QByteArray encodedData;
 
     QDataStream stream(&encodedData, QIODevice::WriteOnly);
 
     foreach (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 est fournie à la fonction, cette approche est généralement suffisante pour être utilisée à la fois dans les modèles hiérarchiques et non hiérarchiques.

À noter que les types de données personnalisés doivent être déclarés en tant que métaobjets 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 déposées dans un modèle

La façon dont un modèle gère les données déposées dépend à la fois de son type (liste, table ou arbre), et de la façon dont son contenu est susceptible d?être présenté à l?utilisateur. En général, la méthode choisie pour gérer les données déposées devrait être celle qui s?adapte le mieux à la structure de données sous-jacente du modèle.

Des types de modèles différents tendent à gérer les données déposées de différentes façons. Les modèles liste et table fournissent une structure à plat dans laquelle les données des éléments sont stockées. En conséquence, ces modèles peuvent insérer de nouvelles lignes (et colonnes) lorsqu?une donnée est déposée sur une vue existante, ou ils peuvent écraser le contenu de l?élément du modèle en utilisant une partie des données fournies. Les modèles arbre sont souvent capables d?ajouter des éléments enfants contenant de nouvelles données à leur structure de stockage sous-jacent, et agiront donc de façon plus prévisible pour l?utilisateur.

Les données déposées sont gérées par la réimplémentation de la fonction QAbstractItemModel::dropMimeData() du modèle. Par exemple, un modèle qui gère une liste de chaînes simple peut fournir une implémentation différenciée pour les données déposées dans des éléments et pour les données déposées dans le premier niveau du modèle (c'est-à-dire sur un élément non valide).

Le modèle doit d?abord s?assurer que l?opération peut être réalisée, que les données fournies sont dans un format pouvant être utilisé, et que la destination à l?intérieur du modèle est valide :

 bool DragDropListModel::dropMimeData(const QMimeData *data,
     Qt::DropAction action, int row, int column, const QModelIndex &parent)
 {
     if (action == Qt::IgnoreAction)
         return true;
 
     if (!data->hasFormat("application/vnd.text.list"))
         return false;
 
     if (column > 0)
         return false;

Un simple modèle liste de chaînes à une colonne peut indiquer un échec si les données fournies ne sont pas en texte brut, ou si le numéro de colonne pour la dépose n?est pas valide.

Les données à insérer dans le modèle sont traitées différemment si elles sont déposées sur un élément existant ou non. Dans cet exemple simple, on veut autoriser les déposes entre vues existantes, avant le premier élément ou après le dernier élément.

Lorsqu?une dépose a lieu, l?index de modèle correspondant à l?élément parent sera soit valide, indiquant que la dépose a eu lieu sur un élément, soit invalide, indiquant que la dépose a eu lieu sur un endroit de la vue correspondant au premier niveau du modèle.

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

On commence par examiner le numéro de ligne fourni pour voir si on peut l?utiliser pour insérer des éléments dans le modèle, indépendamment de la validité de l?index du parent.

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

Si l?index de modèle du parent est valide, la dépose a eu lieu sur un élément. Dans un modèle liste simple, on retrouve le numéro de ligne et on utilise cette valeur pour insérer les éléments déposés dans le premier niveau du modèle.

     else
         beginRow = rowCount(QModelIndex());

Lorsque la dépose a lieu à un autre endroit de la vue et que le numéro de ligne est inutilisable, on ajoute les éléments dans le premier niveau du modèle.

Dans les modèles hiérarchiques, lorsqu?une dépose a lieu sur un élément, il serait préférable d?insérer les nouveaux éléments dans le modèle en tant qu?enfants de cet élément. Dans l?exemple simple présenté ici, le modèle ne possède qu?un seul niveau, cette approche n?est donc pas adaptée.

Décodage des données importées

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

Pour un modèle liste de chaînes simple, les éléments encodés peuvent être décodés et intégrés via un flux dans une 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 la structure de données sous-jacente. Par souci de cohérence, ceci peut être fait à travers l?interface propre au modèle :

     insertRows(beginRow, rows, QModelIndex());
     foreach (const QString &text, newItems) {
         QModelIndex idx = index(beginRow, 0, QModelIndex());
         setData(idx, text);
         beginRow++;
     }
 
     return true;
 }

À noter que, typiquement, le modèle aura besoin de fournir des implémentations des fonctions QAbstractItemModel::insertRows() et QAbstractItemModel::setData().

Modèles proxy

Dans le framework modèle/vue, les données des éléments fournies par un modèle unique peuvent être partagées entre n?importe quel nombre de vues, et chacune d?entre elles peut éventuellement présenter la même information de différentes façons. Les vues et délégués personnalisés sont des moyens efficaces de fournir des représentations radicalement différentes de la même donnée. Toutefois, les applications ont souvent besoin de fournir des vues conventionnelles sur des versions prétraitées de la même donnée, par exemple des vues d'une liste d?éléments triés de différentes façons.

Bien qu?il semble approprié de réaliser les opérations de tri et de filtrage sous forme de fonctions internes à la vue, cette approche n?autorise pas de multiples vues à partager le résultat de telles opérations potentiellement coûteuses en ressources. L?approche alternative, impliquant de trier dans le modèle lui-même, conduit à un problème similaire, ou chaque vue doit afficher les données des éléments organisées selon l?opération de traitement la plus récente.

Pour résoudre ce problème, le framework modèle/vue utilise des modèles proxy pour gérer les informations transitant entre les différents modèles et les vues. Les modèles proxy sont des composants qui se comportent comme des modèles ordinaires depuis la perspective d?une vue, et accèdent aux données des modèles sources pour le compte de cette vue. Les signaux et slots sont utilisés par le framework modèle/vue pour s?assurer que chaque vue est correctement mise à jour, indépendamment du nombre de modèles proxy placés entre la vue elle-même et le modèle source.

Utilisation des modèles proxy

Les modèles proxy peuvent être insérés entre un modèle existant et n?importe quel nombre de vues. Qt est fourni avec un modèle proxy standard, QSortFilterProxyModel, qui est généralement instancié et utilisé directement, mais qui peut également être dérivé pour fournir un comportement de tri et de filtrage personnalisé. La classe QSortFilterProxyModel peut également être utilisée de la façon suivante :

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

Étant donné que les modèles proxy dérivent de QAbstractItemModel, ils peuvent être connectés à tout type de vues, et peuvent être partagés entre plusieurs vues. Ils peuvent aussi être enchaînés, un proxy traitant les informations obtenues d?autres modèles proxy.

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 dérivant de cette classe et en implémentant les opérations de comparaison requises.

Personnalisation des modèles proxy

En général, le type de traitement utilisé dans un modèle proxy implique de convertir la position de chaque élément dans le modèle source vers une position différente dans le modèle proxy. Dans certains modèles, certains éléments peuvent ne pas posséder de position correspondante dans le modèle proxy ; ces modèles sont des modèles proxy de filtrage. Les vues accèdent aux éléments en utilisant les index de modèle fournis par le modèle proxy, et ceux-ci ne contiennent pas d?informations sur le modèle source ou sur les positions des éléments originaux dans ce modèle.

QSortFilterProxyModel autorise les données d?un modèle source à être filtrées avant d?être fournies aux vues et autorise également le contenu du modèle source à être fourni 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 qui est assez polyvalent, et qui peut être utilisé dans un grand nombre de situations courantes. Pour les utilisateurs avancés, QSortFilterProxyModel peut être dérivé, fournissant un mécanisme autorisant l?implémentation de filtres personnalisés.

Les classes dérivées de QSortFilterProxyModel peuvent réimplémenter deux fonctions virtuelles qui sont appelées lorsqu?un index de modèle d?un modèle proxy 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 retournent true pour s?assurer que tous les éléments sont transmis aux vues ; les réimplémentations de ces fonctions doivent retourner false pour exclure des lignes et colonnes particulières.

Modèles de tri personnalisés

Les instances de QSortFilterProxyModel utilisent la fonction intégrée de Qt qStableSort() pour configurer la conversion entre les éléments du modèle source et ceux du modèle proxy, permettant de présenter 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é, on réimplémente la fonction lessThan() pour réaliser des comparaisons personnalisées.

Référence sur la dérivation de modèle

Les classes modèle dérivées doivent fournir les implémentations d?un grand nombre de fonctions virtuelles définies dans la classe de base QAbstractItemModel. Le nombre de ces fonctions devant être implémentées dépend du type de modèle ? suivant que celui-ci fournit aux vues une liste simple, une table, ou une hiérarchie complexe d?éléments. Les modèles qui héritent de QAbstractListModel ou de QAbstractTableModel peuvent tirer parti des implémentations par défaut des fonctions fournies par ces classes. Les modèles qui exposent les données des éléments dans des structures de type arbre doivent fournir les implémentations d?un grand nombre de fonctions virtuelles définies dans la classe QAbstractItemModel.

Les fonctions qui doivent être implémentées dans la classe dérivée d?un modèle peuvent être divisées en trois groupes :

  • gestion des données des éléments : tous les modèles doivent implémenter des fonctions pour autoriser les vues et les délégués à demander les dimensions du modèle, examiner les éléments et retrouver 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 de type arbre et obtenir des index de modèle pour les éléments.
  • prise en charge du glisser-déposer et gestion du type MIME : les modèles héritent de fonctions qui contrôlent la façon dont les opérations de glisser-déposer en interne ou en externe sont réalisées. Ces fonctions autorisent les données des éléments à être décrits sous forme de types MIME que les autres composants et applications peuvent comprendre.

Pour plus d?informations, voir le chapitre "Les classes de vues d?éléments" du Guide de programmation d'interfaces utilisateur en C++ avec Qt 4.

Gestion 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 gérer les opérations de redimensionnement et d?autres peuvent autoriser à modifier les éléments.

Accès en lecture seule

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

flags() Utilisée par les autres composants pour obtenir des informations sur chaque élément fourni par le modèle. Dans la plupart des modèles, la combinaison de drapeaux devrait inclure Qt::ItemIsEnabled et Qt::ItemIsSelectable.
data() Utilisée pour fournir des données des éléments aux vues et aux délégués. En général, les modèles n?ont besoin de fournir que les données pour le rôle Qt::DisplayRole et pour tout autre rôle utilisateur spécifique à l?application, mais une bonne pratique consiste à fournir également des données pour les rôles Qt::ToolTipRole, Qt::AccessibleTextRole et Qt::AccessibleDescriptionRole. Voir la documentation sur l?énumération Qt::ItemDataRole pour des 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 utilisées que par les vues affichant des informations d?en-têtes.
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 liste (sous-classes de QAbstractListModel), et les modèles table (sous-classes de QAbstractTableModel).

De plus, la fonction suivante doit être implémentée 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 liste ne fournissent pas cette fonction car elle est déjà implémentée dans QAbstractListModel.
Éléments modifiables

Les modèles modifiables autorisent à modifier des données des éléments, et peuvent aussi fournir des fonctions pour autoriser l?insertion et la suppression de lignes et de colonnes. Pour autoriser l?édition, les fonctions suivantes doivent être implémentées correctement :

flags() Doit retourner une combinaison appropriée d?indicateurs pour chaque élément. En particulier, la valeur retournée par cette fonction doit inclure Qt::ItemIsEditable en plus des valeurs appliquées aux éléments d?un modèle en lecture seule.
setData() Utilisée pour modifier l?élément associé à l?index de modèle spécifié. Pour être capable d?autoriser les entrées utilisateur fournies par les éléments d?interface utilisateur, cette fonction doit gérer les données associées à Qt::EditRole. L?implémentation peut aussi accepter les données associées à de nombreux types de rôles spécifiés par Qt::ItemDataRole. Après la modification de l?élément de données, les modèles doivent émettre le signal dataChanged() pour informer les autres composants du changement.
setHeaderData() Utilisée pour modifier les informations d?en-têtes horizontaux et verticaux. Après la modification des données de l?élément, les modèles doivent émettre le signal headerDataChanged () pour informer les autres composants du changement.
Modèles redimensionnables

Tous les types de modèles peuvent prendre en charge l?insertion et la suppression de lignes. Les modèles tables et les modèles hiérarchiques peuvent aussi prendre en charge l?insertion et la suppression de colonnes. Il est important de prévenir les autres composants des changements de dimensions du modèle, à la fois avant et après le changement. En conséquence, les fonctions suivantes peuvent être implémentées pour autoriser le modèle à être redimensionné, mais les implémentations doivent s?assurer que les fonctions appropriées sont appelées pour prévenir les vues et délégués attachés :

insertRows() Utilisée pour ajouter de nouvelles lignes et éléments de données à tout type de modèle. Les implémentations doivent appeler beginInsertRows() avant l?insertion de nouvelles lignes dans toute structure de données sous-jacente, et appeler endInsertRows() immédiatement après.
removeRows() Utilisée pour supprimer des lignes et les éléments de données qu?elles contiennent dans tout type de modèle. Les implémentations doivent appeler beginRemoveRows() avant la suppression de lignes de toute structure de données sous-jacente, et appeler endRemoveRows() immédiatement après.
insertColumns() Utilisée pour ajouter de nouvelles colonnes et éléments de données aux modèles table et aux modèles hiérarchiques. Les implémentations doivent appeler beginInsertColumns() avant l?insertion de nouvelles colonnes dans toute structure de données sous-jacente, et appeler endInsertColumns() immédiatement après.
removeColumns() Utilisée pour supprimer des colonnes et les éléments de données qu?elles contiennent dans des modèles table et des modèles hiérarchiques. Les implémentations doivent appeler beginRemoveColumns() avant la suppression de colonnes de toute structure de données sous-jacente, et appeler endRemoveColumns() immédiatement après.

En général, ces fonctions devraient retourner true si l?opération a réussi. Toutefois, dans certains cas l?opération n?a réussi qu?en partie ; par exemple si un nombre inférieur au nombre spécifié de lignes a pu être inséré. Dans de tels cas, le modèle devrait retourner false pour indiquer un échec, pour permettre à tous les 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é de réaliser des actions avant qu?une donnée ne devienne indisponible. L?encapsulation des opérations d?insertion et de suppression dans des fonctions de début et de fin autorise également le modèle à gérer correctement les index de modèle persistants.

Normalement, les fonctions de début et de fin sont capables d?informer les autres composants sur les changements de la structure de données sous-jacente. Pour des changements plus complexes de la structure du modèle, impliquant par exemple une réorganisation interne ou un tri des données, il est nécessaire d?émettre le signal layoutChanged() pour provoquer la mise à jour de toutes les vues attachées.

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

Le peuplement différé des données du modèle permet de différer, de manière efficace, les requêtes d?informations sur le modèle jusqu?à ce qu'elles soient réellement nécessaires aux vues.

Certains modèles ont besoin d?obtenir des données de sources distantes, ou doivent réaliser des opérations coûteuses en temps pour obtenir des informations sur la façon dont les données sont organisées. Étant donné que les vues demandent généralement le plus d?informations possible pour afficher avec précision les données du modèle, il peut être utile de limiter la quantité d?informations qui leur est retournée pour réduire les requêtes de données superflues.

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 que lorsque c?est nécessaire. Pour cela, la fonction hasChildren() peut être réimplémentée pour fournir aux vues une façon peu coûteuse de vérifier la présence d?enfants et, dans le cas d?une vue QTreeView, dessiner la décoration appropriée pour l?élément parent.

En fonction de la valeur retournée par la réimplémentation de la fonction hasChildren(), true ou false, il peut être nécessaire pour la vue d?appeler rowCount() pour savoir combien d?enfants sont présents. Par exemple, QTreeView n?a pas besoin de savoir combien d?enfants existent si l?élément parent n?a pas été développé pour les montrer.

Si on sait que beaucoup d?éléments vont avoir des enfants, réimplémenter hasChildren() pour qu'elle retourne toujours true est parfois un bon choix. Cela assure que chaque élément peut être plus tard examiné pour trouver ses enfants, tout en rendant le peuplement initial des données du modèle aussi rapide que possible. Le seul inconvénient est que les éléments sans enfant peuvent être affichés de manière incorrecte dans certaines vues, jusqu?à ce que l?utilisateur tente de voir les éléments enfants inexistants.

Les modèles hiérarchiques doivent fournir des fonctions que les vues peuvent appeler pour naviguer dans les structures de type arbre, et obtenir des index de modèle pour les éléments.

Parents et enfants

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

index() Étant donné un index de modèle pour un élément parent, cette fonction autorise les vues et délégués à 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 ? n?est trouvé, la fonction doit retourner QModelIndex(), qui est un index de modèle non valide.
parent() Fournit un index de modèle correspondant au parent de tout élément enfant donné. Si l?index de modèle spécifié correspond à un élément de premier niveau du modèle, ou qu?il n?y a pas d?élément parent valide dans le modèle, la fonction doit retourner un index de modèle non valide, créé avec le constructeur vide QModelIndex().

Les deux fonctions ci-dessus utilisent la fonction de fabrication createIndex() pour générer des index utilisables par les autres composants. Il est habituel que les modèles fournissent certains identifiants uniques à cette fonction pour s?assurer que l?index de modèle puisse plus tard être réassocié avec son élément correspondant.

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

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

De plus, les classes de vues de commodité implémentent un comportement spécialisé qui doit normalement correspondre assez bien à celui attendu par les développeurs. La section Vues de commodité fournit un aperçu 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 faire circuler les informations sur les index de modèle. Ce type spécifie les 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 en utilisant 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 les données des éléments dans des formats spécialisés en réimplémentant la fonction suivante :

mimeData() Cette fonction peut être réimplémentée pour retourner les 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 depuis la classe de base, et lui 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 commun représenté par des types MIME tels que text/plain ou image/png. À noter que des images, des couleurs et les documents HTML peuvent être facilement ajoutés à un objet QMimeData avec les fonctions QMimeData::setImageData(), QMimeData::setColorData() et QMimeData::setHtml().

Acceptation des données déposées

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

Lorsqu?un élément sérialisé est déposé sur une vue, les données sont insérées dans le modèle courant en utilisant l?implémentation de QAbstractItemModel::dropMimeData(). L?implémentation par défaut de cette fonction ne remplacera jamais aucune donnée du modèle ; à la place, elle essaye d?insérer les éléments de données soit en tant que frères de l?é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() insertColumns() Ces fonctions autorisent le modèle à insérer automatiquement les nouvelles données en utilisant l?implémentation existante fournie par QAbstractItemModel::dropMimeData().
setData() Autorise les nouvelles lignes et colonnes à être peuplées par des éléments.
setItemData() Cette fonction fournit une prise en charge plus efficace pour le peuplement de nouveaux éléments.

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

supportedDropActions() Utilisée pour retourner une combinaison d'actions de dépose, indiquant les types d?opérations de glisser-déposer acceptées par le modèle.
mimeTypes() Utilisée pour retourner une liste des types MIME qui peuvent être décodés et gérés par le modèle. En général, les types MIME qui sont pris en charge pour les entrées du modèle sont les mêmes que ceux qui peuvent être utilisés lors de l?encodage des données pour une utilisation par des composants externes.
dropMimeData() Réalise le décodage des données transférées par des opérations de glisser-déposer, détermine l?endroit du modèle où elles seront enregistrées, et insère de nouvelles lignes et colonnes si nécessaire. La façon dont cette fonction est implémentée dans les sous-classes dépend des exigences sur les données exposées par chaque modèle.

Si l?implémentation de la fonction dropMimeData() modifie les dimensions du modèle en insérant ou supprimant des lignes ou des colonnes, ou si des éléments de données sont modifiés, on doit prendre soin de s?assurer que tous les signaux pertinents sont émis. Il peut être utile d?appeler simplement les réimplémentations des autres fonctions de la sous-classe, telles que setData(), insertRows() ou insertColumns(), pour s?assurer que le modèle agit 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 :

Pour plus d?informations sur le glisser-déposer avec des vues d?éléments, se référer à Utilisation du glisser-déposer avec des vues d?éléments.

Vues de commodité

Les vues de commodité(QListWidget, QTableWidget et QTreeWidget) redéfinissent la fonctionnalité de glisser-déposer par défaut pour fournir un comportement moins souple mais plus naturel qui est approprié pour de nombreuses applications. Par exemple, étant donné qu'il est plus courant de déposer des données dans des cellules dans un QTableWidget, en remplaçant le contenu existant par les données en cours de transfert, le modèle sous-jacent va remplacer 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 dans des vues d'éléments.

Optimisation de performances pour les grandes quantités de données

La fonction canFetchMore() vérifie si le parent a des données supplémentaires disponibles, et retourne true ou false suivant le cas. La fonction fetchMore() récupère les données basées sur le parent spécifié. Ces deux fonctions peuvent être combinées, par exemple dans une requête vers une base de données impliquant le peuplement de QAbstractItemModel par des données incrémentales. On réimplémente canFetchMore() pour indiquer si des données supplémentaires peuvent être récupérées, et fetchMore() pour peupler le modèle si nécessaire.

Un autre exemple serait un modèle arbre peuplé dynamiquement, où l'on réimplémente fetchMore() lorsqu?une branche du modèle arbre est déroulée.

Si les réimplémentations de fetchMore() ajoutent des lignes aux modèles, il faut appeler beginInsertRows() et endInsertRows(). De plus, les fonctions canFetchMore() et fetchMore() doivent toutes les deux être réimplémentées étant donné que leur implémentation par défaut retourne false et ne fait rien.

Classes modèle/vue

Ces classes utilisent le design pattern modèle/vue dans lequel les données sous-jacentes (dans le modèle) sont séparées de la façon dont elles sont présentées et manipulées par l'utilisateur (dans la vue).

QAbstractItemDelegate Utilisée pour afficher et éditer les éléments de données d'un modèle
QAbstractItemModel L'interface abstraite pour les classes de modèle d'éléments
QAbstractItemView La fonctionnalité basique pour les classes de vues d'éléments
QAbstractListModel Le modèle abstrait qui peut être dérivé pour créer des modèles listes à une seule dimension
QAbstractProxyModel Classe de base pour les modèles d'éléments proxy qui peuvent réaliser le tri, le filtrage et d'autres tâches de traitement de données
QAbstractTableModel Modèle abstrait qui peut être dérivé pour créer des modèles table
QColumnView Implémentation modèle/vue d'une vue en colonne
QDataWidgetMapper Conversion entre une section d'un modèle de données et des widgets
QFileSystemModel Modèle de données pour le système de fichiers local
QHeaderView Ligne ou colonne d'en-tête pour les vues d'éléments
QItemDelegate Fonctionnalités d'affichage et d'édition pour les éléments de données d'un modèle
QItemEditorCreator Rend possible la création des bases de création d'un éditeur d'éléments sans dériver QItemEditorCreatorBase
QItemEditorCreatorBase Classe de base abstraite qui doit être dérivée pour l'implémentation de nouveaux créateurs d'éditeurs d'éléments
QItemEditorFactory Widgets pour l'édition de données d'éléments dans les vues et les délégués
QItemSelection Gère les informations sur les éléments sélectionnés d'un modèle
QItemSelectionModel Mémorise les éléments sélectionnés de la vue
QItemSelectionRange Gère les informations sur l'intervalle d'éléments sélectionnés du modèle
QListView Vue en liste ou en icônes d'un modèle
QListWidget Widget liste
QListWidgetItem Élément à utiliser avec la classe de vue d'éléments QListWidget
QModelIndex Utilisée pour localiser les données dans un modèle de données
QPersistentModelIndex Utilisée pour localiser les données dans un modèle de données
QSortFilterProxyModel Gestion de tri et de filtrage de données passées d'un autre modèle à une vue
QStandardItem Élément à utiliser avec la classe QStandardItemModel
QStandardItemEditorCreator La possibilité d'enregistrer des widgets sans avoir besoin de dériver QItemEditorCreatorBase
QStandardItemModel Modèle générique pour stocker des données personnalisées
QStringListModel Modèle qui fournit des chaînes aux vues
QStyledItemDelegate Fonctionnalités d'affichage et d'édition pour les éléments de données d'un modèle
QTableView Implémentation modèle/vue par défaut d'une vue table
QTableWidget Vue table basée sur les éléments avec un modèle par défaut
QTableWidgetItem Élément à utiliser avec la classe QTableWidget
QTableWidgetSelectionRange Manière d'interagir avec la sélection d'un modèle sans utiliser d'index de modèle ni de modèle de sélection
QTreeView Implémentation modèle/vue par défaut d'une vue arbre
QTreeWidget Vue arbre utilisant un modèle d'arbre prédéfini
QTreeWidgetItem Élément à utiliser avec la classe de commodité QTreeWidget
QTreeWidgetItemIterator Manière de parcourir les éléments d'une instance de QTreeWidget

Exemples associés

Voir aussi Exemple : puzzle de vues d'éléments.

Remerciements

Merci à Lo?c Leguay pour la traduction, ainsi qu'à Ilya Diallo et Claude Leloup pour la relecture !

Cette page est une traduction d'une page de la documentation de Qt, écrite par Nokia Corporation and/or its subsidiary(-ies). Les éventuels problèmes résultant d'une mauvaise traduction ne sont pas imputables à Nokia. Qt 4.7
Copyright © 2024 Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon, vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts. Cette page est déposée à la SACD.
Vous avez déniché une erreur ? Un bug ? Une redirection cassée ? Ou tout autre problème, quel qu'il soit ? Ou bien vous désirez participer à ce projet de traduction ? N'hésitez pas à nous contacter ou par MP !