Exemple de modèle d'arbre simple
L'exemple « Modèle d'arborescence simple » montre comment utiliser un modèle hiérarchique avec les classes de vue standard de Qt.

L'architecture modèle/vue de Qt fournit un moyen standard permettant aux vues de manipuler les informations d'une source de données, en utilisant un modèle abstrait des données afin de simplifier et de normaliser leur accès. Les modèles simples représentent les données sous la forme d'un tableau d'éléments et permettent aux vues d'accéder à ces données via un système basé sur des index. Plus généralement, les modèles peuvent être utilisés pour représenter des données sous la forme d'une structure arborescente en permettant à chaque élément d'agir en tant que parent d'un tableau d'éléments enfants.
Avant de tenter d’implémenter un modèle arborescent, il convient de se demander si les données proviennent d’une source externe ou si elles seront gérées au sein même du modèle. Dans cet exemple, nous allons implémenter une structure interne pour stocker les données plutôt que d’aborder la manière d’intégrer des données provenant d’une source externe.
Conception et concepts
La structure de données que nous utilisons pour représenter l'organisation des données prend la forme d'un arbre constitué d'objets ` TreeItem `. Chaque ` TreeItem ` représente un élément dans une arborescence et contient plusieurs colonnes de données.
| Structure simple du modèle arborescent Les données sont stockées en interne dans le modèle à l’aide d’objets « Chaque « Étant donné que chaque élément d’une arborescence contient généralement plusieurs colonnes de données (un titre et un résumé dans cet exemple), il est naturel de stocker ces informations dans chaque élément. Par souci de simplicité, nous utiliserons une liste d’objets ` QVariant ` pour stocker les données de chaque colonne de l’élément. |
L’utilisation d’une structure arborescente basée sur des pointeurs signifie que, lorsque l’on transmet un index de modèle à une vue, on peut enregistrer l’adresse de l’élément correspondant dans l’index (voir QAbstractItemModel::createIndex()) et la récupérer ultérieurement avec QModelIndex::internalPointer(). Cela facilite l’écriture du modèle et garantit que tous les index de modèle faisant référence au même élément possèdent le même pointeur de données interne.
Une fois la structure de données appropriée en place, nous pouvons créer un modèle arborescent avec un minimum de code supplémentaire pour fournir des index de modèle et des données à d’autres composants.
Définition de la classe `TreeItem`
La classe ` TreeItem ` est définie comme suit :
class TreeItem
{
public:
explicit TreeItem(QVariantList data, TreeItem *parentItem = nullptr);
void appendChild(std::unique_ptr<TreeItem> &&child);
TreeItem *child(int row);
int childCount() const;
int columnCount() const;
QVariant data(int column) const;
int row() const;
TreeItem *parentItem();
private:
std::vector<std::unique_ptr<TreeItem>> m_childItems;
QVariantList m_itemData;
TreeItem *m_parentItem;
};Il s'agit d'une classe C++ de base. Elle n'hérite pas de la classe ` QObject ` et ne fournit ni signaux ni slots. Elle sert à contenir une liste de `QVariants`, renfermant les données des colonnes ainsi que des informations sur leur position dans la structure arborescente. Les fonctions offrent les fonctionnalités suivantes :
- La classe `
appendChildItem()` sert à ajouter des données lors de la construction initiale du modèle et n’est pas utilisée en fonctionnement normal. - Les fonctions `
child()` et `childCount()` permettent au modèle d’obtenir des informations sur tous les éléments enfants. - La fonction `
columnCount()` fournit des informations sur le nombre de colonnes associées à l’élément, et les données de chaque colonne peuvent être obtenues à l’aide de la fonction `data()`. - Les fonctions
row()etparent()permettent d’obtenir le numéro de ligne de l’élément et son élément parent.
L’élément parent et les données des colonnes sont stockés dans les variables membres privées ` parentItem ` et ` itemData `. La variable ` childItems ` contient une liste de pointeurs vers les éléments enfants de l’élément.
Implémentation de la classe `TreeItem`
Le constructeur sert uniquement à enregistrer le parent de l'élément et les données associées à chaque colonne.
TreeItem::TreeItem(QVariantList data, TreeItem *parent)
: m_itemData(std::move(data)), m_parentItem(parent)
{}Un pointeur vers chacun des éléments enfants appartenant à cet élément sera stocké dans la variable membre privée ` childItems ` sous la forme d'un `std::unique_ptr`. Lorsque le destructeur de la classe est appelé, les éléments enfants sont automatiquement supprimés afin de garantir la réutilisation de leur mémoire :
Étant donné que chacun des éléments enfants est construit lors du remplissage initial du modèle avec des données, la fonction permettant d’ajouter des éléments enfants est simple :
void TreeItem::appendChild(std::unique_ptr<TreeItem> &&child)
{
m_childItems.push_back(std::move(child));
}Chaque élément est capable de renvoyer n’importe lequel de ses éléments enfants lorsqu’on lui fournit un numéro de ligne approprié. Par exemple, dans le diagramme ci-dessus, l'élément marqué de la lettre « A » correspond au fils de l'élément racine avec row = 0, l'élément « B » est un fils de l'élément « A » avec row = 1, et l'élément « C » est un fils de l'élément racine avec row = 1.
La fonction child() renvoie le fils qui correspond au numéro de ligne spécifié dans la liste des fils de l'élément :
TreeItem *TreeItem::child(int row)
{
return row >= 0 && row < childCount() ? m_childItems.at(row).get() : nullptr;
}Le nombre d’éléments enfants peut être obtenu à l’aide de ` childCount()` :
int TreeItem::childCount() const
{
return int(m_childItems.size());
}L'TreeModel utilise cette fonction pour déterminer le nombre de lignes existantes pour un élément parent donné.
La fonction row() indique la position de l'élément dans la liste des éléments de son élément parent :
int TreeItem::row() const
{
if (m_parentItem == nullptr)
return 0;
const auto it = std::find_if(m_parentItem->m_childItems.cbegin(), m_parentItem->m_childItems.cend(),
[this](const std::unique_ptr<TreeItem> &treeItem) {
return treeItem.get() == this;
});
if (it != m_parentItem->m_childItems.cend())
return std::distance(m_parentItem->m_childItems.cbegin(), it);
Q_ASSERT(false); // should not happen
return -1;
}Notez que, bien que l'élément racine (sans élément parent) se voie automatiquement attribuer le numéro de ligne 0, cette information n'est jamais utilisée par le modèle.
Le nombre de colonnes de données contenues dans l'élément est simplement renvoyé par la fonction ` columnCount() `.
int TreeItem::columnCount() const
{
return int(m_itemData.count());
}Les données des colonnes sont renvoyées par la fonction ` data() `. Nous utilisons la fonction de commodité ` QList::value()` qui vérifie les limites et renvoie un objet ` QVariant ` instancié par défaut en cas de dépassement :
QVariant TreeItem::data(int column) const
{
return m_itemData.value(column);
}Le parent de l’élément est trouvé à l’aide de ` parent()` :
TreeItem *TreeItem::parentItem()
{
return m_parentItem;
}Notez que, comme l'élément racine du modèle n'aura pas de parent, cette fonction renverra zéro dans ce cas. Nous devons nous assurer que le modèle gère correctement ce cas lors de l'implémentation de la fonction TreeModel::parent().
Définition de la classe `TreeModel`
La classe TreeModel est définie comme suit :
class TreeModel : public QAbstractItemModel
{
Q_OBJECT
public:
Q_DISABLE_COPY_MOVE(TreeModel)
explicit TreeModel(const QString &data, QObject *parent = nullptr);
~TreeModel() override;
QVariant data(const QModelIndex &index, int role) const override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const override;
QModelIndex index(int row, int column,
const QModelIndex &parent = {}) const override;
QModelIndex parent(const QModelIndex &index) const override;
int rowCount(const QModelIndex &parent = {}) const override;
int columnCount(const QModelIndex &parent = {}) const override;
private:
static void setupModelData(const QList<QStringView> &lines, TreeItem *parent);
std::unique_ptr<TreeItem> rootItem;
};Cette classe est similaire à la plupart des autres sous-classes de QAbstractItemModel qui fournissent des modèles en lecture seule. Seules la forme du constructeur et la fonction setupModelData() sont spécifiques à ce modèle. De plus, nous fournissons un destructeur pour effectuer le nettoyage lorsque le modèle est détruit.
Implémentation de la classe `TreeModel`
Par souci de simplicité, le modèle ne permet pas la modification de ses données. Par conséquent, le constructeur prend un argument contenant les données que le modèle partagera avec les vues et les délégués :
TreeModel::TreeModel(const QString &data, QObject *parent)
: QAbstractItemModel(parent)
, rootItem(std::make_unique<TreeItem>(QVariantList{tr("Title"), tr("Summary")}))
{
setupModelData(QStringView{data}.split(u'\n'), rootItem.get());
}C'est au constructeur qu'il revient de créer un élément racine pour le modèle. Cet élément ne contient que des données d'en-tête verticales, pour des raisons pratiques. Nous l’utilisons également pour référencer la structure de données interne qui contient les données du modèle, et il sert à représenter un parent imaginaire des éléments de niveau supérieur dans le modèle. L’élément racine est géré à l’aide d’un std::unique_ptr afin de garantir que l’arborescence complète des éléments soit supprimée lorsque le modèle est supprimé.
La structure de données interne du modèle est remplie d’éléments par la fonction ` setupModelData() `. Nous examinerons cette fonction séparément à la fin de ce document.
Le destructeur garantit que l’élément racine et tous ses descendants sont supprimés lorsque le modèle est détruit. Cela se fait automatiquement puisque l’élément racine est stocké dans un unique_ptr.
TreeModel::~TreeModel() = default;Comme il n’est pas possible d’ajouter des données au modèle une fois qu’il a été construit et configuré, cela simplifie la gestion de l’arborescence interne des éléments.
Les modèles doivent implémenter une fonction ` index() ` afin de fournir des index que les vues et les délégués peuvent utiliser pour accéder aux données. Des index sont créés pour d’autres composants lorsqu’ils sont référencés par leurs numéros de ligne et de colonne, ainsi que par l’index de leur modèle parent. Si un index de modèle non valide est spécifié comme parent, il appartient au modèle de renvoyer un index correspondant à un élément de niveau supérieur dans le modèle.
Lorsqu’un index de modèle nous est fourni, nous vérifions d’abord s’il est valide. S’il ne l’est pas, nous supposons qu’il s’agit d’une référence à un élément de niveau supérieur ; dans le cas contraire, nous obtenons le pointeur de données à partir de l’index du modèle via sa fonction ` internalPointer()` et l’utilisons pour référencer un objet ` TreeItem `. Notez que tous les index de modèle que nous construisons contiendront un pointeur vers un objet ` TreeItem` existant ; nous pouvons donc garantir que tout index de modèle valide que nous recevons contiendra un pointeur de données valide.
QModelIndex TreeModel::index(int row, int column, const QModelIndex &parent) const
{
if (!hasIndex(row, column, parent))
return {};
TreeItem *parentItem = parent.isValid()
? static_cast<TreeItem*>(parent.internalPointer())
: rootItem.get();
if (auto *childItem = parentItem->child(row))
return createIndex(row, column, childItem);
return {};
}Étant donné que les arguments « row » et « column » de cette fonction font référence à un élément enfant de l’élément parent correspondant, nous obtenons l’élément à l’aide de la fonction ` TreeItem::child() `. La fonction ` createIndex()` est utilisée pour créer un index de modèle à renvoyer. Nous spécifions les numéros de ligne et de colonne, ainsi qu’un pointeur vers l’élément lui-même. L’index de modèle peut être utilisé ultérieurement pour obtenir les données de l’élément.
La manière dont les objets ` TreeItem ` sont définis facilite l'écriture de la fonction ` parent() ` :
QModelIndex TreeModel::parent(const QModelIndex &index) const
{
if (!index.isValid())
return {};
auto *childItem = static_cast<TreeItem*>(index.internalPointer());
TreeItem *parentItem = childItem->parentItem();
return parentItem != rootItem.get()
? createIndex(parentItem->row(), 0, parentItem) : QModelIndex{};
}Il suffit simplement de veiller à ne jamais renvoyer d’index de modèle correspondant à l’élément racine. Par souci de cohérence avec la manière dont la fonction ` index() ` est implémentée, nous renvoyons un index de modèle invalide pour le parent de tout élément de niveau supérieur du modèle.
Lors de la création d’un index de modèle à renvoyer, nous devons spécifier les numéros de ligne et de colonne de l’élément parent au sein de son propre parent. Nous pouvons facilement déterminer le numéro de ligne à l’aide de la fonction ` TreeItem::row() `, mais nous suivons une convention consistant à spécifier 0 comme numéro de colonne du parent. L’index de modèle est créé avec ` createIndex()` de la même manière que dans la fonction ` index() `.
La fonction ` rowCount() ` renvoie simplement le nombre d’éléments enfants pour l’ TreeItem correspondant à un index de modèle donné, ou le nombre d’éléments de niveau supérieur si un index non valide est spécifié :
int TreeModel::rowCount(const QModelIndex &parent) const
{
if (parent.column() > 0)
return 0;
const TreeItem *parentItem = parent.isValid()
? static_cast<const TreeItem*>(parent.internalPointer())
: rootItem.get();
return parentItem->childCount();
}Étant donné que chaque élément gère ses propres données de colonnes, la fonction ` columnCount() ` doit appeler la fonction ` columnCount() ` de l’élément concerné afin de déterminer le nombre de colonnes présentes pour un indice de modèle donné. Comme pour la fonction ` rowCount() `, si un indice de modèle non valide est spécifié, le nombre de colonnes renvoyé est déterminé à partir de l’élément racine :
int TreeModel::columnCount(const QModelIndex &parent) const
{
if (parent.isValid())
return static_cast<TreeItem*>(parent.internalPointer())->columnCount();
return rootItem->columnCount();
}Les données sont obtenues à partir du modèle via ` data()`. Étant donné que l’élément gère ses propres colonnes, nous devons utiliser le numéro de colonne pour récupérer les données à l’aide de la fonction ` TreeItem::data() ` :
QVariant TreeModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || role != Qt::DisplayRole)
return {};
const auto *item = static_cast<const TreeItem*>(index.internalPointer());
return item->data(index.column());
}Notez que nous ne prenons en charge que la méthode ` DisplayRole ` dans cette implémentation, et que nous renvoyons également des objets ` QVariant ` non valides pour les index de modèle non valides.
Nous utilisons la fonction ` flags() ` pour garantir que les vues sachent que le modèle est en lecture seule :
Qt::ItemFlags TreeModel::flags(const QModelIndex &index) const
{
return index.isValid()
? QAbstractItemModel::flags(index) : Qt::ItemFlags(Qt::NoItemFlags);
}La fonction ` headerData() ` renvoie les données que nous avons judicieusement stockées dans l'élément racine :
QVariant TreeModel::headerData(int section, Qt::Orientation orientation,
int role) const
{
return orientation == Qt::Horizontal && role == Qt::DisplayRole
? rootItem->data(section) : QVariant{};
}Ces informations auraient pu être fournies d’une autre manière : soit en les spécifiant dans le constructeur, soit en les codant en dur dans la fonction ` headerData() `.
Configuration des données dans le modèle
Nous utilisons la fonction ` setupModelData() ` pour configurer les données initiales dans le modèle. Cette fonction analyse un fichier texte, en extrait des chaînes de caractères à utiliser dans le modèle, et crée des objets « item » qui enregistrent à la fois les données et la structure globale du modèle. Naturellement, cette fonction fonctionne d’une manière très spécifique à ce modèle. Nous fournissons ci-dessous une description de son comportement et renvoyons le lecteur au code d’exemple lui-même pour plus d’informations.
Nous partons d’un fichier texte au format suivant :
Getting Started How to familiarize yourself with Qt Widgets Designer
Launching Designer Running the Qt Widgets Designer application
The User Interface How to interact with Qt Widgets Designer
...Connection Editing Mode Connecting widgets together with signals and slots
Connecting Objects Making connections in Qt Widgets Designer
Editing Connections Changing existing connectionsNous traitons le fichier texte selon les deux règles suivantes :
- Pour chaque paire de chaînes de caractères sur chaque ligne, créer un élément (ou nœud) dans une structure arborescente, et placer chaque chaîne dans une colonne de données de cet élément.
- Lorsque la première chaîne d'une ligne est en retrait par rapport à la première chaîne de la ligne précédente, faire de l'élément un enfant de l'élément créé précédemment.
Pour garantir le bon fonctionnement du modèle, il suffit de créer des instances de ` TreeItem ` avec les données et l'élément parent corrects.
Test du modèle
La mise en œuvre correcte d’un modèle d’élément peut s’avérer complexe. La classe QAbstractItemModelTester du Qt Test module vérifie la cohérence du modèle, notamment la création de l’index du modèle et les relations parent-enfant.
Vous pouvez tester votre modèle en transmettant simplement une instance de modèle au constructeur de la classe, par exemple dans le cadre d’un test unitaire Qt Test :
class TestSimpleTreeModel : public QObject
{
Q_OBJECT
private slots:
void testTreeModel();
};
void TestSimpleTreeModel::testTreeModel()
{
constexpr auto fileName = ":/default.txt"_L1;
QFile file(fileName);
QVERIFY2(file.open(QIODevice::ReadOnly | QIODevice::Text),
qPrintable(fileName + " cannot be opened: "_L1 + file.errorString()));
TreeModel modèle(QString::fromUtf8(file.readAll()));
QAbstractItemModelTester tester(&model);
}
QTEST_APPLESS_MAIN(TestSimpleTreeModel)
#include "test.moc"Pour créer un test pouvant être exécuté à l'aide de l'exécutable ctest, on utilise ` add_test() ` :
# Unit Test
include(CTest)
qt_add_executable(simpletreemodel_tester
test.cpp
treeitem.cpp treeitem.h
treemodel.cpp treemodel.h)
target_link_libraries(simpletreemodel_tester PRIVATE
Qt6::Core
Qt6::Test
)
if(ANDROID)
target_link_libraries(simpletreemodel_tester PRIVATE
Qt6::Gui
)
endif()
qt_add_resources(simpletreemodel_tester "simpletreemodel_tester"
PREFIX
"/"
FILES
${simpletreemodel_resource_files}
)
add_test(NAME simpletreemodel_tester
COMMAND simpletreemodel_tester)© 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.