Ejemplo de modelo de árbol simple
El ejemplo Simple Tree Model muestra cómo utilizar un modelo jerárquico con las clases de vista estándar de Qt.

La arquitectura modelo/vista de Qt proporciona una forma estándar para que las vistas manipulen la información en una fuente de datos, utilizando un modelo abstracto de los datos para simplificar y estandarizar la forma en que se accede a ellos. Los modelos simples representan los datos como una tabla de elementos, y permiten a las vistas acceder a estos datos mediante un sistema basado en índices. En términos más generales, los modelos pueden utilizarse para representar datos en forma de estructura de árbol, permitiendo que cada elemento actúe como padre de una tabla de elementos hijos.
Antes de intentar implementar un modelo de árbol, conviene considerar si los datos proceden de una fuente externa o si se van a mantener en el propio modelo. En este ejemplo, implementaremos una estructura interna para mantener los datos en lugar de discutir cómo empaquetar los datos de una fuente externa.
Diseño y conceptos
La estructura de datos que utilizamos para representar la estructura de los datos adopta la forma de un árbol construido a partir de objetos TreeItem. Cada TreeItem representa un elemento en una vista de árbol y contiene varias columnas de datos.
| Estructura simple del modelo de árbol Los datos se almacenan internamente en el modelo mediante objetos Cada Dado que cada elemento de una vista en árbol suele contener varias columnas de datos (un título y un resumen en este ejemplo), es natural almacenar esta información en cada elemento. Para simplificar, utilizaremos una lista de objetos QVariant para almacenar los datos de cada columna del elemento. |
El uso de una estructura de árbol basada en punteros significa que, al pasar un índice del modelo a una vista, podemos registrar la dirección del elemento correspondiente en el índice (véase QAbstractItemModel::createIndex()) y recuperarlo más tarde con QModelIndex::internalPointer(). Esto facilita la escritura del modelo y garantiza que todos los índices del modelo que hagan referencia al mismo elemento tengan el mismo puntero de datos interno.
Con la estructura de datos adecuada, podemos crear un modelo de árbol con una cantidad mínima de código adicional para suministrar índices y datos del modelo a otros componentes.
Definición de la clase TreeItem
La clase TreeItem se define como sigue:
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; };
La clase es una clase C++ básica. No hereda de QObject ni proporciona señales ni ranuras. Se utiliza para contener una lista de QVariants, que contiene datos de columna e información sobre su posición en la estructura de árbol. Las funciones proporcionan las siguientes características:
- La función
appendChildItem()se utiliza para añadir datos cuando se construye el modelo por primera vez y no se utiliza durante el uso normal. - Las funciones
child()ychildCount()permiten al modelo obtener información sobre cualquier elemento hijo. - La información sobre el número de columnas asociadas al elemento se proporciona mediante
columnCount(), y los datos de cada columna pueden obtenerse con la función data(). - Las funciones
row()yparent()se utilizan para obtener el número de fila del elemento y el elemento padre.
Los datos del elemento padre y de la columna se almacenan en las variables privadas parentItem y itemData. La variable childItems contiene una lista de punteros a los elementos hijos del propio elemento.
Implementación de la clase TreeItem
El constructor sólo se utiliza para registrar el elemento padre y los datos asociados a cada columna.
TreeItem::TreeItem(QVariantList data, TreeItem *parent) : m_itemData(std::move(data)), m_parentItem(parent) {}
Un puntero a cada uno de los elementos hijo pertenecientes a este elemento se almacenará en la variable miembro privada childItems como un std::unique_ptr. Cuando se llame al destructor de la clase, los elementos hijos se borrarán automáticamente para garantizar que se reutiliza su memoria:
Dado que cada uno de los elementos hijo se construye cuando el modelo se rellena inicialmente con datos, la función para añadir elementos hijo es sencilla:
void TreeItem::appendChild(std::unique_ptr<TreeItem> &&child) { m_childItems.push_back(std::move(child)); }
Cada elemento es capaz de devolver cualquiera de sus elementos hijos cuando se le da un número de fila adecuado. Por ejemplo, en el diagrama anterior, el elemento marcado con la letra "A" corresponde al hijo del elemento raíz con row = 0, el elemento "B" es hijo del elemento "A" con row = 1, y el elemento "C" es hijo del elemento raíz con row = 1.
La función child() devuelve el elemento hijo que corresponde al número de fila especificado en la lista de elementos hijos del elemento:
TreeItem *TreeItem::child(int row) { return row >= 0 && row < childCount() ? m_childItems.at(row).get() : nullptr; }
El número de ítems hijos que contiene puede consultarse con childCount():
int TreeItem::childCount() const { return int(m_childItems.size()); }
TreeModel utiliza esta función para determinar el número de filas que existen para un determinado artículo padre.
La función row() informa de la ubicación del ítem dentro de la lista de ítems de su padre:
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; }
Tenga en cuenta que, aunque al elemento raíz (sin elemento padre) se le asigna automáticamente un número de fila de 0, esta información nunca es utilizada por el modelo.
El número de columnas de datos en el elemento es devuelto trivialmente por la función columnCount().
int TreeItem::columnCount() const { return int(m_itemData.count()); }
Los datos de las columnas son devueltos por la función data(). Utilizamos la función de conveniencia QList::value() que comprueba los límites y devuelve un QVariant construido por defecto en caso de que se violen:
QVariant TreeItem::data(int column) const { return m_itemData.value(column); }
El elemento padre se encuentra con parent():
TreeItem *TreeItem::parentItem() { return m_parentItem; }
Ten en cuenta que, como el elemento raíz del modelo no tendrá padre, esta función devolverá cero en ese caso. Tenemos que asegurarnos de que el modelo maneja este caso correctamente cuando implementemos la función TreeModel::parent().
Definición de la clase TreeModel
La clase TreeModel se define como sigue:
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; };
Esta clase es similar a la mayoría de las otras subclases de QAbstractItemModel que proporcionan modelos de sólo lectura. Sólo la forma del constructor y la función setupModelData() son específicas de este modelo. Además, proporcionamos un destructor para limpiar cuando se destruye el modelo.
Implementación de la clase TreeModel
Por simplicidad, el modelo no permite que sus datos sean editados. Como resultado, el constructor toma un argumento que contiene los datos que el modelo compartirá con las vistas y los delegados:
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()); }
Depende del constructor crear un elemento raíz para el modelo. Este elemento sólo contiene datos de cabecera verticales por conveniencia. También lo utilizamos para hacer referencia a la estructura de datos interna que contiene los datos del modelo, y se utiliza para representar un padre imaginario de los elementos de nivel superior en el modelo. El elemento raíz se gestiona con un std::unique_ptr para garantizar que todo el árbol de elementos se elimina cuando se borra el modelo.
La estructura de datos interna del modelo se rellena con elementos mediante la función setupModelData(). Examinaremos esta función por separado al final de este documento.
El destructor se asegura de que el elemento raíz y todos sus descendientes se borran cuando se destruye el modelo. Esto se hace automáticamente ya que el elemento raíz se almacena en un unique_ptr.
TreeModel::~TreeModel() = default;
Dado que no podemos añadir datos al modelo una vez construido y configurado, esto simplifica la forma en que se gestiona el árbol interno de elementos.
Los modelos deben implementar una función index() para proporcionar índices para que las vistas y los delegados los utilicen al acceder a los datos. Los índices se crean para otros componentes cuando son referenciados por sus números de fila y columna, y su índice de modelo padre. Si se especifica un índice de modelo no válido como padre, es el modelo el que debe devolver un índice que corresponda a un elemento de nivel superior en el modelo.
Cuando se proporciona un índice de modelo, primero se comprueba si es válido. Si no lo es, asumimos que se está haciendo referencia a un elemento de nivel superior; en caso contrario, obtenemos el puntero de datos del índice del modelo con su función internalPointer() y lo utilizamos para hacer referencia a un objeto TreeItem. Tenga en cuenta que todos los índices de modelo que construyamos contendrán un puntero a un TreeItem existente, por lo que podemos garantizar que cualquier índice de modelo válido que recibamos contendrá un puntero de datos válido.
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 {}; }
Dado que los argumentos de fila y columna de esta función se refieren a un elemento hijo del elemento padre correspondiente, obtenemos el elemento utilizando la función TreeItem::child(). La función createIndex() se utiliza para crear un índice modelo que se devolverá. Especificamos los números de fila y columna, y un puntero al propio artículo. El índice del modelo puede utilizarse posteriormente para obtener los datos del elemento.
La forma en que se definen los objetos TreeItem facilita la escritura de la función 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{}; }
Sólo tenemos que asegurarnos de que nunca devolvemos un índice de modelo correspondiente al elemento raíz. Para ser coherentes con la forma en que se implementa la función index(), devolvemos un índice de modelo no válido para el padre de cualquier elemento de nivel superior en el modelo.
Al crear un índice de modelo para devolver, debemos especificar los números de fila y columna del elemento padre dentro de su propio padre. Podemos descubrir fácilmente el número de fila con la función TreeItem::row(), pero seguimos la convención de especificar 0 como número de columna del elemento padre. El índice del modelo se crea con createIndex() del mismo modo que en la función index().
La función rowCount() simplemente devuelve el número de elementos hijo para el TreeItem que corresponde a un índice modelo dado, o el número de elementos de nivel superior si se especifica un índice no válido:
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(); }
Dado que cada elemento gestiona sus propios datos de columna, la función columnCount() tiene que llamar a la función columnCount() del propio elemento para determinar cuántas columnas están presentes para un índice de modelo dado. Al igual que con la función rowCount(), si se especifica un índice de modelo no válido, el número de columnas devueltas se determina a partir del elemento raíz:
int TreeModel::columnCount(const QModelIndex &parent) const { if (parent.isValid()) return static_cast<TreeItem*>(parent.internalPointer())->columnCount(); return rootItem->columnCount(); }
Los datos se obtienen del modelo a través de data(). Dado que el elemento gestiona sus propias columnas, necesitamos utilizar el número de columna para recuperar los datos con la función 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()); }
Ten en cuenta que en esta implementación sólo admitimos DisplayRole, y que también devolvemos objetos QVariant no válidos para índices de modelo no válidos.
Utilizamos la función flags() para asegurarnos de que las vistas saben que el modelo es de sólo lectura:
Qt::ItemFlags TreeModel::flags(const QModelIndex &index) const { return index.isValid() ? QAbstractItemModel::flags(index) : Qt::ItemFlags(Qt::NoItemFlags); }
La función headerData() devuelve los datos que hemos almacenado convenientemente en el elemento raíz:
QVariant TreeModel::headerData(int section, Qt::Orientation orientation, int role) const { return orientation == Qt::Horizontal && role == Qt::DisplayRole ? rootItem->data(section) : QVariant{}; }
Esta información podría haberse suministrado de otra forma: especificada en el constructor o codificada en la función headerData().
Configuración de los datos en el modelo
Utilizamos la función setupModelData() para configurar los datos iniciales del modelo. Esta función analiza un archivo de texto, extrayendo cadenas de texto para utilizarlas en el modelo, y crea objetos item que registran tanto los datos como la estructura general del modelo. Naturalmente, esta función trabaja de una manera muy específica para este modelo. Proporcionamos la siguiente descripción de su comportamiento, y remitimos al lector al propio código de ejemplo para más información.
Comenzamos con un fichero de texto con el siguiente formato:
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 connections
Procesamos el fichero de texto con las dos reglas siguientes:
- Para cada par de cadenas de cada línea, creamos un elemento (o nodo) en una estructura de árbol y colocamos cada cadena en una columna de datos del elemento.
- Cuando la primera cadena de una línea tiene sangría con respecto a la primera cadena de la línea anterior, el elemento se convierte en hijo del elemento anterior creado.
Para que el modelo funcione correctamente, sólo es necesario crear instancias de TreeItem con los datos y el elemento padre correctos.
Probar el modelo
Implementar correctamente un modelo de artículos puede ser todo un reto. La clase QAbstractItemModelTester del módulo Qt Test comprueba la coherencia del modelo, como la creación del índice del modelo y las relaciones padre-hijo.
Puedes probar tu modelo simplemente pasando una instancia del modelo al constructor de la clase, por ejemplo como parte de una prueba unitaria de Qt:
clase TestSimpleTreeModel : public QObject { Q_OBJECTprivate slots: void testTreeModel(); };void TestSimpleTreeModel::testTreeModel() { constexpr auto fileName = ":/default.txt"_L1; QFile file(fileName); QVERIFY2(file.open(QIODevice::SóloLectura | QIODevice::Texto), qPrintable(fileName + " cannot be opened: "_L1 + file.errorString())); TreeModel modelo(QString::fromUtf8(file.readAll())); QAbstractItemModelTester tester(&model); } QTEST_APPLESS_MAIN(TestSimpleTreeModel)#include "test.moc"
Para crear una prueba que se pueda ejecutar con el ejecutable ctest, se utiliza 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.