Tutoriel Modèle/Vue
Tout développeur d'interface utilisateur devrait connaître la programmation ModelView et l'objectif de ce tutoriel est de vous fournir une introduction facilement compréhensible à ce sujet.
Les widgets de type tableau, liste et arbre sont des composants fréquemment utilisés dans les interfaces graphiques. Ces widgets peuvent accéder à leurs données de deux manières différentes. La méthode traditionnelle implique des widgets qui incluent des conteneurs internes pour le stockage des données. Cette approche est très intuitive, mais dans de nombreuses applications non triviales, elle entraîne des problèmes de synchronisation des données. La deuxième approche est la programmation par modèle/vue, dans laquelle les widgets ne conservent pas de conteneurs de données internes. Ils accèdent aux données externes par le biais d'une interface standardisée et évitent ainsi la duplication des données. Cela peut sembler compliqué à première vue, mais une fois que l'on y regarde de plus près, non seulement c'est facile à comprendre, mais les nombreux avantages de la programmation modèle/vue deviennent également plus clairs.

Au cours de ce processus, nous apprendrons à connaître certaines technologies de base fournies par Qt, telles que :
- la différence entre les widgets standard et les widgets modèle/vue
- Les adaptateurs entre les formulaires et les modèles
- Développement d'une application modèle/vue simple
- Les modèles prédéfinis
- Sujets intermédiaires tels que :
- Arborescences
- la sélection
- Délégués
- Débogage avec le test de modèle
Vous apprendrez également si votre nouvelle application peut être écrite plus facilement avec la programmation modèle/vue ou si les widgets classiques fonctionneront tout aussi bien.
Ce tutoriel comprend un code d'exemple que vous pouvez modifier et intégrer dans votre projet. Le code source du tutoriel se trouve dans le répertoire examples/widgets/tutorials/modelview de Qt Location.
Pour des informations plus détaillées, vous pouvez également consulter la documentation de référence
1. Introduction
Model/View est une technologie utilisée pour séparer les données des vues dans les widgets qui gèrent des ensembles de données. Les widgets standard ne sont pas conçus pour séparer les données des vues et c'est pourquoi Qt Widgets possède deux types de widgets différents. Les deux types de widgets se ressemblent, mais ils interagissent différemment avec les données.
| Les widgets standard utilisent des données qui font partie du widget. |
|
| Les classes de vues opèrent sur des données externes (le modèle) |
|
1.1 Widgets standard
Examinons de plus près un widget de tableau standard. Un widget de tableau est un tableau 2D d'éléments de données que l'utilisateur peut modifier. Le widget de tableau peut être intégré dans le flux d'un programme en lisant et en écrivant les éléments de données fournis par le widget de tableau. Cette méthode est très intuitive et utile dans de nombreuses applications, mais l'affichage et la modification d'un tableau de base de données à l'aide d'un widget de tableau standard peuvent poser des problèmes. Deux copies des données doivent être coordonnées : l'une à l'extérieur du widget, l'autre à l'intérieur du widget. Le développeur est responsable de la synchronisation des deux versions. En outre, le couplage étroit entre la présentation et les données rend plus difficile l'écriture de tests unitaires.
1.2 Modèle/Vue à la rescousse
Le modèle/la vue est apparu pour fournir une solution qui utilise une architecture plus polyvalente. Model/view élimine les problèmes de cohérence des données qui peuvent survenir avec les widgets standard. Le modèle/vue facilite également l'utilisation de plusieurs vues des mêmes données, car un modèle peut être transmis à plusieurs vues. La différence la plus importante est que les widgets modèle/vue ne stockent pas les données derrière les cellules du tableau. En fait, ils opèrent directement à partir de vos données. Étant donné que les classes de vues ne connaissent pas la structure de vos données, vous devez fournir une enveloppe pour que vos données soient conformes à l'interface QAbstractItemModel. Une vue utilise cette interface pour lire et écrire dans vos données. Toute instance d'une classe qui implémente QAbstractItemModel est considérée comme un modèle. Une fois que la vue reçoit un pointeur sur un modèle, elle lit et affiche son contenu et en est l'éditeur.
1.3 Vue d'ensemble des widgets Modèle/Vue
Voici un aperçu des widgets modèle/vue et des widgets standard correspondants.
| Widget | Widget standard (classe de commodité basée sur les éléments) | Modèle/Vue Classe de vue (à utiliser avec des données externes) |
|---|---|---|
![]() | QListWidget | QListView |
![]() | QTableWidget | QTableView |
![]() | QTreeWidget | QTreeView |
![]() | QColumnView présente un arbre sous la forme d'une hiérarchie de listes | |
![]() | QComboBox peut fonctionner à la fois comme une classe de vue et comme un widget traditionnel | |
1.4 Utilisation d'adaptateurs entre les formulaires et les modèles
L'utilisation d'adaptateurs entre les formulaires et les modèles peut s'avérer très utile.
Nous pouvons modifier des données stockées dans des tables directement à partir de la table elle-même, mais il est beaucoup plus confortable de modifier des données dans des champs de texte. Il n'y a pas de contrepartie directe modèle/vue qui sépare les données et les vues pour les widgets qui opèrent sur une valeur (QLineEdit, QCheckBox...) au lieu d'un ensemble de données, nous avons donc besoin d'un adaptateur afin de connecter le formulaire à la source de données.
QDataWidgetMapper L'adaptateur est une excellente solution parce qu'il fait correspondre les widgets du formulaire à une ligne de table et rend très facile la construction de formulaires pour les tables de la base de données.

