Tutoriel Modèle/Vue
Tout développeur d'interface utilisateur devrait connaître la programmation Modèle-Vue, et l'objectif de ce tutoriel est de vous fournir une introduction facile à comprendre à ce sujet.
Les widgets de type tableau, liste et arborescence sont des composants fréquemment utilisés dans les interfaces graphiques. Il existe deux façons différentes pour ces widgets d’accéder à leurs données. La méthode traditionnelle consiste à utiliser des widgets comportant des conteneurs internes destinés au stockage des données. Cette approche est très intuitive, mais dans de nombreuses applications complexes, elle entraîne des problèmes de synchronisation des données. La deuxième approche est la programmation Modèle/Vue, dans laquelle les widgets ne gèrent pas de conteneurs de données internes. Ils accèdent aux données externes via une interface standardisée et évitent ainsi la duplication des données. Cela peut sembler compliqué au premier abord, mais en y regardant de plus près, non seulement c’est facile à comprendre, mais les nombreux avantages de la programmation Modèle/Vue apparaissent également plus clairement.

Au cours de ce processus, nous découvrirons 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
- Le développement d'une application modèle/vue simple
- Les modèles prédéfinis
- Sujets intermédiaires tels que :
- Les vues arborescentes
- Sélection
- Délégués
- Débogage à l'aide du test de modèle
Vous découvrirez également si votre nouvelle application peut être développée plus facilement grâce à la programmation modèle/vue ou si les widgets classiques conviennent tout aussi bien.
Ce tutoriel comprend des exemples de code que vous pouvez modifier et intégrer à votre projet. Le code source du tutoriel se trouve dans le répertoire examples/widgets/tutorials/modelview de Qt.
Pour plus d'informations, vous pouvez également consulter la documentation de référence
1. Introduction
Le modèle/vue 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, c'est pourquoi Qt propose deux types de widgets différents. Ces deux types de widgets ont le même aspect, mais ils interagissent différemment avec les données.
| Les widgets standard utilisent des données qui font partie intégrante du widget. |
|
| Les classes de vue 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 bidimensionnel 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 qu’il fournit. Cette méthode est très intuitive et utile dans de nombreuses applications, mais l'affichage et la modification d'une table de base de données à l'aide d'un widget de table standard peuvent s'avérer problématiques. Il faut en effet coordonner deux copies des données : l'une à l'extérieur du widget, l'autre à l'intérieur. C'est au développeur qu'il incombe de synchroniser ces deux versions. De plus, le couplage étroit entre la présentation et les données complique l'écriture de tests unitaires.
1.2 Le modèle/vue à la rescousse
Le modèle/vue est venu à la rescousse en proposant une solution reposant sur une architecture plus polyvalente. Le modèle/vue élimine les problèmes de cohérence des données pouvant survenir avec les widgets standard. Il facilite également l’utilisation de plusieurs vues pour les mêmes données, car un même modèle peut être transmis à de nombreuses vues. La différence la plus importante réside dans le fait que les widgets Modèle/Vue ne stockent pas de données derrière les cellules du tableau. En effet, ils opèrent directement à partir de vos données. Comme les classes de vue ne connaissent pas la structure de vos données, vous devez fournir un wrapper 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 implémentant l’interface ` QAbstractItemModel ` est considérée comme un modèle. Une fois que la vue reçoit un pointeur vers un modèle, elle lit et affiche son contenu, et fait office d’éditeur pour celui-ci.
1.3 Présentation des widgets Modèle/Vue
Voici un aperçu des widgets modèle/vue et de leurs widgets standard correspondants.
| Widget | du widget standard (classe utilitaire basée sur les éléments) | de la classe de vue « modèle/vue » (à utiliser avec des données externes) |
|---|---|---|
![]() | QListWidget | QListView |
![]() | QTableWidget | QTableView |
![]() | QTreeWidget | QTreeView |
![]() | QColumnView affiche une arborescence sous forme de hiérarchie de listes | |
![]() | QComboBox peut fonctionner à la fois comme classe de vue et comme widget traditionnel | |
1.4 Utilisation d’adaptateurs entre les formulaires et les modèles
Il peut s'avérer utile d'utiliser des adaptateurs entre les formulaires et les modèles.
Nous pouvons modifier les données stockées dans les tableaux directement à partir du tableau lui-même, mais il est bien plus pratique de les modifier dans des champs de texte. Il n'existe pas d'équivalent direct « modèle/vue » qui sépare les données et les vues pour les widgets fonctionnant sur une seule valeur (QLineEdit, QCheckBox...) plutôt que sur un ensemble de données ; nous avons donc besoin d'un adaptateur pour relier le formulaire à la source de données.
QDataWidgetMapper est une excellente solution, car elle associe les widgets du formulaire à une ligne du tableau et facilite grandement la création de formulaires pour les tables de base de données.

