Tutorial Modelo/Vista
Todo desarrollador de interfaz de usuario debería conocer la programación ModelView y el objetivo de este tutorial es proporcionarle una introducción fácilmente comprensible a este tema.
Los widgets de tabla, lista y árbol son componentes frecuentemente utilizados en GUIs. Hay 2 formas diferentes en que estos widgets pueden acceder a sus datos. La forma tradicional implica widgets que incluyen contenedores internos para almacenar datos. Este enfoque es muy intuitivo, sin embargo, en muchas aplicaciones no triviales, conduce a problemas de sincronización de datos. El segundo enfoque es la programación modelo/vista, en la que los widgets no mantienen contenedores de datos internos. Acceden a datos externos a través de una interfaz estandarizada y evitan así la duplicación de datos. Esto puede parecer complicado al principio, pero una vez que se examina más de cerca, no sólo es fácil de entender, sino que también quedan más claras las muchas ventajas de la programación modelo/vista.

En el proceso, vamos a aprender acerca de algunas tecnologías básicas proporcionadas por Qt, tales como:
- La diferencia entre widgets estándar y de modelo/vista
- Adaptadores entre formularios y modelos
- Desarrollo de una aplicación modelo/vista sencilla
- Modelos predefinidos
- Temas intermedios como:
- Vistas en árbol
- Selección
- Delegados
- Depuración con model test
También aprenderá si su nueva aplicación puede escribirse más fácilmente con programación modelo/vista o si los widgets clásicos funcionarán igual de bien.
Este tutorial incluye código de ejemplo para que lo edites e integres en tu proyecto. El código fuente del tutorial se encuentra en el directorio examples/widgets/tutorials/modelview de Qt.
Para obtener información más detallada también puedes consultar la documentación de referencia
1. Introducción
Model/View es una tecnología utilizada para separar datos de vistas en widgets que manejan conjuntos de datos. Los widgets estándar no están diseñados para separar los datos de las vistas y esta es la razón por la que Qt tiene dos tipos diferentes de widgets. Ambos tipos de widgets tienen el mismo aspecto, pero interactúan con los datos de forma diferente.
| Los widgets estándar utilizan datos que forman parte del widget. |
|
| Las clases de vista operan con datos externos (el modelo) |
|
1.1 Widgets estándar
Echemos un vistazo más de cerca a un widget de tabla estándar. Un widget de tabla es una matriz 2D de los elementos de datos que el usuario puede cambiar. El widget de tabla puede integrarse en el flujo de un programa leyendo y escribiendo los elementos de datos que proporciona el widget de tabla. Este método es muy intuitivo y útil en muchas aplicaciones, pero mostrar y editar una tabla de base de datos con un widget de tabla estándar puede ser problemático. Hay que coordinar dos copias de los datos: una fuera del widget y otra dentro del widget. El desarrollador es responsable de sincronizar ambas versiones. Además, el estrecho acoplamiento entre presentación y datos dificulta la escritura de pruebas unitarias.
1.2 Modelo/Vista al rescate
Model/view apareció para ofrecer una solución que utiliza una arquitectura más versátil. Model/view elimina los problemas de coherencia de datos que pueden producirse con los widgets estándar. Model/view también facilita el uso de más de una vista de los mismos datos, ya que un modelo puede pasarse a muchas vistas. La diferencia más importante es que los widgets modelo/vista no almacenan datos detrás de las celdas de la tabla. De hecho, operan directamente a partir de tus datos. Dado que las clases de vista no conocen la estructura de tus datos, necesitas proporcionar una envoltura para hacer que tus datos se ajusten a la interfaz QAbstractItemModel. Una vista utiliza esta interfaz para leer y escribir en tus datos. Cualquier instancia de una clase que implemente QAbstractItemModel se dice que es un modelo. Una vez que la vista recibe un puntero a un modelo, leerá y mostrará su contenido y será su editor.
1.3 Visión general de los widgets modelo/vista
A continuación se presenta una visión general de los widgets modelo/vista y sus correspondientes widgets estándar.
| Widget | Widget estándar (una clase práctica basada en elementos) | Clase modelo/vista (para uso con datos externos) |
|---|---|---|
![]() | QListWidget | QListView |
![]() | QTableWidget | QTableView |
![]() | QTreeWidget | QTreeView |
![]() | QColumnView muestra un árbol como una jerarquía de listas | |
![]() | QComboBox puede funcionar como clase de vista y también como widget tradicional | |
1.4 Uso de adaptadores entre formularios y modelos
Disponer de adaptadores entre formularios y modelos puede resultar muy útil.
Podemos editar datos almacenados en tablas directamente desde la propia tabla, pero es mucho más cómodo editar datos en campos de texto. No existe una contrapartida directa modelo/vista que separe datos y vistas para los widgets que operan sobre un valor (QLineEdit, QCheckBox...) en lugar de sobre un conjunto de datos, por lo que necesitamos un adaptador para conectar el formulario con la fuente de datos.
QDataWidgetMapper es una gran solución porque mapea widgets de formulario a una fila de tabla y hace muy fácil construir formularios para tablas de bases de datos.