Un autre exemple d'adaptateur est QCompleter. Qt dispose de QCompleter pour fournir des auto-complétions dans les Qt Widgets tels que QComboBox et, comme indiqué ci-dessous, QLineEdit. QCompleter utilise un modèle comme source de données.

2. Une application modèle-vue simple
Si vous souhaitez développer une application modèle/vue, par où devriez-vous commencer ? Nous vous recommandons de commencer par un exemple simple et de l'étendre pas à pas. Cela facilite grandement la compréhension de l'architecture. Essayer de comprendre l'architecture modèle/vue en détail avant d'invoquer l'IDE s'est avéré moins pratique pour de nombreux développeurs. Il est nettement plus facile de commencer par une application modèle/vue simple qui contient des données de démonstration. Essayez-la ! Remplacez simplement les données des exemples ci-dessous par les vôtres.
Vous trouverez ci-dessous 7 applications très simples et indépendantes qui montrent différents aspects de la programmation modèle/vue. Le code source se trouve dans le répertoire examples/widgets/tutorials/modelview.
2.1 Un tableau en lecture seule
Nous commençons par une application qui utilise QTableView pour afficher des données. Nous ajouterons plus tard des capacités d'édition.
(source du fichier : examples/widgets/tutorials/modelview/1_readonly/main.cpp)
// main.cpp #include <QApplication> #include <QTableView> #include "mymodel.h" int main(int argc, char *argv[]) { QApplication a(argc, argv); QTableView tableView; MyModel myModel; tableView.setModel(&myModel); tableView.show(); return a.exec(); }
Nous avons la fonction main() habituelle :
Voici la partie intéressante : Nous créons une instance de MyModel et utilisons tableView.setModel(&myModel) ; pour transmettre un pointeur à tableView. tableView invoquera les méthodes du pointeur qu'il a reçu pour découvrir deux choses :
- Combien de lignes et de colonnes doivent être affichées.
- Quel contenu doit être imprimé dans chaque cellule.
Le modèle a besoin de code pour répondre à ces questions.
Nous disposons d'un ensemble de données sous forme de tableau. Commençons donc par QAbstractTableModel, car il est plus facile à utiliser que QAbstractItemModel, qui est plus général.
(source du fichier : examples/widgets/tutorials/modelview/1_readonly/mymodel.h)
// mymodel.h #include <QAbstractTableModel> class MyModel : public QAbstractTableModel { Q_OBJECT public: explicit MyModel(QObject *parent = nullptr); int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; };
QAbstractTableModel nécessite la mise en œuvre de trois méthodes abstraites.
(source du fichier : examples/widgets/tutorials/modelview/1_readonly/mymodel.cpp)
// mymodel.cpp #include "mymodel.h" MyModel::MyModel(QObject *parent) : QAbstractTableModel(parent) { } int MyModel::rowCount(const QModelIndex & /*parent*/) const { return 2; } int MyModel::columnCount(const QModelIndex & /*parent*/) const { return 3; } QVariant MyModel::data(const QModelIndex &index, int role) const { if (role == Qt::DisplayRole) return QString("Row%1, Column%2") .arg(index.row() + 1) .arg(index.column() +1); return QVariant(); }
Le nombre de lignes et de colonnes est fourni par MyModel::rowCount() et MyModel::columnCount(). Lorsque la vue doit connaître le texte de la cellule, elle appelle la méthode MyModel::data(). Les informations sur les lignes et les colonnes sont spécifiées par le paramètre index et le rôle est défini sur Qt::DisplayRole. Les autres rôles sont abordés dans la section suivante. Dans notre exemple, les données à afficher sont générées. Dans une application réelle, MyModel aurait un membre appelé MyData, qui sert de cible pour toutes les opérations de lecture et d'écriture.
Ce petit exemple démontre la nature passive d'un modèle. Le modèle ne sait pas quand il sera utilisé ni quelles données sont nécessaires. Il fournit simplement des données chaque fois que la vue les demande.
Que se passe-t-il lorsque les données du modèle doivent être modifiées ? Comment la vue se rend-elle compte que les données ont changé et qu'elles doivent être lues à nouveau ? Le modèle doit émettre un signal qui indique quelle plage de cellules a été modifiée. Ceci sera démontré dans la section 2.3.
2.2 Extension de l'exemple de lecture seule avec les rôles
En plus de contrôler le texte affiché par la vue, le modèle contrôle également l'apparence du texte. Lorsque nous modifions légèrement le modèle, nous obtenons le résultat suivant :