Un autre exemple d’adaptateur est QCompleter. Qt propose QCompleter pour fournir des suggestions automatiques dans les Qt Widgets tels que QComboBox et, comme illustré 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ù commencer ? Nous vous recommandons de commencer par un exemple simple et de le développer étape par étape. Cela facilite grandement la compréhension de l’architecture. Tenter de comprendre l’architecture modèle/vue en détail avant de lancer 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 contenant des données de démonstration. Essayez donc ! Il vous suffit de remplacer les données des exemples ci-dessous par les vôtres.
Vous trouverez ci-dessous 7 applications très simples et indépendantes qui illustrent différents aspects de la programmation modèle/vue. Le code source se trouve dans le répertoire « examples/widgets/tutorials/modelview ».
2.1 Une table en lecture seule
Nous commençons par une application qui utilise une vue « QTableView » pour afficher des données. Nous ajouterons des fonctionnalités d'édition ultérieurement.
(fichier source : 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 vers celle-ci à ` tableView`. ` tableView ` invoquera les méthodes du pointeur qu'il a reçu afin de déterminer deux choses :
- le nombre de lignes et de colonnes à afficher ;
- Quel contenu doit être affiché dans chaque cellule.
Le modèle doit comporter du code pour répondre à ces demandes.
Nous disposons d'un ensemble de données sous forme de tableau ; commençons donc par QAbstractTableModel, car cette méthode est plus simple à utiliser que la méthode plus générale QAbstractItemModel.
(fichier source : 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 l'implémentation de trois méthodes abstraites.
(fichier source : 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 les fonctions ` MyModel::rowCount()` et ` MyModel::columnCount()`. Lorsque la vue a besoin de connaître le texte d'une cellule, elle appelle la méthode ` MyModel::data()`. Les informations relatives à la ligne et à la colonne sont spécifiées via le paramètre ` index ` et le rôle est défini par ` 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 ` disposerait d’un membre appelé ` MyData`, qui servirait de cible pour toutes les opérations de lecture et d’écriture.
Ce petit exemple illustre 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 se contente de fournir des données chaque fois que la vue en fait la 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 doivent être relues ? Le modèle doit émettre un signal indiquant quelle plage de cellules a été modifiée. Ceci sera illustré dans la section 2.3.
2.2 Extension de l’exemple « en lecture seule » avec des rôles
Outre le contrôle du texte affiché par la vue, le modèle contrôle également l'apparence de ce texte. Lorsque nous modifions légèrement le modèle, nous obtenons le résultat suivant :

En réalité, il suffit de modifier la méthode ` data()` pour définir la police, la couleur d’arrière-plan, l’alignement et l’affichage d’une case à cocher. Vous trouverez ci-dessous la méthode ` data()` qui produit le résultat présenté ci-dessus. La différence réside dans le fait que, cette fois-ci, nous utilisons le paramètre `int role` pour renvoyer différentes informations en fonction de sa valeur.
(fichier source : examples/widgets/tutorials/modelview/2_formatting/mymodel.cpp)
// mymodel.cpp
QVariant MyModel::data(const QModelIndex&index, int role) const
{
int row = index.row();
int col = index.column();
// générer un message de journalisation lorsque cette méthode est appelée
qDebug() << QString("row %1, col%2, role %3")
.arg(ligne).arg(colonne).arg(rôle) ;
switch (rôle) {
case Qt::DisplayRole :
if (row== 0&& col== 1) return QString("<--gauche");
if (row== 1&& col== 1) return QString("right-->");
return QString("Ligne %1, Colonne %2")
.arg(row + 1)
.arg(col+ 1);
case Qt::FontRole:
if (row== 0&& col== 0) { // modifier la police uniquement pour la cellule (0,0)
QFont boldFont;
boldFont.setBold(true);
return boldFont;
}
break;
case Qt::BackgroundRole :
if (row== 1&& col== 2) // modifier l’arrière-plan uniquement pour la cellule (1,2)
return QBrush(Qt::red);
break;
case Qt::TextAlignmentRole:
if (row== 1&& col== 1) // modifier 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) // ajouter une case à cocher à la cellule (1,0)
return Qt::Checked;
break;
}
return QVariant();
}Chaque propriété de mise en forme sera demandée au modèle via un appel distinct à la méthode data(). Le paramètre role permet d'indiquer au modèle quelle propriété est demandée :
| enum Qt::ItemDataRole | Signification | Type |
|---|---|---|
| Qt::DisplayRole | text | QString |
| Qt::FontRole | font | QFont |
| BackgroundRole | pinceau pour l'arrière-plan de la cellule | QBrush |
| Qt::TextAlignmentRole | alignement du texte | enum Qt::AlignmentFlag |
| Qt::CheckStateRole | masque les cases à cocher avec QVariant(), active les cases à cocher avec Qt::Checked | enum Qt::ItemDataRole |
Consultez la documentation relative à l'espace de noms Qt pour en savoir plus sur les fonctionnalités de l’énumération « Qt::ItemDataRole ».
Nous devons maintenant déterminer l’impact de l’utilisation d’un modèle séparé sur les performances de l’application ; observons donc à quelle fréquence la vue appelle la méthode ` data()`. Afin de suivre la fréquence à laquelle la vue appelle le modèle, nous avons inséré une instruction de débogage dans la méthode ` data()`, qui enregistre les informations dans le flux de sortie d’erreurs. Dans notre petit exemple, la méthode ` data()` sera appelée 42 fois. Chaque fois que vous passez le curseur sur un champ, la méthode ` data()` sera appelée à nouveau – 7 fois pour chaque cellule. C’est pourquoi il est important de s’assurer que vos données sont disponibles lorsque la méthode ` data()` est invoquée 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-ci, le contenu change toutes les secondes, car nous affichons l’heure actuelle.
(fichier source : 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 fonctionne. Nous devons signaler à la vue, toutes les secondes, que l'heure a changé et qu'elle doit être relue. Pour cela, nous utilisons un temporisateur. Dans le constructeur, nous définissons son intervalle à 1 seconde et nous connectons son signal de fin de délai.
(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 relire les données de la cellule en haut à gauche en émettant le signal « dataChanged() ». Notez que nous n'avons pas explicitement connecté le signal « dataChanged() » à la vue. Cela s'est fait automatiquement lorsque nous avons appelé « setModel() ».
2.4 Configuration des 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 est toutefois défini via le modèle ; nous réimplémentons donc la méthode ` headerData()` :
(fichier source : 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()` comporte également un paramètre `role` qui a la même signification que dans ` MyModel::data()`.
2.5 L'exemple d'édition minimale
Dans cet exemple, nous allons créer une application qui remplit automatiquement le titre d'une fenêtre en y 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 la méthode ` QTableView ` dans une méthode ` QMainWindow`.
C’est le modèle qui détermine si les fonctionnalités d’édition sont disponibles. Il suffit de modifier le modèle pour que les fonctionnalités d’édition disponibles soient activées. Pour ce faire, il faut réimplémenter les méthodes virtuelles suivantes : ` setData()` et ` flags()`.
(fichier source : 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, le tableau bidimensionnel QString et 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.
(fichier source : 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 modifie une cellule. Le paramètre index indique quel champ a été modifié et value fournit le résultat de la modification. 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 permettaient de la cocher, les appels seraient également effectués avec le rôle défini sur Qt::CheckStateRole.
(fichier source : 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 modifiées à l’aide de la fonction ` flags()`.
Il suffit de renvoyer Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled pour indiquer à un éditeur qu'une cellule peut être sélectionnée.
Si la modification d’une cellule entraîne la modification de données autres que celles contenues dans cette cellule, le modèle doit émettre un signal ` dataChanged()` afin que les données modifiées puissent être lues.
3. Sujets intermédiaires
3.1 TreeView
Vous pouvez transformer l'exemple ci-dessus en une application dotée d'une arborescence. Il suffit de remplacer QTableView par QTreeView, ce qui donne une arborescence en lecture-écriture. Aucune modification n'est nécessaire au niveau du modèle. L'arborescence ne comportera aucune hiérarchie, car le modèle lui-même n'en comporte pas.

QListView, QTableView et QTreeView utilisent tous une abstraction de modèle, qui est une combinaison de liste, de tableau et d’arborescence. Cela permet d’utiliser plusieurs types différents de classes de vue à partir d’un même modèle.

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

Nous souhaitons afficher un véritable arbre. Dans les exemples ci-dessus, nous avons encapsulé nos données afin de créer un modèle. Cette fois-ci, nous utilisons ` QStandardItemModel`, qui est un conteneur de données hiérarchiques implémentant également ` QAbstractItemModel`. Pour afficher un arbre, ` QStandardItemModel ` doit être renseigné avec des ` QStandardItem`, qui peuvent contenir toutes les propriétés standard des éléments, telles que le texte, les polices, les cases à cocher ou les pinceaux.

(fichier source : 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. On peut alors créer une structure de données hiérarchique, car un ` QStandardItem ` peut contenir d’autres ` QStandardItems`. Les nœuds sont repliés et développés au sein de la vue.
3.2 Utilisation des sélections
Nous souhaitons accéder au contenu d'un élément sélectionné afin de l'afficher dans le titre de la fenêtre, accompagné de son niveau hiérarchique.

Créons donc quelques éléments :
(fichier source : 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 au sein d’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()`.
(fichier source : 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 correspondant à la sélection en appelant treeView->selectionModel()->currentIndex() et nous récupérons la chaîne de caractères du champ à l’aide de cet index. Il suffit ensuite de calculer l’ hierarchyLevel de l’élément. Les éléments de niveau supérieur n’ont pas de parents et la méthode parent() renvoie une QModelIndex() construite par défaut. C’est pourquoi nous utilisons la méthode parent() pour remonter jusqu’au niveau supérieur tout en comptant les étapes effectuées au cours de l’itération.
Le modèle de sélection (tel qu’illustré ci-dessus) peut être récupéré, mais il peut également être défini à l’aide de ` QAbstractItemView::setSelectionModel`. C’est ainsi qu’il est possible d’avoir 3 classes de vue avec des sélections synchronisées, car une seule instance d’un modèle de sélection est utilisée. Pour partager un modèle de sélection entre trois vues, utilisez ` selectionModel()` et attribuez le résultat aux deuxième et troisième classes de vue à l’aide de ` setSelectionModel()`.
3.3 Modèles prédéfinis
La méthode classique pour utiliser le modèle/vue consiste à encapsuler des données spécifiques afin de les rendre utilisables avec les classes de vue. Qt fournit toutefois é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 constituer un bon choix.
| QRangeModel | Adapte un conteneur ou une plage C++ existant(e) |
| 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 jeu 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 affiché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 d’affichage et de modification s’appelle un délégué. Nous commençons tout juste à travailler avec les délégués, car la vue utilise un délégué par défaut. Mais imaginons que nous souhaitions disposer d’un éditeur différent (par exemple, un curseur ou une liste déroulante). Ou imaginons que nous souhaitions présenter les données sous forme de graphiques. Examinons un exemple appelé « Star Delegate », dans lequel des étoiles sont utilisées pour afficher une note :

La vue dispose d’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 créé en définissant une classe héritant de ` QStyledItemDelegate`. Pour écrire un délégué qui affiche des étoiles et ne dispose d’aucune fonctionnalité de saisie, il suffit de redéfinir deux 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;
};paintLa méthode () dessine des étoiles en fonction du contenu des données sous-jacentes. Ces données peuvent être consultées en appelant index.data(). La méthode sizeHint() du délégué sert à obtenir les dimensions de chaque étoile, afin que la cellule dispose d’une hauteur et d’une largeur suffisantes pour les accueillir.
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 de vue. Si vous souhaitez sortir de la grille, vous n'utiliserez pas de 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 entraîner le plantage de l’application. Étant donné que le modèle reçoit de nombreux appels provenant de la vue, il est difficile de déterminer quel appel a provoqué le plantage de l’application et quelle opération est à l’origine du problème.
Qt Labs fournit un logiciel appelé ModelTest, qui vérifie les modèles pendant l’exécution de votre programme. Chaque fois que le modèle est modifié, ModelTest l’analyse et signale les erreurs à l’aide d’une assertion. Ceci est particulièrement important pour les modèles arborescents, car leur nature hiérarchique laisse place à de nombreuses possibilités d’incohérences subtiles.
Contrairement aux classes de vue, ModelTest utilise des index hors limites 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 hors limites lorsque vous utilisez ModelTest.
Exemples
Qt est fourni avec de nombreux exemples pour le modèle/vue. Vous pouvez les trouver sur la page Exemples de vues d’éléments.
© 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.