Otro ejemplo de adaptador es QCompleter. Qt dispone de QCompleter para proporcionar autocompletado en widgets Qt como QComboBox y, como se muestra a continuación, QLineEdit. QCompleter utiliza un modelo como fuente de datos.

2. Una simple aplicación modelo/vista
Si quieres desarrollar una aplicación modelo/vista, ¿por dónde deberías empezar? Recomendamos empezar con un ejemplo sencillo e ir ampliándolo paso a paso. Esto hace que la comprensión de la arquitectura sea mucho más fácil. Intentar comprender la arquitectura modelo/vista en detalle antes de invocar el IDE ha demostrado ser menos conveniente para muchos desarrolladores. Es sustancialmente más fácil empezar con una simple aplicación modelo/vista que tenga datos de demostración. ¡Pruébela! Simplemente sustituya los datos de los ejemplos siguientes por los suyos propios.
A continuación hay 7 aplicaciones muy simples e independientes que muestran diferentes lados de la programación modelo/vista. El código fuente se encuentra en el directorio examples/widgets/tutorials/modelview.
2.1 Una tabla de sólo lectura
Comenzamos con una aplicación que utiliza un QTableView para mostrar datos. Más adelante añadiremos capacidades de edición.
(archivo fuente: 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(); }
Tenemos la habitual función main():
Aquí está la parte interesante: Creamos una instancia de MiModelo y usamos tableView.setModel(&myModel); para pasar un puntero del mismo a tableView. tableView invocará los métodos del puntero que ha recibido para averiguar dos cosas:
- Cuántas filas y columnas deben mostrarse.
- Qué contenido debe imprimirse en cada celda.
El modelo necesita algo de código para responder a esto.
Tenemos un conjunto de datos de tabla, así que vamos a empezar con QAbstractTableModel ya que es más fácil de usar que el más general QAbstractItemModel.
(archivo fuente: 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 requiere la implementación de tres métodos abstractos.
(archivo fuente: 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(); }
El número de filas y columnas es proporcionado por MyModel::rowCount() y MyModel::columnCount(). Cuando la vista tiene que saber cuál es el texto de la celda, llama al método MyModel::data(). La información de filas y columnas se especifica con el parámetro index y el rol se establece en Qt::DisplayRole. Otros roles se tratan en la siguiente sección. En nuestro ejemplo, se generan los datos que deben mostrarse. En una aplicación real, MyModel tendría un miembro llamado MyData, que sirve de destino para todas las operaciones de lectura y escritura.
Este pequeño ejemplo demuestra la naturaleza pasiva de un modelo. El modelo no sabe cuándo se va a utilizar ni qué datos se necesitan. Simplemente proporciona datos cada vez que la vista los solicita.
¿Qué ocurre cuando es necesario modificar los datos del modelo? ¿Cómo se da cuenta la vista de que los datos han cambiado y necesitan ser leídos de nuevo? El modelo tiene que emitir una señal que indique qué rango de celdas ha cambiado. Esto se demostrará en la sección 2.3.
2.2 Ampliación del ejemplo de sólo lectura con roles
Además de controlar qué texto muestra la vista, el modelo también controla la apariencia del texto. Cuando cambiamos ligeramente el modelo, obtenemos el siguiente resultado:

De hecho, no hay que cambiar nada excepto el método data() para establecer las fuentes, el color de fondo, la alineación y una casilla de verificación. A continuación se muestra el método data() que produce el resultado mostrado anteriormente. La diferencia es que esta vez utilizamos el parámetro int role para devolver información diferente en función de su valor.
(fuente del fichero: examples/widgets/tutorials/modelview/2_formatting/mymodel.cpp)
// mymodel.cppQVariant MyModel::data(const QModelIndex &index, int role) const{ int row = index.row(); int col = index.column(); // genera un mensaje de registro cuando se llama a este método 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("<--izquierda"); if (fila == 1 && col == 1) return QString("derecha-->"); return QString("Fila%1, Columna%2") .arg(fila + 1) .arg(col+1); case Qt::FontRole: if (row == 0 && col == 0) { // cambiar fuente sólo para celda(0,0) QFont boldFont; boldFont.setBold(true); return boldFont; } break; case Qt::BackgroundRole: if (row == 1 && col == 2) // cambiar fondo sólo para celda(1,2) return QBrush(Qt::red); break; case Qt::TextAlignmentRole: if (row == 1 && col == 1) // cambiar la alineación del texto sólo para la celda(1,1) return int(Qt::AlinearDerecha | Qt::AlignVCenter); break; case Qt::CheckStateRole: if (row == 1 && col == 0) // añade una casilla de verificación a cell(1,0) return Qt::Checked; break; } return QVariant(); }
Cada propiedad de formato se solicitará al modelo con una llamada independiente al método data(). El parámetro role se utiliza para que el modelo sepa qué propiedad se está solicitando:
| enum Qt::ItemDataRole | Significado | Tipo |
|---|---|---|
| Qt::DisplayRole | texto | QString |
| Qt::FontRole | fuente | QFont |
| BackgroundRole | pincel para el fondo de la celda | QBrush |
| Qt::TextAlignmentRole | alineación del texto | enum Qt::AlignmentFlag |
| Qt::CheckStateRole | suprime las casillas de verificación con QVariant(), activa las casillas de verificación con Qt::Checked | enum Qt::ItemDataRole |
Consulte la documentación del espacio de nombres Qt para obtener más información sobre las capacidades del enum Qt::ItemDataRole.
Ahora necesitamos determinar cómo impacta el uso de un modelo separado en el rendimiento de la aplicación, así que vamos a rastrear con qué frecuencia la vista llama al método data(). Para rastrear la frecuencia con la que la vista llama al modelo, hemos puesto una sentencia de depuración en el método data(), que se registra en el flujo de salida de errores. En nuestro pequeño ejemplo, data() será llamado 42 veces. Cada vez que pase el cursor sobre el campo, data() será llamado de nuevo - 7 veces por cada celda. Por eso es importante asegurarse de que sus datos estén disponibles cuando se invoque data() y se almacenen en caché las costosas operaciones de búsqueda.
2.3 Un reloj dentro de una celda de tabla

Seguimos teniendo una tabla de sólo lectura, pero esta vez el contenido cambia cada segundo porque estamos mostrando la hora actual.
(fuente del fichero: 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(); }
Falta algo para que el reloj marque la hora. Necesitamos decirle a la vista cada segundo que la hora ha cambiado y que necesita ser leída de nuevo. Hacemos esto con un temporizador. En el constructor, establecemos su intervalo a 1 segundo y conectamos su señal de tiempo de espera.
(archivo fuente: 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(); }
Aquí está el slot correspondiente:
(archivo fuente: 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}); }
Pedimos a la vista que vuelva a leer los datos de la celda superior izquierda emitiendo la señal dataChanged(). Observa que no conectamos explícitamente la señal dataChanged() a la vista. Esto ocurrió automáticamente cuando llamamos a setModel().
2.4 Configurar encabezados para columnas y filas
Las cabeceras pueden ocultarse mediante un método de la vista: tableView->verticalHeader()->hide();

