Programmation modèle/vueIntroduction à la programmation modèle/vueQt 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/vueMVC (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. 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 :
ModèlesTous 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. VuesDes 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ésQAbstractItemDelegate 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. TriIl 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 vuesLes 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 QtQStandardItemModel 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 existantLes 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. 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èlesAvant 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 baseDans 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. 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èlePour 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 colonnesDans 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.
Parents des élémentsL?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);
Rôles d?élémentsLes é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);
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é
Utilisation des index de modèlePour 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 :
Pour en savoir plusDe 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 vuesConceptsDans 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 existanteQt 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. 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èleOn 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. 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èleFournir 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. 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émentsLe 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 vuesBien 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. 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ésConceptsContrairement 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é existantLes 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é simpleLe 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. 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 éditeurDans 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èleLorsque 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?éditeurLe 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?éditionAprè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émentsConceptsLe 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ésDans 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.
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électionLes 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émentsPour 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. 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électionLes 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 ¤t, const QModelIndex &previous) { statusBar()->showMessage( tr("Moved from (%1,%2) to (%3,%4)") .arg(previous.row()).arg(previous.column()) .arg(current.row()).arg(current.column())); } 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électionLes 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é : 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 : 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èlePour 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èlesLa 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èleLors 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 seuleLe 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èleOn 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èlePour 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 modifiableLe 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 modifiableUn 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 lignesIl 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 suivantesOn 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émentsQt 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 listesLes 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 arbresLes 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 tablesLes 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 communesIl 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ésIl 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électionsLa 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.
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. RechercheIl 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émentsL?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 :
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/vueConfigurer 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émentsLes 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éesLorsque 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èleLa 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éesChaque 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 proxyDans 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 proxyLes 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 proxyEn 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ésLa 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é :
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ésLes 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èleLes 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 :
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émentsLes 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 seulePour 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 :
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 :
Éléments modifiablesLes 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 :
Modèles redimensionnablesTous 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 :
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èleLe 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. Navigation et création d?index de modèleLes 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 :
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 MIMELes 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 MIMEPar 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 :
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éesLorsqu'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 :
Pour accepter d?autres formes de données, ces fonctions doivent être réimplémentées :
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éesLa 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/vueCes 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).
Exemples associésVoir aussi Exemple : puzzle de vues d'éléments. RemerciementsMerci à 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 ! |