En fait, il suffit de modifier la méthode data() pour définir les polices, la couleur d'arrière-plan, l'alignement et la case à cocher. Voici la méthode data() qui produit le résultat ci-dessus. La différence est que cette fois-ci, nous utilisons le paramètre int role pour renvoyer différentes informations en fonction de sa valeur.
(source du fichier : examples/widgets/tutorials/modelview/2_formatting/mymodel.cpp)
// mymodel.cppQVariant MonModèle::data(const QModelIndex &index, int role) const{ int row = index.row() ; int col = index.column() ; // génère un message de log lorsque cette méthode est appelée qDebug() << QString("row %1, col%2, role %3") .arg(row).arg(col).arg(role) ; switch (role) { case Qt::DisplayRole : if (row == 0 && col == 1) return QString("<--left") ; if (row == 1 && col == 1) return QString("right-->") ; return QString("Row%1, Column%2") .arg(row + 1) .arg(col+1) ; case Qt::FontRole : if (row == 0 && col == 0) { // modification de la police uniquement pour la cellule(0,0) QFont boldFont ; boldFont.setBold(true) ; return boldFont ; } break; case Qt::BackgroundRole : if (row == 1 && col == 2) // modification de l'arrière-plan uniquement pour la cellule(1,2) return QBrush(Qt::red) ; break; case Qt::TextAlignmentRole : if (row == 1 && col == 1) // modification de l'alignement du texte uniquement pour la cellule(1,1) return int(Qt::AlignRight | Qt::AlignVCenter) ; break; case Qt::CheckStateRole : if (row == 1 && col == 0) // ajoute une case à cocher à la cellule(1,0) return Qt::Checked ; break; } return QVariant() ; }
Chaque propriété de formatage sera demandée au modèle par un appel séparé à la méthode data(). Le paramètre role est utilisé pour indiquer au modèle quelle propriété est demandée :
| enum Qt::ItemDataRole | Signification | Type de propriété |
|---|---|---|
| Qt::DisplayRole | texte | QString |
| Qt::FontRole | police de caractères | QFont |
| BackgroundRole | brosse pour l'arrière-plan de la cellule | QBrush |
| Qt::TextAlignmentRole | alignement du texte | enum Qt::AlignmentFlag |
| Qt::CheckStateRole | supprime les cases à cocher avec QVariant(), active les cases à cocher avec Qt::Checked | enum Qt::ItemDataRole |
Reportez-vous à la documentation de l'espace de noms Qt pour en savoir plus sur les capacités de l'enum Qt::ItemDataRole.
Nous devons maintenant déterminer l'impact de l'utilisation d'un modèle séparé sur les performances de l'application. Traçons donc le nombre de fois où la vue appelle la méthode data(). Afin de savoir combien de fois la vue appelle le modèle, nous avons placé une instruction de débogage dans la méthode data(), qui se connecte au flux de sortie d'erreur. Dans notre petit exemple, data() sera appelé 42 fois. Chaque fois que vous passez le curseur sur le champ, data() est appelé à nouveau - 7 fois pour chaque cellule. C'est pourquoi il est important de s'assurer que vos données sont disponibles lorsque data() est appelé et que les opérations de recherche coûteuses sont mises en cache.
2.3 Une horloge dans une cellule de tableau