El contenido de la cabecera, sin embargo, se establece a través del modelo, por lo que reimplementamos el método headerData():
(archivo fuente: 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(); }
Observa que el método headerData() también tiene un parámetro role que tiene el mismo significado que en MyModel::data().
2.5 Ejemplo de edición mínima
En este ejemplo vamos a construir una aplicación que rellene automáticamente el título de una ventana con contenido repitiendo los valores introducidos en las celdas de la tabla. Para poder acceder fácilmente al título de la ventana ponemos el QTableView en un QMainWindow.
El modelo decide si las capacidades de edición están disponibles. Sólo tenemos que modificar el modelo para que se habiliten las capacidades de edición disponibles. Esto se hace reimplementando los siguientes métodos virtuales: setData() y flags().
(archivo fuente: 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 &); };
Utilizamos the array bidimensional QString m_gridData para almacenar nuestros datos. Esto hace que m_gridData sea el núcleo de MyModel. El resto de MyModel actúa como una envoltura y adapta m_gridData a la interfaz QAbstractItemModel. También hemos introducido la señal editCompleted(), que permite transferir el texto modificado al título de la ventana.
(archivo fuente: 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() será llamada cada vez que el usuario edite una celda. El parámetro index nos indica qué campo ha sido editado y value nos proporciona el resultado del proceso de edición. La función siempre será Qt::EditRole porque nuestras celdas sólo contienen texto. Si existiera una casilla de verificación y los permisos del usuario estuvieran configurados para permitir la selección de la casilla, las llamadas también se realizarían con el rol configurado a Qt::CheckStateRole.
(fuente del fichero: examples/widgets/tutorials/modelview/5_edit/mymodel.cpp)
Qt::ItemFlags MyModel::flags(const QModelIndex &index) const { return Qt::ItemIsEditable | QAbstractTableModel::flags(index); }
Se pueden ajustar varias propiedades de una celda con flags().
Devolver Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled es suficiente para mostrar a un editor que una celda puede ser seleccionada.
Si al editar una celda se modifican más datos que los de esa celda en particular, el modelo debe emitir una señal dataChanged() para que se lean los datos que se han modificado.
3. Temas intermedios
3.1 Vista de árbol
Puede convertir el ejemplo anterior en una aplicación con una vista de árbol. Simplemente sustituya QTableView por QTreeView, lo que resulta en un árbol de lectura/escritura. No hay que hacer ningún cambio en el modelo. El árbol no tendrá jerarquías porque no hay jerarquías en el propio modelo.

QListView QTableView y utilizan una abstracción de modelo, que es una combinación de lista, tabla y árbol. Esto hace posible utilizar varios tipos diferentes de clases de vista a partir del mismo modelo. QTreeView

Así es como nuestro modelo de ejemplo se ve hasta ahora:

Queremos presentar un árbol real. Hemos envuelto nuestros datos en los ejemplos anteriores para hacer un modelo. Esta vez utilizamos QStandardItemModel, que es un contenedor para datos jerárquicos que también implementa QAbstractItemModel. Para mostrar un árbol, QStandardItemModel debe poblarse con QStandardItems, que son capaces de contener todas las propiedades estándar de elementos como texto, fuentes, casillas de verificación o pinceles.

(archivo fuente: 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)}; }
Simplemente instanciamos un QStandardItemModel y añadimos un par de QStandardItems al constructor. Podemos entonces hacer una estructura de datos jerárquica porque un QStandardItem puede contener otros QStandardItems. Los nodos se contraen y expanden dentro de la vista.
3.2 Trabajar con selecciones
Queremos acceder al contenido de un elemento seleccionado para mostrarlo en el título de la ventana junto con el nivel jerárquico.

Así que vamos a crear un par de elementos:
(archivo fuente: 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); }
Las vistas gestionan las selecciones dentro de un modelo de selección independiente, que se puede recuperar con el método selectionModel(). Recuperamos el modelo de selección para conectar una ranura a su señal selectionChanged().
(fuente del fichero: 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); }
Obtenemos el índice del modelo que corresponde a la selección llamando a treeView->selectionModel()->currentIndex() y obtenemos la cadena del campo utilizando el índice del modelo. A continuación calculamos el hierarchyLevel del elemento. Los elementos de nivel superior no tienen padres y el método parent() devolverá un QModelIndex() construido por defecto. Por eso utilizamos el método parent() para iterar hasta el nivel superior mientras contamos los pasos realizados durante la iteración.
El modelo de selección (como se muestra arriba) se puede recuperar, pero también se puede establecer con QAbstractItemView::setSelectionModel. Así es como es posible tener 3 clases de vistas con selecciones sincronizadas porque sólo se utiliza una instancia de un modelo de selección. Para compartir un modelo de selección entre 3 vistas utilice selectionModel() y asigne el resultado a la segunda y tercera clase de vista con setSelectionModel().
3.3 Modelos predefinidos
La forma típica de utilizar model/view es envolver datos específicos para hacerlos utilizables con clases view. Qt, sin embargo, también proporciona modelos predefinidos para estructuras de datos subyacentes comunes. Si una de las estructuras de datos disponibles es adecuada para tu aplicación, un modelo predefinido puede ser una buena elección.
| QStringListModel | Almacena una lista de cadenas |
| QStandardItemModel | Almacena elementos jerárquicos arbitrarios |
| QFileSystemModel | Encapsula el sistema de archivos local |
| QSqlQueryModel | Encapsula un conjunto de resultados SQL |
| QSqlTableModel | Encapsula una tabla SQL |
| QSqlRelationalTableModel | Encapsula una tabla SQL con claves externas |
| QSortFilterProxyModel | Ordena y/o filtra otro modelo |
3.4 Delegados
En todos los ejemplos anteriores, los datos se presentan como texto o casilla de verificación en una celda y se editan como texto o casilla de verificación. El componente que proporciona estos servicios de presentación y edición se denomina delegado. Apenas estamos empezando a trabajar con el delegado porque la vista utiliza un delegado por defecto. Pero imaginemos que queremos tener un editor diferente (por ejemplo, un deslizador o una lista desplegable) O imaginemos que queremos presentar los datos como gráficos. Veamos un ejemplo llamado Star Delegate, en el que se utilizan estrellas para mostrar una calificación:

La vista tiene un método setItemDelegate() que sustituye al delegado por defecto e instala un delegado personalizado. Se puede escribir un nuevo delegado creando una clase que herede de QStyledItemDelegate. Para escribir un delegado que muestre estrellas y no tenga capacidades de entrada, sólo necesitamos anular 2 métodos.
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() dibuja estrellas dependiendo del contenido de los datos subyacentes. Los datos se pueden buscar llamando a index.data(). El método sizeHint() del delegado se utiliza para obtener las dimensiones de cada estrella, por lo que la celda proporcionará suficiente altura y anchura para acomodar las estrellas.
Escribir delegados personalizados es la elección correcta si quieres mostrar tus datos con una representación gráfica personalizada dentro de la rejilla de la clase view. Si quieres salir de la rejilla, no utilizarías un delegado personalizado sino una clase de vista personalizada.
Otras referencias a delegados en la Documentación de Qt:
- Clases delegadas
- QAbstractItemDelegate Class Reference
- QSqlRelationalDelegate Class Reference
- QStyledItemDelegate Class Reference
- QItemDelegate Class Reference
3.5 Depuración con ModelTest
La naturaleza pasiva de los modelos proporciona nuevos retos a los programadores. Las inconsistencias en el modelo pueden hacer que la aplicación se bloquee. Dado que el modelo es golpeado por numerosas llamadas desde la vista, es difícil averiguar qué llamada ha bloqueado la aplicación y qué operación ha introducido el problema.
Qt Labs proporciona un software llamado ModelTest, que comprueba los modelos mientras se ejecuta la programación. Cada vez que se cambia el modelo, ModelTest escanea el modelo e informa de los errores con un assert. Esto es especialmente importante para los modelos de árbol, ya que su naturaleza jerárquica deja muchas posibilidades de inconsistencias sutiles.
A diferencia de las clases de vista, ModelTest utiliza índices fuera de rango para probar el modelo. Esto significa que su aplicación puede bloquearse con ModelTest incluso si se ejecuta perfectamente sin él. Así que también necesita manejar todos los índices que están fuera de rango cuando se utiliza ModelTest.
Ejemplos
Qt viene con múltiples ejemplos para model/view. Puedes encontrarlos en la página de Ejemplos de Item Views.
© 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.