Nous avons toujours un tableau en lecture seule, mais cette fois le contenu change toutes les secondes parce que nous affichons l'heure actuelle.
(source du fichier : examples/widgets/tutorials/modelview/3_changingmodel/mymodel.cpp)
QVariant MyModel::data(const QModelIndex &index, int role) const { int row = index.row(); int col = index.column(); if (role == Qt::DisplayRole && row == 0 && col == 0) return QTime::currentTime().toString(); return QVariant(); }
Il manque quelque chose pour que l'horloge tourne. Nous devons indiquer à la vue, toutes les secondes, que l'heure a changé et qu'elle doit être lue à nouveau. Pour ce faire, nous utilisons un minuteur. Dans le constructeur, nous fixons son intervalle à 1 seconde et connectons son signal timeout.
(fichier source : examples/widgets/tutorials/modelview/3_changingmodel/mymodel.cpp)
MyModel::MyModel(QObject *parent) : QAbstractTableModel(parent) , timer(new QTimer(this)) { timer->setInterval(1000); connect(timer, &QTimer::timeout , this, &MyModel::timerHit); timer->start(); }
Voici le slot correspondant :
(fichier source : examples/widgets/tutorials/modelview/3_changingmodel/mymodel.cpp)
void MyModel::timerHit() { // we identify the top left cell QModelIndex topLeft = createIndex(0,0); // emit a signal to make the view reread identified data emit dataChanged(topLeft, topLeft, {Qt::DisplayRole}); }
Nous demandons à la vue de lire à nouveau les données dans la cellule supérieure gauche en émettant le signal dataChanged(). Notez que nous n'avons pas explicitement connecté le signal dataChanged() à la vue. Cela s'est produit automatiquement lorsque nous avons appelé setModel().
2.4 Mise en place d'en-têtes pour les colonnes et les lignes
Les en-têtes peuvent être masqués via une méthode de la vue : tableView->verticalHeader()->hide();

Le contenu de l'en-tête, cependant, est défini par le modèle, nous réimplémentons donc la méthode headerData() :
(source du fichier : examples/widgets/tutorials/modelview/4_headers/mymodel.cpp)
QVariant MyModel::headerData(int section, Qt::Orientation orientation, int role) const { if (role == Qt::DisplayRole && orientation == Qt::Horizontal) { switch (section) { case 0: return QString("first"); case 1: return QString("second"); case 2: return QString("third"); } } return QVariant(); }
Notez que la méthode headerData() a également un paramètre role qui a la même signification que dans MyModel::data().
2.5 Exemple d'édition minimale
Dans cet exemple, nous allons construire une application qui remplit automatiquement le titre d'une fenêtre en répétant les valeurs saisies dans les cellules d'un tableau. Pour pouvoir accéder facilement au titre de la fenêtre, nous plaçons le QTableView dans un QMainWindow.
Le modèle décide si les capacités d'édition sont disponibles. Il suffit de modifier le modèle pour que les capacités d'édition disponibles soient activées. Pour ce faire, nous réimplémentons les méthodes virtuelles suivantes : setData() et flags().
(source du fichier : examples/widgets/tutorials/modelview/5_edit/mymodel.h)
// mymodel.h #include <QAbstractTableModel> #include <QString> const int COLS= 3; const int ROWS= 2; class MyModel : public QAbstractTableModel { Q_OBJECT public: MyModel(QObject *parent = nullptr); int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; Qt::ItemFlags flags(const QModelIndex &index) const override; private: QString m_gridData[ROWS][COLS]; //holds text entered into QTableView signals: void editCompleted(const QString &); };
Nous utilisons the un tableau à deux dimensions QString m_gridData pour stocker nos données. Cela fait de m_gridData le cœur de MyModel. Le reste de MyModel agit comme un wrapper et adapte m_gridData à l'interface QAbstractItemModel. Nous avons également introduit le signal editCompleted(), qui permet de transférer le texte modifié dans le titre de la fenêtre.
(source du fichier : examples/widgets/tutorials/modelview/5_edit/mymodel.cpp)
bool MyModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (role == Qt::EditRole) { if (!checkIndex(index)) return false; //save value from editor to member m_gridData m_gridData[index.row()][index.column()] = value.toString(); //for presentation purposes only: build and emit a joined string QString result; for (int row = 0; row < ROWS; row++) { for (int col= 0; col < COLS; col++) result += m_gridData[row][col] + ' '; } emit editCompleted(result); return true; } return false; }
setData() sera appelée chaque fois que l'utilisateur modifiera une cellule. Le paramètre index nous indique quel champ a été édité et value fournit le résultat du processus d'édition. Le rôle sera toujours défini sur Qt::EditRole car nos cellules ne contiennent que du texte. Si une case à cocher était présente et que les autorisations de l'utilisateur étaient définies de manière à permettre la sélection de la case, les appels seraient également effectués avec le rôle défini sur Qt::CheckStateRole.
(source du fichier : examples/widgets/tutorials/modelview/5_edit/mymodel.cpp)
Qt::ItemFlags MyModel::flags(const QModelIndex &index) const { return Qt::ItemIsEditable | QAbstractTableModel::flags(index); }
Diverses propriétés d'une cellule peuvent être ajustées à l'aide de flags().
Le retour de Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled est suffisant pour montrer à un éditeur qu'une cellule peut être sélectionnée.
Si l'édition d'une cellule modifie plus de données que les données de cette cellule particulière, le modèle doit émettre un signal dataChanged() pour que les données modifiées puissent être lues.
3. Sujets intermédiaires
3.1 Arborescence
Vous pouvez convertir l'exemple ci-dessus en une application avec une vue arborescente. Il suffit de remplacer QTableView par QTreeView, ce qui donne un arbre en lecture/écriture. Aucune modification ne doit être apportée au modèle. L'arbre n'aura pas de hiérarchie parce qu'il n'y a pas de hiérarchie dans le modèle lui-même.

QListViewLes systèmes de gestion des données, QTableView et QTreeView, utilisent tous une abstraction de modèle, qui est une liste, une table et un arbre fusionnés. Cela permet d'utiliser plusieurs types de classes de vues différentes à partir du même modèle.

Voici à quoi ressemble notre modèle d'exemple jusqu'à présent :

Nous voulons présenter un véritable arbre. Nous avons enveloppé nos données dans les exemples ci-dessus afin de créer un modèle. Cette fois, nous utilisons QStandardItemModel, qui est un conteneur de données hiérarchiques qui implémente également QAbstractItemModel. Pour afficher un arbre, QStandardItemModel doit être peuplé de QStandardItems, qui peuvent contenir toutes les propriétés standard d'éléments tels que du texte, des polices, des cases à cocher ou des brosses.

(source du fichier : examples/widgets/tutorials/modelview/6_treeview/mainwindow.cpp)
// modelview.cpp #include "mainwindow.h" #include <QTreeView> #include <QStandardItemModel> #include <QStandardItem> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , treeView(new QTreeView(this)) , standardModel(new QStandardItemModel(this)) { setCentralWidget(treeView); QList<QStandardItem *> preparedRow = prepareRow("first", "second", "third"); QStandardItem *item = standardModel->invisibleRootItem(); // adding a row to the invisible root item produces a root element item->appendRow(preparedRow); QList<QStandardItem *> secondRow = prepareRow("111", "222", "333"); // adding a row to an item starts a subtree preparedRow.first()->appendRow(secondRow); treeView->setModel(standardModel); treeView->expandAll(); } QList<QStandardItem *> MainWindow::prepareRow(const QString &first, const QString &second, const QString &third) const { return {new QStandardItem(first), new QStandardItem(second), new QStandardItem(third)}; }
Il suffit d'instancier un QStandardItemModel et d'ajouter quelques QStandardItems au constructeur. Nous pouvons alors créer une structure de données hiérarchique car un QStandardItem peut contenir d'autres QStandardItems. Les nœuds sont réduits et développés dans la vue.
3.2 Travailler avec des sélections
Nous voulons accéder au contenu d'un élément sélectionné afin de l'afficher dans le titre de la fenêtre avec le niveau hiérarchique.

Créons donc quelques éléments :
(source du fichier : examples/widgets/tutorials/modelview/7_selections/mainwindow.cpp)
#include "mainwindow.h" #include <QTreeView> #include <QStandardItemModel> #include <QItemSelectionModel> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , treeView(new QTreeView(this)) , standardModel(new QStandardItemModel(this)) { setCentralWidget(treeView); auto *rootNode = standardModel->invisibleRootItem(); // defining a couple of items auto *americaItem = new QStandardItem("America"); auto *mexicoItem = new QStandardItem("Canada"); auto *usaItem = new QStandardItem("USA"); auto *bostonItem = new QStandardItem("Boston"); auto *europeItem = new QStandardItem("Europe"); auto *italyItem = new QStandardItem("Italy"); auto *romeItem = new QStandardItem("Rome"); auto *veronaItem = new QStandardItem("Verona"); // building up the hierarchy rootNode-> appendRow(americaItem); rootNode-> appendRow(europeItem); americaItem-> appendRow(mexicoItem); americaItem-> appendRow(usaItem); usaItem-> appendRow(bostonItem); europeItem-> appendRow(italyItem); italyItem-> appendRow(romeItem); italyItem-> appendRow(veronaItem); // register the model treeView->setModel(standardModel); treeView->expandAll(); // selection changes shall trigger a slot QItemSelectionModel *selectionModel = treeView->selectionModel(); connect(selectionModel, &QItemSelectionModel::selectionChanged, this, &MainWindow::selectionChangedSlot); }
Les vues gèrent les sélections dans un modèle de sélection distinct, qui peut être récupéré à l'aide de la méthode selectionModel(). Nous récupérons le modèle de sélection afin de connecter un slot à son signal selectionChanged().
(source du fichier : examples/widgets/tutorials/modelview/7_selections/mainwindow.cpp)
void MainWindow::selectionChangedSlot(const QItemSelection & /*newSelection*/, const QItemSelection & /*oldSelection*/) { // get the text of the selected item const QModelIndex index = treeView->selectionModel()->currentIndex(); QString selectedText = index.data(Qt::DisplayRole).toString(); // find out the hierarchy level of the selected item int hierarchyLevel = 1; QModelIndex seekRoot = index; while (seekRoot.parent().isValid()) { seekRoot = seekRoot.parent(); hierarchyLevel++; } QString showString = QString("%1, Level %2").arg(selectedText) .arg(hierarchyLevel); setWindowTitle(showString); }
Nous obtenons l'index du modèle qui correspond à la sélection en appelant treeView->selectionModel()->currentIndex() et nous obtenons la chaîne du champ en utilisant l'index du modèle. Ensuite, nous calculons simplement l'élément hierarchyLevel. Les éléments de premier niveau n'ont pas de parents et la méthode parent() renverra un QModelIndex() construit par défaut. C'est pourquoi nous utilisons la méthode parent() pour itérer jusqu'au niveau supérieur tout en comptant les étapes réalisées au cours de l'itération.
Le modèle de sélection (comme indiqué ci-dessus) peut être récupéré, mais il peut également être défini avec QAbstractItemView::setSelectionModel. C'est ainsi qu'il est possible d'avoir 3 classes de vues avec des sélections synchronisées parce qu'une seule instance d'un modèle de sélection est utilisée. Pour partager un modèle de sélection entre 3 vues, utilisez selectionModel() et assignez le résultat aux deuxième et troisième classes de vues avec setSelectionModel().
3.3 Modèles prédéfinis
La manière habituelle d'utiliser les modèles/vues est d'envelopper des données spécifiques pour les rendre utilisables avec les classes de vues. Cependant, Qt fournit également des modèles prédéfinis pour les structures de données sous-jacentes courantes. Si l'une des structures de données disponibles convient à votre application, un modèle prédéfini peut être un bon choix.
| QStringListModel | Stocke une liste de chaînes de caractères |
| QStandardItemModel | Stocke des éléments hiérarchiques arbitraires |
| QFileSystemModel | Encapsule le système de fichiers local |
| QSqlQueryModel | Encapsule un ensemble de résultats SQL |
| QSqlTableModel | Encapsule une table SQL |
| QSqlRelationalTableModel | Encapsule une table SQL avec des clés étrangères |
| QSortFilterProxyModel | Trie et/ou filtre un autre modèle |
3.4 Délégués
Dans tous les exemples présentés jusqu'à présent, les données sont présentées sous forme de texte ou de case à cocher dans une cellule et sont modifiées sous forme de texte ou de case à cocher. Le composant qui fournit ces services de présentation et d'édition est appelé délégué. Nous commençons à peine à travailler avec le délégué car la vue utilise un délégué par défaut. Mais imaginons que nous voulions un éditeur différent (par exemple, un curseur ou une liste déroulante) ou que nous voulions présenter des données sous forme de graphiques. Examinons un exemple appelé Star Delegate, dans lequel les étoiles sont utilisées pour afficher une note :

La vue possède une méthode setItemDelegate() qui remplace le délégué par défaut et installe un délégué personnalisé. Un nouveau délégué peut être écrit en créant une classe qui hérite de QStyledItemDelegate. Afin d'écrire un délégué qui affiche des étoiles et n'a pas de capacités d'entrée, nous avons seulement besoin de surcharger 2 méthodes.
class StarDelegate : public QStyledItemDelegate { Q_OBJECT public: StarDelegate(QWidget *parent = nullptr); void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; };
paint() dessine les étoiles en fonction du contenu des données sous-jacentes. Les données peuvent être consultées en appelant index.data(). La méthode sizeHint() du délégué est utilisée pour obtenir les dimensions de chaque étoile, afin que la cellule ait une hauteur et une largeur suffisantes pour accueillir les étoiles.
L'écriture de délégués personnalisés est le bon choix si vous souhaitez afficher vos données avec une représentation graphique personnalisée à l'intérieur de la grille de la classe View. Si vous souhaitez quitter la grille, vous ne devez pas utiliser un délégué personnalisé mais une classe de vue personnalisée.
Autres références aux délégués dans la documentation Qt :
- Classes de délégués
- QAbstractItemDelegate Class Reference
- QSqlRelationalDelegate Class Reference
- QStyledItemDelegate Class Reference
- QItemDelegate Class Reference
3.5 Débogage avec ModelTest
La nature passive des modèles pose de nouveaux défis aux programmeurs. Des incohérences dans le modèle peuvent faire planter l'application. Comme le modèle est touché par de nombreux appels de la vue, il est difficile de savoir quel appel a fait planter l'application et quelle opération a introduit le problème.
Qt Labs fournit un logiciel appelé ModelTest, qui vérifie les modèles pendant l'exécution de votre programmation. Chaque fois que le modèle est modifié, ModelTest analyse le modèle et signale les erreurs à l'aide d'un assert. Ceci est particulièrement important pour les modèles d'arbres, car leur nature hiérarchique laisse de nombreuses possibilités d'incohérences subtiles.
Contrairement aux classes de vues, ModelTest utilise des index hors plage pour tester le modèle. Cela signifie que votre application peut planter avec ModelTest même si elle fonctionne parfaitement sans lui. Vous devez donc également gérer tous les index qui sont hors de portée lorsque vous utilisez ModelTest.
Exemples
Qt est livré avec de nombreux exemples pour model/view. Vous pouvez les trouver sur la page Item Views Examples.
© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.





