En esta página

Programación Modelo/Vista

Introducción a la Programación Modelo/Vista

Qt contiene un conjunto de clases de vista de elementos que utilizan una arquitectura modelo/vista para gestionar la relación entre los datos y la forma en que se presentan al usuario. La separación de funcionalidades introducida por esta arquitectura ofrece a los desarrolladores una mayor flexibilidad para personalizar la presentación de los elementos, y proporciona una interfaz de modelo estándar que permite utilizar una amplia gama de fuentes de datos con las vistas de elementos existentes. En este documento se ofrece una breve introducción al paradigma modelo/vista, se esbozan los conceptos implicados y se describe la arquitectura del sistema de vistas de artículos. Se explica cada uno de los componentes de la arquitectura y se dan ejemplos que muestran cómo utilizar las clases proporcionadas.

La arquitectura modelo/vista

Modelo-Vista-Controlador (MVC) es un patrón de diseño originario de Smalltalk que se utiliza a menudo en la construcción de interfaces de usuario. En Design Patterns, Gamma et al. escriben:

MVC consta de tres tipos de objetos. El Modelo es el objeto de la aplicación, la Vista es su presentación en pantalla y el Controlador define la forma en que la interfaz de usuario reacciona a la entrada del usuario. Antes de MVC, los diseños de interfaz de usuario tendían a agrupar estos objetos. MVC los desacopla para aumentar la flexibilidad y la reutilización.

Si se combinan los objetos vista y controlador, el resultado es la arquitectura modelo/vista. Esto sigue separando la forma en que se almacenan los datos de la forma en que se presentan al usuario, pero proporciona un marco más simple basado en los mismos principios. Esta separación permite mostrar los mismos datos en varias vistas diferentes e implementar nuevos tipos de vistas sin cambiar las estructuras de datos subyacentes. Para permitir un tratamiento flexible de las entradas del usuario, introducimos el concepto de delegado. La ventaja de tener un delegado en este marco es que permite personalizar la forma en que se presentan y editan los datos.

Diagrama de interacción entre modelo, vista y delegadoArquitectura modelo/vista

El modelo se comunica con una fuente de datos, proporcionando una interfaz para los demás componentes de la arquitectura. La naturaleza de la comunicación depende del tipo de fuente de datos y de la forma en que se implemente el modelo.

La vista obtiene los índices del modelo a partir del modelo; se trata de referencias a elementos de datos. Al proporcionar índices de modelo al modelo, la vista puede recuperar elementos de datos de la fuente de datos.

En las vistas estándar, un delegado representa los elementos de datos. Cuando se edita un elemento, el delegado se comunica con el modelo directamente utilizando los índices del modelo.

En general, las clases de modelo/vista pueden dividirse en los tres grupos descritos anteriormente: modelos, vistas y delegados. Cada uno de estos componentes está definido por clases abstractas que proporcionan interfaces comunes y, en algunos casos, implementaciones por defecto de características. Las clases abstractas están pensadas para ser subclasificadas con el fin de proporcionar el conjunto completo de funcionalidades esperadas por otros componentes; esto también permite escribir componentes especializados.

Los modelos, las vistas y los delegados se comunican entre sí mediante señales y ranuras:

  • Las señales del modelo informan a la vista de los cambios en los datos de la fuente de datos.
  • Las señales de la vista proporcionan información sobre la interacción del usuario con los elementos que se muestran.
  • Las señales del delegado se utilizan durante la edición para informar al modelo y a la vista sobre el estado del editor.

Modelos

Todos los modelos de elementos se basan en la clase QAbstractItemModel. Esta clase define una interfaz que es utilizada por las vistas y los delegados para acceder a los datos. Los datos en sí no tienen por qué almacenarse en el modelo; pueden guardarse en una estructura de datos o repositorio proporcionado por una clase independiente, un archivo, una base de datos o algún otro componente de la aplicación.

Los conceptos básicos en torno a los modelos se presentan en la sección Clases de modelos.

QAbstractItemModel proporciona una interfaz de datos lo suficientemente flexible como para manejar vistas que representan datos en forma de tablas, listas y árboles. Sin embargo, a la hora de implementar nuevos modelos para estructuras de datos de tipo lista y tabla, las clases QAbstractListModel y QAbstractTableModel son mejores puntos de partida porque proporcionan implementaciones predeterminadas adecuadas de funciones comunes. Cada una de estas clases puede subclasificarse para proporcionar modelos que admitan tipos especializados de listas y tablas.

El proceso de subclasificación de modelos se discute en la sección Creación de nuevos modelos.

Qt proporciona algunos modelos ya hechos que se pueden utilizar para manejar elementos de datos:

Si estos modelos estándar no satisfacen sus necesidades, puede subclasificar QAbstractItemModel, QAbstractListModel, o QAbstractTableModel para crear sus propios modelos personalizados.

Vistas

Se proporcionan implementaciones completas para diferentes clases de vistas: QListView muestra una lista de elementos, QTableView muestra datos de un modelo en una tabla y QTreeView muestra elementos de datos de un modelo en una lista jerárquica. Cada una de estas clases se basa en la clase base abstracta QAbstractItemView. Aunque estas clases son implementaciones listas para usar, también pueden subclasificarse para proporcionar vistas personalizadas.

Las vistas disponibles se examinan en la sección Clases de vistas.

Delegados

QAbstractItemDelegate es la clase base abstracta para los delegados en el marco modelo/vista. La implementación de delegados por defecto es proporcionada por QStyledItemDelegate, y es utilizada como delegado por defecto por las vistas estándar de Qt. Sin embargo, QStyledItemDelegate y QItemDelegate son alternativas independientes para pintar y proporcionar editores para elementos en vistas. La diferencia entre ellas es que QStyledItemDelegate utiliza el estilo actual para pintar sus elementos. Por lo tanto, recomendamos utilizar QStyledItemDelegate como clase base cuando se implementen delegados personalizados o cuando se trabaje con hojas de estilo Qt.

Los delegados se describen en la sección Clases delegadas.

Ordenación

Existen dos formas de enfocar la ordenación en la arquitectura modelo/vista; la elección de una u otra dependerá del modelo subyacente.

Si su modelo es ordenable, es decir, si reimplementa la función QAbstractItemModel::sort(), tanto QTableView como QTreeView proporcionan una API que le permite ordenar los datos de su modelo mediante programación. Además, puede habilitar la ordenación interactiva (es decir, permitir a los usuarios ordenar los datos haciendo clic en los encabezados de la vista), conectando la señal QHeaderView::sortIndicatorChanged() a la ranura QTableView::sortByColumn() o a la ranura QTreeView::sortByColumn(), respectivamente.

El enfoque alternativo, si su modelo no tiene la interfaz requerida o si desea utilizar una vista de lista para presentar sus datos, es utilizar un modelo proxy para transformar la estructura de su modelo antes de presentar los datos en la vista. Este tema se trata en detalle en la sección Modelos proxy.

Clases de conveniencia

Una serie de clases de conveniencia se derivan de las clases de vista estándar para el beneficio de las aplicaciones que se basan en la vista de elementos y las clases de tabla de Qt. No están pensadas para ser subclasificadas.

Ejemplos de estas clases son QListWidget, QTreeWidget, y QTableWidget.

Estas clases son menos flexibles que las clases de vista y no pueden utilizarse con modelos arbitrarios. Le recomendamos que utilice un enfoque modelo/vista para gestionar los datos en las vistas de elementos, a menos que necesite un conjunto de clases basado en elementos.

Si desea aprovechar las características que ofrece el enfoque modelo/vista sin dejar de utilizar una interfaz basada en ítems, considere la posibilidad de utilizar clases de vista, como QListView, QTableView, y QTreeView con QStandardItemModel.

Uso de modelos y vistas

Las siguientes secciones explican cómo utilizar el patrón modelo/vista en Qt. Cada sección incluye un ejemplo y va seguida de una sección que muestra cómo crear nuevos componentes.

Dos modelos incluidos en Qt

Dos de los modelos estándar proporcionados por Qt son QStandardItemModel y QFileSystemModel. QStandardItemModel es un modelo polivalente que puede utilizarse para representar varias estructuras de datos diferentes necesarias para las vistas de lista, tabla y árbol. Este modelo también mantiene los elementos de datos. QFileSystemModel es un modelo que mantiene información sobre el contenido de un directorio. Por lo tanto, no contiene ningún elemento de datos, sino que simplemente representa archivos y directorios en el sistema de archivos local.

QFileSystemModel proporciona un modelo listo para experimentar con él, y puede configurarse fácilmente para utilizar datos existentes. Utilizando este modelo, podemos mostrar cómo configurar un modelo para utilizarlo con vistas ya creadas, y explorar cómo manipular datos utilizando índices de modelo.

Uso de vistas con un modelo existente

Las clases QListView y QTreeView son las vistas más adecuadas para utilizar con QFileSystemModel. El ejemplo que se presenta a continuación muestra el contenido de un directorio en una vista de árbol junto a la misma información en una vista de lista. Las vistas comparten la selección del usuario, de modo que los elementos seleccionados aparecen resaltados en ambas vistas.

Vista de árbol y vista de lista para mostrar el mismo modelo de sistema de archivos

Configuramos un QFileSystemModel para que esté listo para su uso, y creamos algunas vistas para mostrar el contenido de un directorio. Esto muestra la forma más sencilla de utilizar un modelo. La construcción y el uso del modelo se realizan desde una única función de main():

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    QSplitter *splitter = new QSplitter;

    QFileSystemModel *model = new QFileSystemModel;
    model->setRootPath(QDir::currentPath());

El modelo se configura para utilizar datos de un determinado sistema de ficheros. La llamada a setRootPath() indica al modelo qué unidad del sistema de ficheros debe exponer a las vistas.

Creamos dos vistas para poder examinar los elementos contenidos en el modelo de dos maneras diferentes:

QTreeView *tree = new QTreeView(splitter);
tree->setModel(model);
tree->setRootIndex(model->index(QDir::currentPath()));

QListView *list = new QListView(splitter);
list->setModel(model);
list->setRootIndex(model->index(QDir::currentPath()));

Las vistas se construyen de la misma manera que otros widgets. Configurar una vista para que muestre los elementos del modelo es simplemente cuestión de llamar a su función setModel() con el modelo de directorio como argumento. Filtramos los datos suministrados por el modelo llamando a la función setRootIndex() en cada vista, pasando un índice de modelo adecuado del modelo del sistema de archivos para el directorio actual.

La función index() utilizada en este caso es exclusiva de QFileSystemModel; le proporcionamos un directorio y nos devuelve un índice de modelo. Los índices de modelos se tratan en Clases de modelos.

El resto de la función sólo muestra las vistas dentro de un widget divisor, y ejecuta el bucle de eventos de la aplicación:

    splitter->setWindowTitle("Two views onto the same file system model");
    splitter->show();
    return app.exec();
}

En el ejemplo anterior, nos olvidamos de mencionar cómo manejar las selecciones de elementos. Este tema se trata con más detalle en la sección sobre Manejo de selecciones en vistas de elementos.

Clases Modelo

Antes de examinar cómo se manejan las selecciones, puede resultarle útil examinar los conceptos utilizados en el marco modelo/vista.

Conceptos básicos

En la arquitectura modelo/vista, el modelo proporciona una interfaz estándar que las vistas y los delegados utilizan para acceder a los datos. En Qt, la interfaz estándar está definida por la clase QAbstractItemModel. Independientemente de cómo se almacenen los elementos de datos en cualquier estructura de datos subyacente, todas las subclases de QAbstractItemModel representan los datos como una estructura jerárquica que contiene tablas de elementos. Las vistas utilizan esta convención para acceder a los elementos de datos del modelo, pero no están restringidas en la forma de presentar esta información al usuario.

Modelo de lista, modelo de tabla y modelo de árbol

Los modelos también notifican a las vistas adjuntas los cambios en los datos a través del mecanismo de señales y ranuras.

En esta sección se describen algunos conceptos básicos relacionados con el modo en que otros componentes acceden a los datos a través de una clase modelo. En secciones posteriores se tratan conceptos más avanzados.

Índices del modelo

Para garantizar que la representación de los datos se mantiene separada del modo en que se accede a ellos, se introduce el concepto de índice de modelo. Cada dato que puede obtenerse a través de un modelo se representa mediante un índice de modelo. Las vistas y los delegados utilizan estos índices para solicitar elementos de datos que mostrar.

Como resultado, sólo el modelo necesita saber cómo obtener datos, y el tipo de datos gestionados por el modelo puede definirse de forma bastante general. Los índices de modelo contienen un puntero al modelo que los ha creado, lo que evita confusiones cuando se trabaja con más de un modelo.

const QAbstractItemModel *model = index.model();

Los índices de modelo proporcionan referencias temporales a fragmentos de información, y pueden utilizarse para recuperar o modificar datos a través del modelo. Dado que los modelos pueden reorganizar sus estructuras internas de vez en cuando, los índices de modelos pueden perder su validez y no deben almacenarse. Si se necesita una referencia a largo plazo a un dato, debe crearse un índice de modelo persistente. Esto proporciona una referencia a la información que el modelo mantiene actualizada. Los índices temporales del modelo los proporciona la clase QModelIndex, y los índices persistentes del modelo los proporciona la clase QPersistentModelIndex.

Para obtener un índice de modelo que corresponda a un elemento de datos, se deben especificar tres propiedades al modelo: un número de fila, un número de columna y el índice de modelo de un elemento padre. En las secciones siguientes se describen y explican detalladamente estas propiedades.

Filas y columnas

En su forma más básica, se puede acceder a un modelo como a una simple tabla en la que los elementos se localizan por sus números de fila y columna. Esto no significa que los datos subyacentes se almacenen en una estructura de matriz; el uso de números de fila y columna es sólo una convención que permite a los componentes comunicarse entre sí. Podemos recuperar información sobre cualquier elemento especificando sus números de fila y columna al modelo, y recibimos un índice que representa el elemento:

QModelIndex index = model->index(row, column /*...*/);

Los modelos que proporcionan interfaces a estructuras de datos simples, de un solo nivel, como listas y tablas, no necesitan que se proporcione ninguna otra información pero, como indica el código anterior, necesitamos suministrar más información cuando obtenemos un índice del modelo.

Estructura del modelo de tabla mediante filas y columnasFilas y columnas

El diagrama muestra una representación de un modelo de tabla básico en el que cada elemento se localiza mediante un par de números de fila y columna. Para obtener un índice de modelo que haga referencia a un dato, hay que pasar al modelo los números de fila y columna correspondientes.

QModelIndex indexA = model->index(0, 0, QModelIndex());
QModelIndex indexB = model->index(1, 1, QModelIndex());
QModelIndex indexC = model->index(2, 1, QModelIndex());

Los elementos de nivel superior de un modelo se referencian siempre especificando QModelIndex() como su elemento padre. Este tema se trata en la sección siguiente.

Elementos padre

La interfaz similar a una tabla para los datos de los elementos que proporcionan los modelos es ideal cuando se utilizan datos en una vista de tabla o de lista; el sistema de numeración de filas y columnas se corresponde exactamente con la forma en que las vistas muestran los elementos. Sin embargo, estructuras como las vistas en árbol requieren que el modelo exponga una interfaz más flexible a los elementos que contiene. Como resultado, cada elemento puede ser también el padre de otra tabla de elementos, de la misma forma que un elemento de nivel superior en una vista en árbol puede contener otra lista de elementos.

Al solicitar un índice para un elemento del modelo, debemos proporcionar alguna información sobre el elemento padre. Fuera del modelo, la única forma de referirse a un elemento es a través de un índice de modelo, por lo que también debe proporcionarse un índice de modelo padre:

QModelIndex index = model->index(row, column, parent);
Estructura del modelo de árbol con elementos padre, fila y columnaPadres, filas y columnas

El diagrama muestra una representación de un modelo de árbol en el que se hace referencia a cada elemento mediante un padre, un número de fila y un número de columna.

Los elementos "A" y "C" se representan como hermanos de nivel superior en el modelo:

QModelIndex indexA = model->index(0, 0, QModelIndex());
QModelIndex indexC = model->index(2, 1, QModelIndex());

El elemento "A" tiene varios hijos. Un índice del modelo para el elemento "B" se obtiene con el siguiente código:

QModelIndex indexB = model->index(1, 0, indexA);

Funciones de los elementos

Los elementos de un modelo pueden desempeñar varias funciones para otros componentes, lo que permite suministrar distintos tipos de datos para diferentes situaciones. Por ejemplo, Qt::DisplayRole se utiliza para acceder a una cadena que puede mostrarse como texto en una vista. Normalmente, los elementos contienen datos para un número de roles diferentes, y los roles estándar están definidos por Qt::ItemDataRole.

Podemos pedir al modelo los datos del ítem pasándole el índice del modelo correspondiente al ítem, y especificando un rol para obtener el tipo de datos que queremos:

QVariant value = model->data(index, role);
Diferentes funciones en un modeloItem roles

El rol indica al modelo a qué tipo de datos se está haciendo referencia. Las vistas pueden mostrar los roles de diferentes maneras, por lo que es importante suministrar la información adecuada para cada rol.

En la sección Creación de nuevos modelos se tratan con más detalle algunos usos específicos de los roles.

Los usos más comunes de los datos de artículos están cubiertos por los roles estándar definidos en Qt::ItemDataRole. Al suministrar los datos de artículos apropiados para cada rol, los modelos pueden proporcionar pistas a las vistas y a los delegados sobre cómo deben presentarse los artículos al usuario. Los distintos tipos de vistas tienen libertad para interpretar o ignorar esta información según sus necesidades. También es posible definir funciones adicionales para aplicaciones específicas.

Resumen

  • Los índices de modelos proporcionan a las vistas y a los delegados información sobre la ubicación de los elementos proporcionados por los modelos de forma independiente de cualquier estructura de datos subyacente.
  • Se hace referencia a los elementos por sus números de fila y columna, y por el índice de modelo de sus elementos padre.
  • Los índices de modelo los construyen los modelos a petición de otros componentes, como las vistas y los delegados.
  • Si se especifica un índice de modelo válido para el elemento padre cuando se solicita un índice mediante index(), el índice devuelto se refiere a un elemento situado debajo de ese elemento padre en el modelo. El índice obtenido se refiere a un elemento hijo de ese elemento.
  • Si se especifica un índice de modelo no válido para el elemento padre cuando se solicita un índice mediante index(), el índice devuelto se refiere a un elemento de nivel superior en el modelo.
  • role distingue entre los distintos tipos de datos asociados a un elemento.

Utilización de los índices del modelo

Para demostrar cómo se pueden recuperar datos de un modelo, utilizando índices de modelo, configuramos un QFileSystemModel sin vista y mostramos los nombres de los archivos y directorios en un widget. Aunque esto no muestra una forma normal de utilizar un modelo, demuestra las convenciones utilizadas por los modelos cuando se trata de índices de modelos.

QFileSystemModel La carga es asíncrona para minimizar el uso de recursos del sistema. Debemos tenerlo en cuenta al tratar este modelo.

Construimos un modelo de sistema de archivos de la siguiente manera:

auto *model = new QFileSystemModel;

auto onDirectoryLoaded = [model, layout, &window](const QString &directory) {
    QModelIndex parentIndex = model->index(directory);
    const int numRows = model->rowCount(parentIndex);
    for (int row = 0; row < numRows; ++row) {
        QModelIndex index = model->index(row, 0, parentIndex);

        QString text = model->data(index, Qt::DisplayRole).toString();

        // Display the text in a widget.
        auto *label = new QLabel(text, &window);
        layout->addWidget(label);
    }
};

QObject::connect(model, &QFileSystemModel::directoryLoaded, onDirectoryLoaded);
model->setRootPath(QDir::currentPath());

En este caso, empezamos configurando un QFileSystemModel por defecto. Conectamos su señal directoryLoaded(QString) a una lambda, en la que obtendremos un índice padre para el directorio utilizando una implementación específica de index() proporcionada por ese modelo.

En la lambda, determinamos el número de filas del modelo utilizando la función rowCount().

Para simplificar, sólo nos interesan los elementos de la primera columna del modelo. Examinamos cada fila sucesivamente, obteniendo un índice del modelo para el primer elemento de cada fila, y leemos los datos almacenados para ese elemento en el modelo.

for (int row = 0; row < numRows; ++row) {
    QModelIndex index = model->index(row, 0, parentIndex);

Para obtener un índice de modelo, se especifica el número de fila, el número de columna (cero para la primera columna) y el índice de modelo apropiado para el elemento principal de todos los elementos que se deseen. El texto almacenado en cada elemento se recupera utilizando la función data() del modelo. Especificamos el índice del modelo y la dirección DisplayRole para obtener los datos del elemento en forma de cadena.

    QString text = model->data(index, Qt::DisplayRole).toString();

}

Por último, establecemos la ruta raíz de QFileSystemModel para que comience a cargar los datos y active la lambda.

El ejemplo anterior demuestra los principios básicos utilizados para recuperar datos de un modelo:

  • Las dimensiones de un modelo se pueden encontrar utilizando rowCount() y columnCount(). Estas funciones generalmente requieren que se especifique un índice de modelo padre.
  • Los índices del modelo se utilizan para acceder a los elementos del modelo. La fila, la columna y el índice del modelo padre son necesarios para especificar el elemento.
  • Para acceder a los elementos de nivel superior de un modelo, especifique un índice de modelo nulo como índice padre con QModelIndex().
  • Los elementos contienen datos para diferentes roles. Para obtener los datos de un rol en particular, tanto el índice del modelo como el rol deben ser suministrados al modelo.

Más información

Se pueden crear nuevos modelos implementando la interfaz estándar proporcionada por QAbstractItemModel. En la sección Creación de nuevos modelos, lo demostramos creando un práctico modelo listo para usar que contiene listas de cadenas.

Ver clases

Conceptos

En la arquitectura modelo/vista, la vista obtiene elementos de datos del modelo y los presenta al usuario. La forma en que se presentan los datos no tiene por qué parecerse a la representación de los datos proporcionada por el modelo, y puede ser completamente diferente de la estructura de datos subyacente utilizada para almacenar los elementos de datos.

La separación entre contenido y presentación se consigue mediante el uso de una interfaz de modelo estándar proporcionada por QAbstractItemModel, una interfaz de vista estándar proporcionada por QAbstractItemView, y el uso de índices de modelo que representan elementos de datos de forma general. Las vistas suelen gestionar la presentación general de los datos obtenidos de los modelos. Pueden representar ellas mismas elementos individuales de datos, o utilizar delegados para gestionar tanto las funciones de representación como las de edición.

Además de presentar los datos, las vistas gestionan la navegación entre elementos y algunos aspectos de la selección de elementos. Las vistas también implementan funciones básicas de interfaz de usuario, como los menús contextuales y la función de arrastrar y soltar. Una vista puede proporcionar facilidades de edición por defecto para los elementos, o puede trabajar con un delegado para proporcionar un editor personalizado.

Una vista puede construirse sin un modelo, pero debe proporcionarse un modelo antes de que pueda mostrar información útil. Las vistas mantienen un registro de los elementos que el usuario ha seleccionado mediante el uso de selecciones que pueden ser mantenidas por separado para cada vista, o compartidas entre múltiples vistas.

Algunas vistas, como QTableView y QTreeView, muestran encabezados además de elementos. Éstas también se implementan mediante una clase de vista, QHeaderView. Las cabeceras suelen acceder al mismo modelo que la vista que las contiene. Recuperan datos del modelo mediante la función QAbstractItemModel::headerData() y suelen mostrar la información de la cabecera en forma de etiqueta. Se pueden subclasificar nuevas cabeceras a partir de la clase QHeaderView para proporcionar etiquetas más especializadas para las vistas.

Utilizar una vista existente

Qt proporciona tres clases de vistas listas para usar que presentan los datos de los modelos de formas que resultan familiares a la mayoría de los usuarios. QListView puede mostrar elementos de un modelo como una simple lista, o en forma de una vista de iconos clásica. QTreeView muestra elementos de un modelo como una jerarquía de listas, lo que permite representar estructuras profundamente anidadas de forma compacta. QTableView presenta elementos de un modelo en forma de tabla, muy similar al diseño de una aplicación de hoja de cálculo.

Vista de lista, vista de árbol y vista de tabla

El comportamiento por defecto de las vistas estándar mostradas anteriormente debería ser suficiente para la mayoría de las aplicaciones. Proporcionan facilidades básicas de edición y pueden personalizarse para adaptarse a las necesidades de interfaces de usuario más especializadas.

Utilización de un modelo

Tomamos el modelo de lista de cadenas que hemos creado como modelo de ejemplo, lo configuramos con algunos datos y construimos una vista para mostrar el contenido del modelo. Todo esto se puede realizar dentro de una sola función:

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

// Unindented for quoting purposes:
QStringList numbers;
numbers << "One" << "Two" << "Three" << "Four" << "Five";

QAbstractItemModel *model = new StringListModel(numbers);

Observe que StringListModel se declara como QAbstractItemModel. Esto nos permite utilizar la interfaz abstracta del modelo y garantiza que el código siga funcionando aunque sustituyamos el modelo de lista de cadenas por un modelo diferente.

La vista de lista proporcionada por QListView es suficiente para presentar los elementos del modelo de lista de cadenas. Construimos la vista y configuramos el modelo utilizando las siguientes líneas de código:

QListView *view = new QListView;
view->setModel(model);

La vista se muestra de forma normal:

    view->show();
    return app.exec();
}

La vista muestra el contenido de un modelo, accediendo a los datos a través de la interfaz del modelo. Cuando el usuario intenta editar un elemento, la vista utiliza un delegado por defecto para proporcionar un widget editor.

Lista de texto utilizando un modelo de lista de cadenas

La imagen anterior muestra cómo un QListView representa los datos en el modelo de lista de cadenas. Dado que el modelo es editable, la vista permite automáticamente editar cada elemento de la lista utilizando el delegado predeterminado.

Uso de múltiples vistas de un modelo

Proporcionar múltiples vistas sobre el mismo modelo es simplemente una cuestión de establecer el mismo modelo para cada vista. En el siguiente código creamos dos vistas de tabla, cada una utilizando el mismo modelo de tabla simple que hemos creado para este ejemplo:

QTableView *firstTableView = new QTableView;
QTableView *secondTableView = new QTableView;

firstTableView->setModel(model);
secondTableView->setModel(model);

El uso de señales y ranuras en la arquitectura modelo/vista significa que los cambios en el modelo pueden propagarse a todas las vistas adjuntas, asegurando que siempre podamos acceder a los mismos datos independientemente de la vista que se esté utilizando.

Dos vistas de tabla comparten un modelo, pero no comparten el modelo de selección

La imagen anterior muestra dos vistas diferentes del mismo modelo, cada una de las cuales contiene una serie de elementos seleccionados. Aunque los datos del modelo se muestran de forma consistente en todas las vistas, cada vista mantiene su propio modelo de selección interno. Esto puede ser útil en determinadas situaciones pero, para muchas aplicaciones, es deseable un modelo de selección compartido.

Manejo de selecciones de elementos

El mecanismo para manejar selecciones de elementos dentro de las vistas lo proporciona la clase QItemSelectionModel. Todas las vistas estándar construyen sus propios modelos de selección por defecto e interactúan con ellos de la forma habitual. El modelo de selección utilizado por una vista puede obtenerse mediante la función selectionModel(), y puede especificarse un modelo de selección de sustitución con setSelectionModel(). La capacidad de controlar el modelo de selección utilizado por una vista es útil cuando queremos proporcionar múltiples vistas consistentes sobre los mismos datos del modelo.

Generalmente, a menos que se esté subclasificando un modelo o una vista, no es necesario manipular el contenido de las selecciones directamente. Sin embargo, se puede acceder a la interfaz del modelo de selección, si es necesario, y esto se explora en Manejo de selecciones en vistas de elementos.

Compartir selecciones entre vistas

Aunque es conveniente que las clases de vista proporcionen sus propios modelos de selección por defecto, cuando utilizamos más de una vista sobre el mismo modelo a menudo es deseable que tanto los datos del modelo como la selección del usuario se muestren de forma consistente en todas las vistas. Dado que las clases de vista permiten sustituir sus modelos de selección internos, podemos conseguir una selección unificada entre vistas con la siguiente línea:

secondTableView->setSelectionModel(firstTableView->selectionModel());

La segunda vista recibe el modelo de selección de la primera vista. Ambas vistas operan ahora sobre el mismo modelo de selección, manteniendo sincronizados tanto los datos como los elementos seleccionados.

Dos vistas de tabla con el mismo modelo de selección

En el ejemplo anterior, se han utilizado dos vistas del mismo tipo para mostrar los datos del mismo modelo. Sin embargo, si se utilizaran dos tipos de vistas diferentes, los elementos seleccionados podrían representarse de forma muy distinta en cada vista; por ejemplo, una selección contigua en una vista de tabla puede representarse como un conjunto fragmentado de elementos resaltados en una vista de árbol.

Clases de delegados

Conceptos

A diferencia del patrón Modelo-Vista-Controlador, el diseño modelo/vista no incluye un componente completamente separado para gestionar la interacción con el usuario. Generalmente, la vista es responsable de la presentación de los datos del modelo al usuario, y de procesar las entradas del usuario. Para permitir cierta flexibilidad en la forma en que se obtiene esta entrada, la interacción se realiza mediante delegados. Estos componentes proporcionan capacidades de entrada y también son responsables de renderizar elementos individuales en algunas vistas. La interfaz estándar para controlar a los delegados se define en la clase QAbstractItemDelegate.

Se espera que los delegados sean capaces de renderizar sus contenidos por sí mismos implementando las funciones paint() y sizeHint(). Sin embargo, los delegados basados en widgets simples pueden subclasificar QStyledItemDelegate en lugar de QAbstractItemDelegate, y aprovechar las implementaciones por defecto de estas funciones.

Los editores para los delegados pueden implementarse utilizando widgets para gestionar el proceso de edición o gestionando los eventos directamente. El primer enfoque se trata más adelante en esta sección.

Utilizar un delegado existente

Las vistas estándar proporcionadas con Qt utilizan instancias de QStyledItemDelegate para proporcionar facilidades de edición. Esta implementación por defecto de la interfaz de delegado muestra los elementos en el estilo habitual para cada una de las vistas estándar: QListView QTableView y QTreeView.

Todas las funciones estándar son gestionadas por el delegado por defecto utilizado por las vistas estándar. La forma en que se interpretan se describe en la documentación de QStyledItemDelegate.

El delegado utilizado por una vista es devuelto por la función itemDelegate(). La función setItemDelegate() permite instalar un delegado personalizado para una vista estándar, y es necesario utilizar esta función cuando se establece el delegado para una vista personalizada.

Un delegado sencillo

El delegado implementado aquí utiliza un QSpinBox para proporcionar facilidades de edición, y está pensado principalmente para su uso con modelos que muestran números enteros. Aunque para ello hemos creado un modelo de tabla basado en números enteros, podríamos haber utilizado QStandardItemModel, ya que el delegado personalizado controla la entrada de datos. Construimos una vista de tabla para mostrar el contenido del modelo, que utilizará el delegado personalizado para la edición.

Delegado de cuadro de giro personalizado para edición

Subclasificamos el delegado de QStyledItemDelegate porque no queremos escribir funciones de visualización personalizadas. Sin embargo, debemos proporcionar funciones para gestionar el widget del editor:

class SpinBoxDelegate : public QStyledItemDelegate
{
    Q_OBJECT

public:
    SpinBoxDelegate(QObject *parent = nullptr);

    QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
                          const QModelIndex &index) const override;

    void setEditorData(QWidget *editor, const QModelIndex &index) const override;
    void setModelData(QWidget *editor, QAbstractItemModel *model,
                      const QModelIndex &index) const override;

    void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option,
                              const QModelIndex &index) const override;
};

SpinBoxDelegate::SpinBoxDelegate(QObject *parent)
    : QStyledItemDelegate(parent)
{
}

Ten en cuenta que no se configura ningún widget editor cuando se construye el delegado. Sólo construimos un widget editor cuando es necesario.

Proporcionar un editor

En este ejemplo, cuando la vista de tabla necesita proporcionar un editor, pide al delegado que proporcione un widget editor que sea apropiado para el elemento que se está modificando. La función createEditor() se suministra con todo lo que el delegado necesita para poder configurar un widget adecuado:

QWidget *SpinBoxDelegate::createEditor(QWidget *parent,
                                       const QStyleOptionViewItem &/* option */,
                                       const QModelIndex &/* index */) const
{
    QSpinBox *editor = new QSpinBox(parent);
    editor->setFrame(false);
    editor->setMinimum(0);
    editor->setMaximum(100);

    return editor;
}

Ten en cuenta que no necesitamos mantener un puntero al widget del editor porque la vista se encarga de destruirlo cuando ya no es necesario.

Instalamos el filtro de eventos por defecto del delegado en el editor para asegurarnos de que proporciona los atajos de edición estándar que esperan los usuarios. Se pueden añadir atajos adicionales al editor para permitir un comportamiento más sofisticado; éstos se discuten en la sección de Sugerencias de Edición.

La vista asegura que los datos y la geometría del editor se establecen correctamente llamando a funciones que definiremos más adelante para estos propósitos. Podemos crear diferentes editores dependiendo del índice del modelo suministrado por la vista. Por ejemplo, si tenemos una columna de enteros y otra de cadenas podríamos devolver un QSpinBox o un QLineEdit, dependiendo de la columna que se esté editando.

El delegado debe proporcionar una función para copiar los datos del modelo en el editor. En este ejemplo, leemos los datos almacenados en display role, y establecemos el valor en el cuadro de giro en consecuencia.

void SpinBoxDelegate::setEditorData(QWidget *editor,
                                    const QModelIndex &index) const
{
    int value = index.data(Qt::EditRole).toInt();

    QSpinBox *spinBox = static_cast<QSpinBox*>(editor);
    spinBox->setValue(value);
}

En este ejemplo, sabemos que el widget editor es un cuadro de giro, pero podríamos haber proporcionado diferentes editores para diferentes tipos de datos en el modelo, en cuyo caso tendríamos que convertir el widget al tipo apropiado antes de acceder a sus funciones miembro.

Envío de datos al modelo

Cuando el usuario ha terminado de editar el valor en el cuadro de giro, la vista pide al delegado que almacene el valor editado en el modelo llamando a la función setModelData().

void SpinBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
                                   const QModelIndex &index) const
{
    QSpinBox *spinBox = static_cast<QSpinBox*>(editor);
    spinBox->interpretText();
    int value = spinBox->value();

    model->setData(index, value, Qt::EditRole);
}

Dado que la vista gestiona los widgets del editor para el delegado, sólo necesitamos actualizar el modelo con el contenido del editor suministrado. En este caso, nos aseguramos de que el cuadro de giro está actualizado, y actualizamos el modelo con el valor que contiene utilizando el índice especificado.

La clase estándar QStyledItemDelegate informa a la vista cuando ha terminado de editar emitiendo la señal closeEditor(). La vista se encarga de cerrar y destruir el widget del editor. En este ejemplo, sólo proporcionamos facilidades de edición simples, por lo que nunca necesitamos emitir esta señal.

Todas las operaciones sobre los datos se realizan a través de la interfaz proporcionada por QAbstractItemModel. Esto hace que el delegado sea en gran medida independiente del tipo de datos que manipula, pero hay que hacer algunas suposiciones para poder utilizar ciertos tipos de widgets de edición. En este ejemplo, hemos asumido que el modelo siempre contiene valores enteros, pero aún así podemos utilizar este delegado con diferentes tipos de modelos porque QVariant proporciona valores por defecto sensibles para datos inesperados.

Actualización de la geometría del editor

Es responsabilidad del delegado gestionar la geometría del editor. La geometría debe establecerse cuando se crea el editor y cuando se cambia el tamaño o la posición del elemento en la vista. Afortunadamente, la vista proporciona toda la información necesaria sobre la geometría dentro de un objeto view option.

void SpinBoxDelegate::updateEditorGeometry(QWidget *editor,
                                           const QStyleOptionViewItem &option,
                                           const QModelIndex &/* index */) const
{
    editor->setGeometry(option.rect);
}

En este caso, sólo utilizamos la información de geometría proporcionada por la opción de vista en el rectángulo del elemento. Un delegado que renderice elementos con varios elementos no utilizaría el rectángulo del elemento directamente. Posicionaría el editor en relación a los otros elementos del ítem.

Consejos de edición

Después de la edición, los delegados deben proporcionar pistas a los otros componentes sobre el resultado del proceso de edición, y proporcionar pistas que ayudarán a cualquier operación de edición posterior. Esto se consigue emitiendo la señal closeEditor() con una pista adecuada. De esto se encarga el filtro de eventos por defecto QStyledItemDelegate que instalamos en el spin box cuando se construyó.

El comportamiento de la caja de giro podría ajustarse para hacerlo más fácil de usar. En el filtro de eventos por defecto suministrado por QStyledItemDelegate, si el usuario pulsa Return para confirmar su elección en el cuadro de giro, el delegado consigna el valor en el modelo y cierra el cuadro de giro. Podemos cambiar este comportamiento instalando nuestro propio filtro de eventos en el cuadro de giro, y proporcionar pistas de edición que se adapten a nuestras necesidades; por ejemplo, podríamos emitir closeEditor() con la pista EditNextItem para iniciar automáticamente la edición del siguiente elemento de la vista.

Otro enfoque que no requiere el uso de un filtro de eventos es proporcionar nuestro propio widget editor, quizás subclasificando QSpinBox por conveniencia. Este enfoque alternativo nos daría más control sobre cómo se comporta el widget del editor a costa de escribir código adicional. Normalmente es más fácil instalar un filtro de eventos en el delegado si necesitas personalizar el comportamiento de un widget editor Qt estándar.

Los delegados no tienen por qué emitir estas sugerencias, pero los que no lo hagan estarán menos integrados en las aplicaciones, y serán menos utilizables que los que emiten sugerencias para soportar acciones de edición comunes.

Manejo de selecciones en vistas de elementos

Conceptos

El modelo de selección utilizado en las clases de vistas de elementos proporciona una descripción general de las selecciones basada en las facilidades de la arquitectura modelo/vista. Aunque las clases estándar para manipular selecciones son suficientes para las vistas de ítems proporcionadas, el modelo de selección le permite crear modelos de selección especializados para satisfacer los requisitos de sus propios modelos y vistas de ítems.

La información sobre los elementos seleccionados en una vista se almacena en una instancia de la clase QItemSelectionModel. Esto mantiene los índices del modelo para los elementos en un único modelo, y es independiente de cualquier vista. Dado que puede haber muchas vistas en un modelo, es posible compartir selecciones entre vistas, lo que permite a las aplicaciones mostrar varias vistas de forma coherente.

Las selecciones se componen de rangos de selección. Éstos mantienen eficazmente la información sobre grandes selecciones de elementos registrando sólo los índices de inicio y fin del modelo para cada rango de elementos seleccionados. Las selecciones no contiguas de elementos se construyen utilizando más de un rango de selección para describir la selección.

Las selecciones se aplican a una colección de índices modelo mantenidos por un modelo de selección. La selección de elementos aplicada más recientemente se conoce como selección actual. Los efectos de esta selección pueden modificarse incluso después de su aplicación mediante el uso de ciertos tipos de comandos de selección. Éstos se tratan más adelante en esta sección.

Elemento actual y elementos seleccionados

En una vista siempre hay un elemento actual y un elemento seleccionado, dos estados independientes. Un elemento puede ser el elemento actual y estar seleccionado al mismo tiempo. La vista es responsable de garantizar que siempre haya un elemento actual, ya que la navegación por teclado, por ejemplo, requiere un elemento actual.

La siguiente tabla muestra las diferencias entre el elemento actual y los elementos seleccionados.

Elemento actualElementos seleccionados
Sólo puede haber un elemento actual.Puede haber varios elementos seleccionados.
El elemento actual se cambiará con la navegación mediante teclas o pulsando los botones del ratón.El estado seleccionado de los elementos se activa o desactiva en función de varios modos predefinidos (por ejemplo, selección única, selección múltiple, etc.) cuando el usuario interactúa con ellos. - cuando el usuario interactúa con los elementos.
El elemento actual se editará si se pulsa la tecla de edición, F2, o si se hace doble clic en el elemento (siempre que la edición esté activada).El elemento actual puede utilizarse junto con un ancla para especificar un rango que debe seleccionarse o deseleccionarse (o una combinación de ambos).
El elemento actual se indica mediante el rectángulo de enfoque.Los elementos seleccionados se indican con el rectángulo de selección.

Al manipular selecciones, a menudo es útil pensar en QItemSelectionModel como un registro del estado de selección de todos los elementos de un modelo de elementos. Una vez configurado un modelo de selección, las colecciones de elementos se pueden seleccionar, deseleccionar o cambiar su estado de selección sin necesidad de saber qué elementos están ya seleccionados. Los índices de todos los elementos seleccionados pueden recuperarse en cualquier momento, y otros componentes pueden ser informados de los cambios en el modelo de selección a través del mecanismo de señales y ranuras.

Utilizar un modelo de selección

Las clases de vistas estándar proporcionan modelos de selección predeterminados que pueden utilizarse en la mayoría de las aplicaciones. Un modelo de selección perteneciente a una vista puede obtenerse utilizando la función selectionModel() de la vista, y compartirse entre muchas vistas con setSelectionModel(), por lo que generalmente no es necesario construir nuevos modelos de selección.

Una selección se crea especificando un modelo, y un par de índices de modelo a un QItemSelection. Éste utiliza los índices para referirse a elementos en el modelo dado, y los interpreta como los elementos superior izquierdo e inferior derecho en un bloque de elementos seleccionados. Para aplicar la selección a los elementos de un modelo es necesario someter la selección a un modelo de selección; esto puede conseguirse de varias maneras, cada una de las cuales tiene un efecto diferente sobre las selecciones ya presentes en el modelo de selección.

Seleccionar elementos

Para demostrar algunas de las principales características de las selecciones, construimos una instancia de un modelo de tabla personalizado con 32 elementos en total, y abrimos una vista de tabla sobre sus datos:

TableModel *model = new TableModel(8, 4, &app);

QTableView *table = new QTableView(0);
table->setModel(model);

QItemSelectionModel *selectionModel = table->selectionModel();

El modelo de selección por defecto de la vista de tabla se recupera para su uso posterior. No modificamos ningún elemento del modelo, sino que seleccionamos algunos elementos que la vista mostrará en la parte superior izquierda de la tabla. Para ello, necesitamos recuperar los índices del modelo correspondientes a los elementos de la parte superior izquierda e inferior derecha de la región a seleccionar:

QModelIndex topLeft;
QModelIndex bottomRight;

topLeft = model->index(0, 0, QModelIndex());
bottomRight = model->index(5, 2, QModelIndex());

Para seleccionar estos elementos en el modelo, y ver el cambio correspondiente en la vista de la tabla, necesitamos construir un objeto de selección y luego aplicarlo al modelo de selección:

QItemSelection selection(topLeft, bottomRight);
selectionModel->select(selection, QItemSelectionModel::Select);

La selección se aplica al modelo de selección mediante un comando definido por una combinación de selection flags. En este caso, los indicadores utilizados hacen que los elementos registrados en el objeto de selección se incluyan en el modelo de selección, independientemente de su estado anterior. La selección resultante se muestra en la vista.

El modelo de selección de un modelo de tabla aparece resaltado en azul

La selección de elementos puede modificarse mediante diversas operaciones definidas por los indicadores de selección. La selección resultante de estas operaciones puede tener una estructura compleja, pero se representa de forma eficiente mediante el modelo de selección. El uso de diferentes indicadores de selección para manipular los elementos seleccionados se describe cuando examinamos cómo actualizar una selección.

Lectura del estado de la selección

Los índices del modelo almacenados en el modelo de selección pueden leerse utilizando la función selectedIndexes(). Esta función devuelve una lista sin ordenar de índices de modelos sobre la que podemos iterar siempre que sepamos a qué modelo corresponden:

const QModelIndexList indexes = selectionModel->selectedIndexes();

for (const QModelIndex &index : indexes) {
    QString text = QString("(%1,%2)").arg(index.row()).arg(index.column());
    model->setData(index, text);
}

El código anterior utiliza un bucle for basado en rangos para recorrer y modificar los elementos correspondientes a los índices devueltos por el modelo de selección.

El modelo de selección emite señales para indicar cambios en la selección. Estas señales notifican a otros componentes los cambios tanto en la selección en su conjunto como en el elemento actualmente seleccionado en el modelo de elementos. Podemos conectar la señal selectionChanged() a una ranura y examinar los elementos del modelo que se seleccionan o deseleccionan cuando cambia la selección. La ranura se llama con dos objetos QItemSelection: uno contiene una lista de índices que corresponden a los nuevos elementos seleccionados; el otro contiene índices que corresponden a los nuevos elementos deseleccionados.

En el siguiente código, proporcionamos una ranura que recibe la señal selectionChanged(), rellena los elementos seleccionados con una cadena y borra el contenido de los elementos deseleccionados.

void MainWindow::updateSelection(const QItemSelection &selected,
    const QItemSelection &deselected)
{
    QModelIndexList items = selected.indexes();

    for (const QModelIndex &index : std::as_const(items)) {
        QString text = QString("(%1,%2)").arg(index.row()).arg(index.column());
        model->setData(index, text);
    }

    items = deselected.indexes();

    for (const QModelIndex &index : std::as_const(items)) {
        model->setData(index, QString());
    }
}

Podemos realizar un seguimiento del elemento actualmente enfocado conectando la señal currentChanged() a una ranura que se llama con dos índices de modelo. Éstos corresponden al elemento seleccionado anteriormente y al elemento seleccionado actualmente.

En el siguiente código, proporcionamos una ranura que recibe la señal currentChanged(), y utiliza la información proporcionada para actualizar la barra de estado de un QMainWindow:

void MainWindow::changeCurrent(const QModelIndex &current,
    const QModelIndex &previous)
{
    statusBar()->showMessage(
        tr("Moved from (%1,%2) to (%3,%4)")
            .arg(previous.row()).arg(previous.column())
            .arg(current.row()).arg(current.column()));
}

Controlar las selecciones realizadas por el usuario es sencillo con estas señales, pero también podemos actualizar el modelo de selección directamente.

Actualizar una selección

Los comandos de selección se proporcionan mediante una combinación de banderas de selección, definidas por QItemSelectionModel::SelectionFlag. Cada bandera de selección indica al modelo de selección cómo actualizar su registro interno de elementos seleccionados cuando se llama a cualquiera de las funciones select(). El indicador más utilizado es Select, que indica al modelo de selección que registre los elementos especificados como seleccionados. La bandera Toggle hace que el modelo de selección invierta el estado de los elementos especificados, seleccionando cualquier elemento deseleccionado dado, y deseleccionando cualquier elemento seleccionado actualmente. La bandera Deselect deselecciona todos los elementos especificados.

Los elementos individuales del modelo de selección se actualizan creando una selección de elementos y aplicándola al modelo de selección. En el siguiente código, aplicamos una segunda selección de elementos al modelo de tabla mostrado anteriormente, utilizando el comando Toggle para invertir el estado de selección de los elementos dados.

QItemSelection toggleSelection;

topLeft = model->index(2, 1, QModelIndex());
bottomRight = model->index(7, 3, QModelIndex());
toggleSelection.select(topLeft, bottomRight);

selectionModel->select(toggleSelection, QItemSelectionModel::Toggle);

Los resultados de esta operación se muestran en la vista de tabla, proporcionando una forma conveniente de visualizar lo que hemos conseguido:

El modelo de selección actualizado tiene los colores invertidos

Por defecto, los comandos de selección sólo operan sobre los elementos individuales especificados por los índices del modelo. Sin embargo, la bandera utilizada para describir el comando de selección puede combinarse con banderas adicionales para cambiar filas y columnas enteras. Por ejemplo, si llama a select() con un solo índice, pero con un comando que es una combinación de Select y Rows, se selecciona toda la fila que contiene el elemento al que se hace referencia. El siguiente código demuestra el uso de los indicadores Rows y Columns:

QItemSelection columnSelection;

topLeft = model->index(0, 1, QModelIndex());
bottomRight = model->index(0, 2, QModelIndex());

columnSelection.select(topLeft, bottomRight);

selectionModel->select(columnSelection,
    QItemSelectionModel::Select | QItemSelectionModel::Columns);

QItemSelection rowSelection;

topLeft = model->index(0, 0, QModelIndex());
bottomRight = model->index(1, 0, QModelIndex());

rowSelection.select(topLeft, bottomRight);

selectionModel->select(rowSelection,
    QItemSelectionModel::Select | QItemSelectionModel::Rows);

Aunque sólo se suministran cuatro índices al modelo de selección, el uso de los indicadores de selección Columns y Rows significa que se seleccionan dos columnas y dos filas. La siguiente imagen muestra el resultado de estas dos selecciones:

Actualización de columnas o filas enteras en el modelo de selección

Todos los comandos realizados en el modelo de ejemplo han implicado la acumulación de una selección de elementos en el modelo. También es posible borrar la selección, o sustituir la selección actual por una nueva.

Para reemplazar la selección actual con una nueva selección, combine las otras banderas de selección con la bandera Current. Una orden que utilice este indicador indica al modelo de selección que sustituya su colección actual de índices de modelo por los especificados en una llamada a select(). Para borrar todas las selecciones antes de empezar a añadir otras nuevas, combine las otras banderas de selección con la bandera Clear. Esto tiene el efecto de reiniciar la colección de índices del modelo de selección.

Seleccionar todos los elementos de un modelo

Para seleccionar todos los elementos de un modelo, es necesario crear una selección para cada nivel del modelo que cubra todos los elementos de ese nivel. Para ello, se recuperan los índices correspondientes a los elementos situados arriba a la izquierda y abajo a la derecha con un índice padre determinado:

QModelIndex topLeft = model->index(0, 0, parent);
QModelIndex bottomRight = model->index(model->rowCount(parent)-1,
    model->columnCount(parent)-1, parent);

Se construye una selección con estos índices y el modelo. A continuación, se seleccionan los elementos correspondientes en el modelo de selección:

QItemSelection selection(topLeft, bottomRight);
selectionModel->select(selection, QItemSelectionModel::Select);

Esto debe realizarse para todos los niveles del modelo. Para los elementos de nivel superior, definiríamos el índice padre de la forma habitual:

En los modelos jerárquicos, se utiliza la función hasChildren() para determinar si un elemento determinado es el padre de otro nivel de elementos.

Creación de nuevos modelos

La separación de funciones entre los componentes modelo/vista permite crear modelos que pueden aprovechar las vistas existentes. Este enfoque nos permite presentar datos de una variedad de fuentes utilizando componentes de interfaz gráfica de usuario estándar, como QListView, QTableView, y QTreeView.

La clase QAbstractItemModel proporciona una interfaz lo suficientemente flexible como para soportar fuentes de datos que organizan la información en estructuras jerárquicas, permitiendo la posibilidad de que los datos sean insertados, eliminados, modificados u ordenados de alguna manera. También ofrece soporte para operaciones de arrastrar y soltar.

Las clases QAbstractListModel y QAbstractTableModel proporcionan soporte para interfaces a estructuras de datos no jerárquicas más simples, y son más fáciles de usar como punto de partida para modelos simples de listas y tablas.

En esta sección, creamos un modelo sencillo de sólo lectura para explorar los principios básicos de la arquitectura modelo/vista. Más adelante, adaptaremos este modelo simple para que los elementos puedan ser modificados por el usuario.

Para ver un ejemplo de un modelo más complejo, consulte el ejemplo Modelo de árbol simple.

Los requisitos de las subclases de QAbstractItemModel se describen con más detalle en el documento Model Subclassing Reference.

Diseño de un modelo

Al crear un nuevo modelo para una estructura de datos existente, es importante tener en cuenta qué tipo de modelo debe utilizarse para proporcionar una interfaz a los datos. Si la estructura de datos puede representarse como una lista o una tabla de elementos, puede subclasificar QAbstractListModel o QAbstractTableModel, ya que estas clases proporcionan implementaciones predeterminadas adecuadas para muchas funciones.

Sin embargo, si la estructura de datos subyacente sólo puede representarse mediante una estructura jerárquica de árbol, es necesario subclasificar QAbstractItemModel. Este enfoque se adopta en el ejemplo Modelo de árbol simple.

En esta sección, implementamos un modelo simple basado en una lista de cadenas, por lo que QAbstractListModel proporciona una clase base ideal sobre la que construir.

Sea cual sea la forma que adopte la estructura de datos subyacente, suele ser una buena idea complementar la API estándar QAbstractItemModel en modelos especializados con otra que permita un acceso más natural a la estructura de datos subyacente. De este modo, es más fácil rellenar el modelo con datos y, al mismo tiempo, otros componentes generales del modelo/vista pueden interactuar con él utilizando la API estándar. El modelo descrito a continuación proporciona un constructor personalizado para este propósito.

Un modelo de ejemplo de sólo lectura

El modelo implementado aquí es un modelo de datos simple, no jerárquico y de sólo lectura basado en la clase estándar QStringListModel. Su fuente de datos interna es QStringList e implementa sólo lo necesario para que el modelo funcione. Para facilitar la implementación, subclasificamos QAbstractListModel porque define un comportamiento por defecto razonable para los modelos de listas y expone una interfaz más sencilla que la clase QAbstractItemModel.

Al implementar un modelo es importante recordar que QAbstractItemModel no almacena ningún dato en sí, simplemente presenta una interfaz que las vistas utilizan para acceder a los datos. Para un modelo mínimo de sólo lectura sólo es necesario implementar unas pocas funciones, ya que existen implementaciones por defecto para la mayor parte de la interfaz. La declaración de la clase es la siguiente:

class StringListModel : public QAbstractListModel
{
    Q_OBJECT

public:
    StringListModel(const QStringList &strings, QObject *parent = nullptr)
        : QAbstractListModel(parent), stringList(strings) {}

    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
    QVariant data(const QModelIndex &index, int role) const override;
    QVariant headerData(int section, Qt::Orientation orientation,
                        int role = Qt::DisplayRole) const override;

private:
    QStringList stringList;
};

Aparte del constructor del modelo, sólo necesitamos implementar dos funciones: rowCount() devuelve el número de filas del modelo y data() devuelve un dato correspondiente a un índice especificado del modelo.

Los modelos que se comportan bien también implementan headerData() para dar a las vistas de árbol y tabla algo que mostrar en sus cabeceras.

Tenga en cuenta que este es un modelo no jerárquico, por lo que no tenemos que preocuparnos por las relaciones padre-hijo. Si nuestro modelo fuera jerárquico, también tendríamos que implementar las funciones index() y parent().

La lista de cadenas se almacena internamente en la variable miembro privada stringList.

Dimensiones del modelo

Queremos que el número de filas del modelo sea el mismo que el número de cadenas de la lista de cadenas. Para ello implementamos la función rowCount():

int StringListModel::rowCount(const QModelIndex &parent) const
{
    return stringList.count();
}

Dado que el modelo no es jerárquico, podemos ignorar con seguridad el índice del modelo correspondiente al elemento padre. Por defecto, los modelos derivados de QAbstractListModel sólo contienen una columna, por lo que no necesitamos reimplementar la función columnCount().

Cabeceras y datos del modelo

Para los elementos de la vista, queremos devolver las cadenas de la lista de cadenas. La función data() se encarga de devolver el elemento de datos que corresponde al argumento índice:

QVariant StringListModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
        return QVariant();

    if (index.row() >= stringList.size())
        return QVariant();

    if (role == Qt::DisplayRole)
        return stringList.at(index.row());
    else
        return QVariant();
}

Sólo devolvemos un QVariant válido si el índice del modelo suministrado es válido, el número de fila está dentro del rango de elementos de la lista de cadenas y la función solicitada es una de las que admitimos.

Algunas vistas, como QTreeView y QTableView, pueden mostrar cabeceras junto con los datos de los elementos. Si nuestro modelo se muestra en una vista con cabeceras, queremos que las cabeceras muestren los números de fila y columna. Podemos proporcionar información sobre las cabeceras subclasificando la función headerData():

QVariant StringListModel::headerData(int section, Qt::Orientation orientation,
                                     int role) const
{
    if (role != Qt::DisplayRole)
        return QVariant();

    if (orientation == Qt::Horizontal)
        return QStringLiteral("Column %1").arg(section);
    else
        return QStringLiteral("Row %1").arg(section);
}

De nuevo, devolvemos un QVariant válido sólo si el rol es uno que soportamos. La orientación de la cabecera también se tiene en cuenta a la hora de decidir los datos exactos a devolver.

No todas las vistas muestran cabeceras con los datos del elemento, y las que lo hacen pueden estar configuradas para ocultarlas. No obstante, se recomienda implementar la función headerData() para proporcionar información relevante sobre los datos proporcionados por el modelo.

Un ítem puede tener varios roles, proporcionando diferentes datos dependiendo del rol especificado. Los ítems de nuestro modelo sólo tienen un rol, DisplayRole, por lo que devolvemos los datos de los ítems independientemente del rol especificado. Sin embargo, podríamos reutilizar los datos que proporcionamos para el DisplayRole en otros roles, como el ToolTipRole que las vistas pueden utilizar para mostrar información sobre los elementos en un tooltip.

Un modelo editable

El modelo de sólo lectura muestra cómo se pueden presentar al usuario opciones sencillas pero, para muchas aplicaciones, un modelo de lista editable es mucho más útil. Podemos modificar el modelo de sólo lectura para que los elementos sean editables cambiando la función data() que implementamos para sólo lectura, e implementando dos funciones extra: flags() y setData(). Las siguientes declaraciones de funciones se añaden a la definición de la clase:

    Qt::ItemFlags flags(const QModelIndex &index) const override;
    bool setData(const QModelIndex &index, const QVariant &value,
                 int role = Qt::EditRole) override;

Hacer editable el modelo

Un delegado comprueba si un elemento es editable antes de crear un editor. El modelo debe hacer saber al delegado que sus elementos son editables. Esto se consigue devolviendo los indicadores correctos para cada elemento del modelo; en este caso, habilitamos todos los elementos y los hacemos seleccionables y editables:

Qt::ItemFlags StringListModel::flags(const QModelIndex &index) const
{
    if (!index.isValid())
        return Qt::ItemIsEnabled;

    return QAbstractItemModel::flags(index) | Qt::ItemIsEditable;
}

En este caso, activamos todos los elementos y los hacemos seleccionables y editables. Sólo tenemos que proporcionar una forma para que el delegado establezca los datos en el modelo. Esto se consigue mediante la función setData():

bool StringListModel::setData(const QModelIndex &index,
                              const QVariant &value, int role)
{
    if (index.isValid() && role == Qt::EditRole) {

        stringList.replace(index.row(), value.toString());
        emit dataChanged(index, index, {role});
        return true;
    }
    return false;
}

En este modelo, el elemento de la lista de cadenas que corresponde al índice del modelo se sustituye por el valor proporcionado. Sin embargo, antes de poder modificar la lista de cadenas, debemos asegurarnos de que el índice es válido, el elemento es del tipo correcto y el rol es compatible. Por convención, insistimos en que el rol sea EditRole, ya que es el rol utilizado por el delegado de ítem estándar. Sin embargo, para los valores booleanos, puede utilizar Qt::CheckStateRole y establecer la bandera Qt::ItemIsUserCheckable; a continuación, se utiliza una casilla de verificación para editar el valor. Los datos subyacentes en este modelo son los mismos para todos los roles, por lo que este detalle sólo facilita la integración del modelo con componentes estándar.

Cuando los datos se han establecido, el modelo debe dejar que las vistas sepan que algunos datos han cambiado. Esto se hace emitiendo la señal dataChanged(). Dado que sólo ha cambiado un elemento de datos, el rango de elementos especificados en la señal se limita a un solo índice del modelo.

También es necesario modificar la función data() para añadir la prueba Qt::EditRole:

QVariant StringListModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
        return QVariant();

    if (index.row() >= stringList.size())
        return QVariant();

    if (role == Qt::DisplayRole || role == Qt::EditRole)
        return stringList.at(index.row());
    else
        return QVariant();
}

Inserción y eliminación de filas

Es posible cambiar el número de filas y columnas de un modelo. En el modelo de lista de cadenas sólo tiene sentido cambiar el número de filas, por lo que sólo reimplementamos las funciones para insertar y eliminar filas. Éstas se declaran en la definición de la clase:

    bool insertRows(int position, int rows, const QModelIndex &index = QModelIndex()) override;
    bool removeRows(int position, int rows, const QModelIndex &index = QModelIndex()) override;

Dado que las filas en este modelo corresponden a cadenas en una lista, la función insertRows() inserta un número de cadenas vacías en la lista de cadenas antes de la posición especificada. El número de cadenas insertadas es equivalente al número de filas especificado.

El índice padre se utiliza normalmente para determinar en qué parte del modelo deben añadirse las filas. En este caso, sólo tenemos una lista de cadenas de nivel superior, por lo que nos limitamos a insertar cadenas vacías en esa lista.

bool StringListModel::insertRows(int position, int rows, const QModelIndex &parent)
{
    beginInsertRows(QModelIndex(), position, position+rows-1);

    for (int row = 0; row < rows; ++row) {
        stringList.insert(position, "");
    }

    endInsertRows();
    return true;
}

El modelo llama primero a la función beginInsertRows() para informar a otros componentes de que el número de filas está a punto de cambiar. La función especifica los números de fila de la primera y la última nueva fila que se va a insertar, así como el índice del modelo para su elemento padre. Después de cambiar la lista de filas, llama a endInsertRows() para completar la operación e informar a otros componentes de que las dimensiones del modelo han cambiado, devolviendo true para indicar el éxito.

La función para eliminar filas del modelo también es sencilla de escribir. Las filas a eliminar del modelo se especifican mediante la posición y el número de filas dados. Ignoramos el índice padre para simplificar nuestra implementación, y sólo eliminamos los elementos correspondientes de la lista de cadenas.

bool StringListModel::removeRows(int position, int rows, const QModelIndex &parent)
{
    beginRemoveRows(QModelIndex(), position, position+rows-1);

    for (int row = 0; row < rows; ++row) {
        stringList.removeAt(position);
    }

    endRemoveRows();
    return true;
}

La función beginRemoveRows() se ejecuta siempre antes de que se elimine cualquier dato subyacente, y especifica la primera y la última fila a eliminar. Esto permite a otros componentes acceder a los datos antes de que dejen de estar disponibles. Una vez eliminadas las filas, el modelo emite endRemoveRows() para finalizar la operación e informar a otros componentes de que las dimensiones del modelo han cambiado.

Pasos siguientes

Podemos mostrar los datos proporcionados por este modelo, o cualquier otro modelo, utilizando la clase QListView para presentar los elementos del modelo en forma de lista vertical. Para el modelo de lista de cadenas, esta vista también proporciona un editor por defecto para poder manipular los elementos. Examinaremos las posibilidades que ofrecen las clases de vistas estándar en Clases de vistas.

En el documento Model Subclassing Reference se analizan con más detalle los requisitos de las subclases de QAbstractItemModel y se ofrece una guía de las funciones virtuales que deben implementarse para habilitar diversas características en distintos tipos de modelos.

Clases de conveniencia de la vista de elementos

Los widgets basados en elementos tienen nombres que reflejan sus usos: QListWidget proporciona una lista de elementos, QTreeWidget muestra una estructura de árbol multinivel y QTableWidget proporciona una tabla de elementos de celda. Cada clase hereda el comportamiento de la clase QAbstractItemView, que implementa un comportamiento común para la selección de elementos y la gestión de cabeceras.

Widgets de lista

Las listas de elementos de un solo nivel suelen mostrarse utilizando un QListWidget y varios QListWidgetItems. Un widget de lista se construye del mismo modo que cualquier otro widget:

QListWidget *listWidget = new QListWidget(this);

Los elementos de la lista pueden añadirse directamente al widget de lista cuando se construyen:

new QListWidgetItem(tr("Sycamore"), listWidget);
new QListWidgetItem(tr("Chestnut"), listWidget);
new QListWidgetItem(tr("Mahogany"), listWidget);

También pueden construirse sin un widget de lista principal y añadirse a una lista posteriormente:

QListWidgetItem *newItem = new QListWidgetItem;
newItem->setText(itemText);
listWidget->insertItem(row, newItem);

Cada elemento de una lista puede mostrar una etiqueta de texto y un icono. Los colores y la fuente utilizados para representar el texto pueden cambiarse para proporcionar un aspecto personalizado a los elementos. La información sobre herramientas, sobre el estado y la ayuda "¿Qué es esto?" se configuran fácilmente para garantizar que la lista se integre correctamente en la aplicación.

newItem->setToolTip(toolTipText);
newItem->setStatusTip(toolTipText);
newItem->setWhatsThis(whatsThisText);

Por defecto, los elementos de una lista se presentan en el orden de su creación. Las listas de elementos pueden ordenarse según los criterios indicados en Qt::SortOrder para producir una lista de elementos ordenada alfabéticamente hacia delante o hacia atrás:

listWidget->sortItems(Qt::AscendingOrder);
listWidget->sortItems(Qt::DescendingOrder);

Widgets de árbol

Las clases QTreeWidget y QTreeWidgetItem proporcionan árboles o listas jerárquicas de elementos. Cada elemento del widget de árbol puede tener sus propios elementos secundarios y mostrar varias columnas de información. Los widgets de árbol se crean como cualquier otro widget:

QTreeWidget *treeWidget = new QTreeWidget(this);

Antes de añadir elementos al widget de árbol, hay que definir el número de columnas. Por ejemplo, podríamos definir dos columnas, y crear una cabecera para proporcionar etiquetas en la parte superior de cada columna:

treeWidget->setColumnCount(2);
QStringList headers;
headers << tr("Subject") << tr("Default");
treeWidget->setHeaderLabels(headers);

La forma más sencilla de configurar las etiquetas de cada sección es proporcionar una lista de cadenas. Para cabeceras más sofisticadas, puede construir un elemento de árbol, decorarlo como desee y utilizarlo como cabecera del widget de árbol.

Los elementos de nivel superior en el widget de árbol se construyen con el widget de árbol como su widget padre. Pueden insertarse en un orden arbitrario, o puedes asegurarte de que aparecen en un orden concreto especificando el elemento anterior al construir cada elemento:

QTreeWidgetItem *cities = new QTreeWidgetItem(treeWidget);
cities->setText(0, tr("Cities"));
QTreeWidgetItem *osloItem = new QTreeWidgetItem(cities);
osloItem->setText(0, tr("Oslo"));
osloItem->setText(1, tr("Yes"));

QTreeWidgetItem *planets = new QTreeWidgetItem(treeWidget, cities);

Los widgets de árbol tratan los elementos de nivel superior de forma ligeramente diferente a otros elementos situados más abajo en el árbol. Los elementos se pueden eliminar del nivel superior del árbol llamando a la función takeTopLevelItem() del widget de árbol, pero los elementos de niveles inferiores se eliminan llamando a la función takeChild() de su elemento padre. Los elementos se insertan en el nivel superior del árbol con la función insertTopLevelItem(). En los niveles inferiores del árbol, se utiliza la función insertChild() del elemento padre.

Es fácil mover elementos entre el nivel superior y los niveles inferiores del árbol. Sólo tenemos que comprobar si los elementos son de nivel superior o no, y esta información la proporciona la función parent() de cada elemento. Por ejemplo, podemos eliminar el elemento actual del widget del árbol independientemente de su ubicación:

QTreeWidgetItem *parent = currentItem->parent();
int index;

if (parent) {
    index = parent->indexOfChild(treeWidget->currentItem());
    delete parent->takeChild(index);
} else {
    index = treeWidget->indexOfTopLevelItem(treeWidget->currentItem());
    delete treeWidget->takeTopLevelItem(index);
}

Insertar el elemento en otro lugar del widget de árbol sigue el mismo patrón:

QTreeWidgetItem *parent = currentItem->parent();
QTreeWidgetItem *newItem;
if (parent)
    newItem = new QTreeWidgetItem(parent, treeWidget->currentItem());
else
    newItem = new QTreeWidgetItem(treeWidget, treeWidget->currentItem());

Widgets de tabla

Las tablas de elementos similares a las que se encuentran en las aplicaciones de hojas de cálculo se construyen con las funciones QTableWidget y QTableWidgetItem. Estos proporcionan un widget de tabla desplazable con cabeceras y elementos para utilizar dentro de ella.

Las tablas pueden crearse con un número determinado de filas y columnas, o éstas pueden añadirse a una tabla sin tamaño a medida que se necesiten.

QTableWidget *tableWidget;
tableWidget = new QTableWidget(12, 3, this);

Los elementos se construyen fuera de la tabla antes de añadirse a ella en el lugar necesario:

QTableWidgetItem *newItem = new QTableWidgetItem(tr("%1").arg(
    pow(row, column+1)));
tableWidget->setItem(row, column, newItem);

Las cabeceras horizontales y verticales pueden añadirse a la tabla construyendo elementos fuera de la tabla y utilizándolos como cabeceras:

QTableWidgetItem *valuesHeaderItem = new QTableWidgetItem(tr("Values"));
tableWidget->setHorizontalHeaderItem(0, valuesHeaderItem);

Las filas y columnas de la tabla empiezan en cero.

Funciones comunes

Hay una serie de características basadas en elementos comunes a cada una de las clases de conveniencia que están disponibles a través de las mismas interfaces en cada clase. Las presentamos en las siguientes secciones con algunos ejemplos para diferentes widgets. Mira la lista de Clases Modelo/Vista para cada uno de los widgets para más detalles sobre el uso de cada función utilizada.

Elementos ocultos

A veces es útil poder ocultar elementos en un widget de vista de elementos en lugar de eliminarlos. Los elementos de todos los widgets anteriores se pueden ocultar y volver a mostrar más tarde. Puede determinar si un elemento está oculto llamando a la función isItemHidden(), y los elementos pueden ocultarse con setItemHidden().

Dado que esta operación se basa en los elementos, la misma función está disponible para las tres clases de conveniencia.

Selecciones

La forma en que se seleccionan los elementos está controlada por el modo de selección del widget (QAbstractItemView::SelectionMode). Esta propiedad controla si el usuario puede seleccionar uno o muchos elementos y, en selecciones de muchos elementos, si la selección debe ser un rango continuo de elementos. El modo de selección funciona de la misma manera para todos los widgets anteriores.

Seleccionar un único elemento

Selecciones de un solo elemento: Cuando el usuario necesita elegir un único elemento de un widget, el modo por defecto SingleSelection es el más adecuado. En este modo, el elemento actual y el elemento seleccionado son el mismo.

Seleccionar varios elementos

Selecciones de varios elementos: En este modo, el usuario puede alternar el estado de selección de cualquier elemento del widget sin cambiar la selección existente, de forma muy parecida a como pueden alternarse independientemente las casillas de verificación no exclusivas.

Selección de elementos ampliados y adyacentes

Selecciones ampliadas: Los widgets que a menudo requieren que se seleccionen muchos elementos adyacentes, como los que se encuentran en las hojas de cálculo, requieren el modo ExtendedSelection. En este modo, los rangos continuos de elementos del widget pueden seleccionarse tanto con el ratón como con el teclado. Las selecciones complejas, que incluyen muchos elementos que no son adyacentes a otros elementos seleccionados en el widget, también pueden crearse si se utilizan teclas modificadoras.

Si el usuario selecciona un elemento sin utilizar una tecla modificadora, se borra la selección existente.

Los elementos seleccionados en un widget se leen utilizando la función selectedItems(), proporcionando una lista de elementos relevantes sobre la que se puede iterar. Por ejemplo, podemos encontrar la suma de todos los valores numéricos dentro de una lista de elementos seleccionados con el siguiente código:

const QList<QTableWidgetItem *> selected = tableWidget->selectedItems();
int number = 0;
double total = 0;

for (QTableWidgetItem *item : selected) {
    bool ok;
    double value = item->text().toDouble(&ok);

    if (ok && !item->text().isEmpty()) {
        total += value;
        number++;
    }
}

Tenga en cuenta que para el modo de selección simple, el elemento actual estará en la selección. En los modos de selección múltiple y extendida, el elemento actual puede no estar dentro de la selección, dependiendo de la forma en que el usuario formó la selección.

Búsqueda en

A menudo es útil poder encontrar elementos dentro de un widget de vista de elementos, ya sea como desarrollador o como servicio para presentar a los usuarios. Las tres clases de comodidad de la vista de elementos proporcionan una función común findItems() para que esto sea lo más coherente y sencillo posible.

Los elementos se buscan por el texto que contienen según los criterios especificados por una selección de valores de Qt::MatchFlags. Podemos obtener una lista de elementos coincidentes con la función findItems():

const QList<QTreeWidgetItem *> found = treeWidget->findItems(
    itemText, Qt::MatchWildcard);

for (QTreeWidgetItem *item : found) {
    item->setSelected(true);
    // Show the item->text(0) for each item.
}

El código anterior hace que los elementos de un widget de árbol se seleccionen si contienen el texto indicado en la cadena de búsqueda. Este patrón también puede utilizarse en los widgets de lista y tabla.

Uso de la función arrastrar y soltar con las vistas de elementos

La infraestructura de arrastrar y soltar de Qt está totalmente soportada por el framework modelo/vista. Los elementos de listas, tablas y árboles pueden arrastrarse dentro de las vistas, y los datos pueden importarse y exportarse como datos codificados MIME.

Las vistas estándar admiten automáticamente la función interna de arrastrar y soltar, que permite desplazar los elementos para cambiar el orden en que se muestran. Por defecto, la función de arrastrar y soltar no está activada para estas vistas porque están configuradas para los usos más sencillos y comunes. Para permitir el arrastre de elementos, es necesario habilitar ciertas propiedades de la vista, y los propios elementos también deben permitir el arrastre.

Los requisitos de un modelo que sólo permite exportar elementos desde una vista, y que no permite soltar datos en él, son menores que los de un modelo de arrastrar y soltar totalmente habilitado.

Consulte también la Referencia de subclases de modelos para obtener más información sobre cómo activar la función de arrastrar y soltar en los nuevos modelos.

Utilización de vistas de conveniencia

Cada uno de los tipos de elementos utilizados con QListWidget, QTableWidget, y QTreeWidget está configurado para utilizar un conjunto diferente de indicadores por defecto. Por ejemplo, cada QListWidgetItem o QTreeWidgetItem está inicialmente habilitado, se puede marcar, seleccionar y se puede utilizar como origen de una operación de arrastrar y soltar; cada QTableWidgetItem también se puede editar y utilizar como destino de una operación de arrastrar y soltar.

Aunque todos los elementos estándar tienen uno o ambos indicadores establecidos para arrastrar y soltar, generalmente es necesario establecer varias propiedades en la propia vista para aprovechar el soporte incorporado para arrastrar y soltar:

  • Para permitir el arrastre de elementos, establezca la propiedad dragEnabled de la vista en true.
  • Para permitir al usuario soltar elementos internos o externos dentro de la vista, establezca la propiedad acceptDrops de la vista viewport() a true.
  • Para mostrar al usuario dónde se colocará el elemento que está arrastrando en caso de soltarlo, establezca la propiedad showDropIndicator de la vista. Esto proporciona al usuario información continuamente actualizada sobre la colocación del elemento dentro de la vista.

Por ejemplo, podemos activar arrastrar y soltar en un widget de lista con las siguientes líneas de código:

QListWidget *listWidget = new QListWidget(this);
listWidget->setSelectionMode(QAbstractItemView::SingleSelection);
listWidget->setDragEnabled(true);
listWidget->viewport()->setAcceptDrops(true);
listWidget->setDropIndicatorShown(true);

El resultado es un widget de lista que permite copiar los elementos dentro de la vista, e incluso permite al usuario arrastrar elementos entre vistas que contengan el mismo tipo de datos. En ambas situaciones, los elementos se copian en lugar de moverse.

Para que el usuario pueda mover los elementos dentro de la vista, debemos configurar el widget de lista dragDropMode:

listWidget->setDragDropMode(QAbstractItemView::InternalMove);

Uso de clases modelo/vista

La configuración de una vista para arrastrar y soltar sigue el mismo patrón utilizado con las vistas de conveniencia. Por ejemplo, una QListView puede configurarse del mismo modo que una QListWidget:

QListView *listView = new QListView(this);
listView->setSelectionMode(QAbstractItemView::ExtendedSelection);
listView->setDragEnabled(true);
listView->setAcceptDrops(true);
listView->setDropIndicatorShown(true);

Dado que el acceso a los datos mostrados por la vista está controlado por un modelo, el modelo utilizado también tiene que proporcionar soporte para las operaciones de arrastrar y soltar. Las acciones soportadas por un modelo pueden especificarse reimplementando la función QAbstractItemModel::supportedDropActions(). Por ejemplo, las operaciones de copiar y mover se habilitan con el siguiente código:

Qt::DropActions DragDropListModel::supportedDropActions() const
{
    return Qt::CopyAction | Qt::MoveAction;
}

Aunque se puede dar cualquier combinación de valores de Qt::DropActions, es necesario escribir el modelo para que los soporte. Por ejemplo, para permitir que Qt::MoveAction se utilice correctamente con un modelo de lista, el modelo debe proporcionar una implementación de QAbstractItemModel::removeRows(), ya sea directamente o heredando la implementación de su clase base.

Activar la función de arrastrar y soltar elementos

Los modelos indican a las vistas qué elementos se pueden arrastrar y cuáles aceptan soltar, reimplementando la función QAbstractItemModel::flags() para proporcionar los indicadores adecuados.

Por ejemplo, un modelo que proporcione una lista simple basada en QAbstractListModel puede habilitar la función arrastrar y soltar para cada uno de los elementos asegurándose de que los indicadores devueltos contengan los valores Qt::ItemIsDragEnabled y Qt::ItemIsDropEnabled:

Qt::ItemFlags DragDropListModel::flags(const QModelIndex &index) const
{
    Qt::ItemFlags defaultFlags = QStringListModel::flags(index);

    if (index.isValid())
        return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags;
    else
        return Qt::ItemIsDropEnabled | defaultFlags;
}

Tenga en cuenta que los elementos se pueden soltar en el nivel superior del modelo, pero el arrastre sólo está habilitado para los elementos válidos.

En el código anterior, dado que el modelo se deriva de QStringListModel, obtenemos un conjunto predeterminado de banderas llamando a su implementación de la función flags().

Codificación de los datos exportados

Cuando los elementos de datos se exportan desde un modelo en una operación de arrastrar y soltar, se codifican en un formato apropiado correspondiente a uno o más tipos MIME. Los modelos declaran los tipos MIME que pueden utilizar para suministrar elementos reimplementando la función QAbstractItemModel::mimeTypes(), que devuelve una lista de tipos MIME estándar.

Por ejemplo, un modelo que sólo proporciona texto sin formato proporcionaría la siguiente implementación:

QStringList DragDropListModel::mimeTypes() const
{
    QStringList types;
    types << "application/vnd.text.list";
    return types;
}

El modelo también debe proporcionar código para codificar los datos en el formato anunciado. Esto se consigue reimplementando la función QAbstractItemModel::mimeData() para proporcionar un objeto QMimeData, como en cualquier otra operación de arrastrar y soltar.

El siguiente código muestra cómo cada elemento de datos, correspondiente a una lista dada de índices, se codifica como texto sin formato y se almacena en un objeto QMimeData.

QMimeData *DragDropListModel::mimeData(const QModelIndexList &indexes) const
{
    QMimeData *mimeData = new QMimeData;
    QByteArray encodedData;

    QDataStream stream(&encodedData, QIODevice::WriteOnly);

    for (const QModelIndex &index : indexes) {
        if (index.isValid()) {
            QString text = data(index, Qt::DisplayRole).toString();
            stream << text;
        }
    }

    mimeData->setData("application/vnd.text.list", encodedData);
    return mimeData;
}

Dado que se suministra a la función una lista de índices del modelo, este enfoque es lo suficientemente general como para ser utilizado tanto en modelos jerárquicos como no jerárquicos.

Tenga en cuenta que los tipos de datos personalizados deben declararse como meta objects y que deben implementarse operadores de flujo para ellos. Para más detalles, consulte la descripción de la clase QMetaObject.

Inserción de datos eliminados en un modelo

La forma en que un modelo gestiona los datos eliminados depende tanto de su tipo (lista, tabla o árbol) como de la forma en que su contenido se presenta al usuario. En general, el enfoque adoptado para acomodar los datos eliminados debe ser el que mejor se adapte al almacén de datos subyacente del modelo.

Los distintos tipos de modelos tienden a tratar los datos eliminados de formas diferentes. Los modelos de listas y tablas sólo proporcionan una estructura plana en la que se almacenan los datos. En consecuencia, pueden insertar nuevas filas (y columnas) cuando se eliminan datos de un elemento existente en una vista, o pueden sobrescribir el contenido del elemento en el modelo utilizando algunos de los datos suministrados. Los modelos de árbol suelen ser capaces de añadir elementos hijos que contengan nuevos datos a sus almacenes de datos subyacentes, por lo que se comportarán de forma más predecible para el usuario.

Los datos eliminados se gestionan mediante la reimplementación de QAbstractItemModel::dropMimeData() en un modelo. Por ejemplo, un modelo que maneje una lista simple de cadenas puede proporcionar una implementación que maneje los datos soltados en elementos existentes de forma separada a los datos soltados en el nivel superior del modelo (es decir, en un elemento no válido).

Los modelos pueden prohibir la colocación de datos en determinados elementos, o en función de los datos eliminados, reimplementando QAbstractItemModel::canDropMimeData().

En primer lugar, el modelo debe asegurarse de que la operación debe realizarse, de que los datos suministrados están en un formato que puede utilizarse y de que su destino dentro del modelo es válido:

bool DragDropListModel::canDropMimeData(const QMimeData *data,
    Qt::DropAction action, int row, int column, const QModelIndex &parent) const
{
    Q_UNUSED(action);
    Q_UNUSED(row);
    Q_UNUSED(parent);

    if (!data->hasFormat("application/vnd.text.list"))
        return false;

    if (column > 0)
        return false;

    return true;
}
bool DragDropListModel::dropMimeData(const QMimeData *data,
    Qt::DropAction action, int row, int column, const QModelIndex &parent)
{
    if (!canDropMimeData(data, action, row, column, parent))
        return false;

    if (action == Qt::IgnoreAction)
        return true;

Un simple modelo de lista de cadenas de una columna puede indicar un fallo si los datos suministrados no son texto plano, o si el número de columna dado para la caída no es válido.

Los datos que se insertan en el modelo se tratan de forma diferente dependiendo de si se sueltan sobre un elemento existente o no. En este sencillo ejemplo, deseamos permitir la inserción de datos entre elementos existentes, antes del primer elemento de la lista y después del último.

Cuando se produce un drop, el índice del modelo correspondiente al ítem padre será válido, indicando que el drop se produjo sobre un ítem, o será inválido, indicando que el drop se produjo en algún lugar de la vista que corresponde al nivel superior del modelo.

int beginRow;

if (row != -1)
    beginRow = row;

Inicialmente examinamos el número de fila suministrado para ver si podemos utilizarlo para insertar elementos en el modelo, independientemente de si el índice padre es válido o no.

else if (parent.isValid())
    beginRow = parent.row();

Si el índice del modelo padre es válido, la caída se produjo en un elemento. En este modelo de lista simple, averiguamos el número de fila del elemento y utilizamos ese valor para insertar los elementos eliminados en el nivel superior del modelo.

else
    beginRow = rowCount(QModelIndex());

Cuando se produce una caída en otra parte de la vista, y el número de fila no es utilizable, añadimos elementos al nivel superior del modelo.

En los modelos jerárquicos, cuando se produce una caída en un elemento, sería mejor insertar nuevos elementos en el modelo como hijos de ese elemento. En el ejemplo sencillo que se muestra aquí, el modelo sólo tiene un nivel, por lo que este enfoque no es apropiado.

Descodificación de datos importados

Cada implementación de dropMimeData() también debe descodificar los datos e insertarlos en la estructura de datos subyacente del modelo.

Para un modelo simple de lista de cadenas, los elementos codificados pueden descodificarse y transmitirse a QStringList:

QByteArray encodedData = data->data("application/vnd.text.list");
QDataStream stream(&encodedData, QIODevice::ReadOnly);
QStringList newItems;
int rows = 0;

while (!stream.atEnd()) {
    QString text;
    stream >> text;
    newItems << text;
    ++rows;
}

A continuación, las cadenas pueden insertarse en el almacén de datos subyacente. En aras de la coherencia, esto puede hacerse a través de la propia interfaz del modelo:

    insertRows(beginRow, rows, QModelIndex());
    for (const QString &text : std::as_const(newItems)) {
        QModelIndex idx = index(beginRow, 0, QModelIndex());
        setData(idx, text);
        beginRow++;
    }

    return true;
}

Tenga en cuenta que el modelo normalmente tendrá que proporcionar implementaciones de las funciones QAbstractItemModel::insertRows() y QAbstractItemModel::setData().

Modelos proxy

En el marco modelo/vista, los elementos de datos suministrados por un único modelo pueden ser compartidos por cualquier número de vistas, y es posible que cada una de ellas represente la misma información de formas completamente distintas. Las vistas personalizadas y los delegados son formas eficaces de proporcionar representaciones radicalmente diferentes de los mismos datos. Sin embargo, las aplicaciones a menudo necesitan proporcionar vistas convencionales sobre versiones procesadas de los mismos datos, como vistas ordenadas de forma diferente sobre una lista de elementos.

Aunque parece apropiado realizar operaciones de ordenación y filtrado como funciones internas de las vistas, este enfoque no permite que varias vistas compartan los resultados de estas operaciones potencialmente costosas. El enfoque alternativo, que implica la ordenación dentro del propio modelo, conduce a un problema similar en el que cada vista tiene que mostrar elementos de datos que están organizados de acuerdo con la operación de procesamiento más reciente.

Para resolver este problema, el marco modelo/vista utiliza modelos proxy para gestionar la información suministrada entre los modelos individuales y las vistas. Los modelos proxy son componentes que se comportan como modelos ordinarios desde la perspectiva de una vista, y acceden a los datos de los modelos fuente en nombre de esa vista. Las señales y ranuras utilizadas por el marco modelo/vista garantizan que cada vista se actualice de forma adecuada, independientemente del número de modelos proxy que haya entre ella y el modelo de origen.

Utilización de modelos proxy

Los modelos proxy pueden insertarse entre un modelo existente y cualquier número de vistas. Qt se suministra con un modelo proxy estándar, QSortFilterProxyModel, que suele instanciarse y utilizarse directamente, pero que también puede subclasificarse para proporcionar un comportamiento de filtrado y ordenación personalizado. La clase QSortFilterProxyModel puede utilizarse de la siguiente manera:

QSortFilterProxyModel *filterModel = new QSortFilterProxyModel(parent);
filterModel->setSourceModel(stringListModel);

QListView *filteredView = new QListView;
filteredView->setModel(filterModel);

Dado que los modelos proxy heredan de QAbstractItemModel, pueden conectarse a cualquier tipo de vista, y pueden compartirse entre vistas. También pueden utilizarse para procesar la información obtenida de otros modelos proxy en una disposición de canalización.

La clase QSortFilterProxyModel está diseñada para ser instanciada y utilizada directamente en las aplicaciones. Se pueden crear modelos proxy más especializados subclasificando esta clase e implementando las operaciones de comparación necesarias.

Personalización de los modelos proxy

Por lo general, el tipo de procesamiento utilizado en un modelo proxy implica la asignación de cada elemento de datos desde su ubicación original en el modelo fuente a una ubicación diferente en el modelo proxy. En algunos modelos, algunos elementos pueden no tener una ubicación correspondiente en el modelo proxy; estos modelos son modelos proxy de filtrado. Las vistas acceden a los elementos utilizando índices de modelo proporcionados por el modelo proxy, y éstos no contienen información sobre el modelo fuente o las ubicaciones de los elementos originales en ese modelo.

QSortFilterProxyModel permite filtrar los datos de un modelo fuente antes de suministrarlos a las vistas, y también permite suministrar el contenido de un modelo fuente a las vistas como datos preclasificados.

Modelos de filtrado personalizados

La clase QSortFilterProxyModel proporciona un modelo de filtrado que es bastante versátil, y que puede utilizarse en una variedad de situaciones comunes. Para usuarios avanzados, QSortFilterProxyModel puede subclasificarse, proporcionando un mecanismo que permite implementar filtros personalizados.

Las subclases de QSortFilterProxyModel pueden reimplementar dos funciones virtuales que se llaman cada vez que se solicita o utiliza un índice del modelo proxy:

  • filterAcceptsColumn() se utiliza para filtrar columnas específicas de una parte del modelo fuente.
  • filterAcceptsRow() se utiliza para filtrar filas específicas de una parte del modelo fuente.

Las implementaciones predeterminadas de las funciones anteriores en QSortFilterProxyModel devuelven true para garantizar que todos los elementos se pasan a las vistas; las reimplementaciones de estas funciones deben devolver false para filtrar filas y columnas individuales.

Modelos de ordenación personalizados

QSortFilterProxyModel Las instancias utilizan la función std::stable_sort() para establecer correspondencias entre los elementos del modelo fuente y los del modelo proxy, lo que permite exponer una jerarquía ordenada de elementos a las vistas sin modificar la estructura del modelo fuente. Para proporcionar un comportamiento de ordenación personalizado, reimplemente la función lessThan() para realizar comparaciones personalizadas.

Referencia de subclases de modelos

Las subclases de modelos necesitan proporcionar implementaciones de muchas de las funciones virtuales definidas en la clase base QAbstractItemModel. El número de estas funciones que deben implementarse depende del tipo de modelo: si proporciona vistas con una lista simple, una tabla o una jerarquía compleja de elementos. Los modelos que heredan de QAbstractListModel y QAbstractTableModel pueden aprovechar las implementaciones por defecto de las funciones proporcionadas por esas clases. Los modelos que exponen elementos de datos en estructuras arborescentes deben proporcionar implementaciones para muchas de las funciones virtuales de QAbstractItemModel.

Las funciones que deben implementarse en una subclase de modelo pueden dividirse en tres grupos:

  • Gestión de datos de elementos: Todos los modelos deben implementar funciones que permitan a las vistas y delegados consultar las dimensiones del modelo, examinar elementos y recuperar datos.
  • Navegación y creación de índices: Los modelos jerárquicos deben proporcionar funciones a las que las vistas puedan llamar para navegar por las estructuras arborescentes que exponen y obtener índices del modelo para los elementos.
  • Arrastrar y soltar y gestión de tipos MIME: Los modelos heredan funciones que controlan la forma en que se realizan las operaciones internas y externas de arrastrar y soltar. Estas funciones permiten que los elementos de datos se describan en términos de tipos MIME que otros componentes y aplicaciones puedan entender.

Gestión de datos de elementos

Los modelos pueden ofrecer distintos niveles de acceso a los datos que proporcionan: Pueden ser simples componentes de sólo lectura, algunos modelos pueden soportar operaciones de redimensionamiento y otros pueden permitir la edición de elementos.

Acceso de sólo lectura

Para proporcionar acceso de sólo lectura a los datos proporcionados por un modelo, deben implementarse las siguientes funciones en la subclase del modelo:

flags()Utilizada por otros componentes para obtener información sobre cada elemento proporcionado por el modelo. En muchos modelos, la combinación de indicadores debe incluir Qt::ItemIsEnabled y Qt::ItemIsSelectable.
data()Utilizado para suministrar datos de elementos a vistas y delegados. Generalmente, los modelos sólo necesitan proporcionar datos para Qt::DisplayRole y cualquier rol de usuario específico de la aplicación, pero también es una buena práctica proporcionar datos para Qt::ToolTipRole, Qt::AccessibleTextRole, y Qt::AccessibleDescriptionRole. Consulte la documentación del enum Qt::ItemDataRole para obtener información sobre los tipos asociados a cada rol.
headerData()Proporciona a las vistas información para mostrar en sus cabeceras. La información sólo es recuperada por las vistas que pueden mostrar información de cabecera.
rowCount()Proporciona el número de filas de datos expuestos por el modelo.

Estas cuatro funciones deben implementarse en todos los tipos de modelo, incluidos los modelos de lista (subclases deQAbstractListModel ) y los modelos de tabla (subclases deQAbstractTableModel ).

Además, las siguientes funciones deben implementarse en las subclases directas de QAbstractTableModel y QAbstractItemModel:

columnCount()Proporciona el número de columnas de datos expuestas por el modelo. Los modelos de lista no proporcionan esta función porque ya está implementada en QAbstractListModel.

Elementos editables

Los modelos editables permiten modificar elementos de datos, y también pueden proporcionar funciones que permitan insertar y eliminar filas y columnas. Para permitir la edición, las siguientes funciones deben implementarse correctamente:

flags()Debe devolver una combinación adecuada de indicadores para cada elemento. En particular, el valor devuelto por esta función debe incluir Qt::ItemIsEditable además de los valores aplicados a los elementos en un modelo de sólo lectura.
setData()Se utiliza para modificar el elemento de datos asociado a un índice de modelo especificado. Para poder aceptar entradas del usuario, proporcionadas por elementos de la interfaz de usuario, esta función debe manejar datos asociados con Qt::EditRole. La implementación también puede aceptar datos asociados con muchos tipos diferentes de roles especificados por Qt::ItemDataRole. Después de cambiar el elemento de datos, los modelos deben emitir la señal dataChanged() para informar a otros componentes del cambio.
setHeaderData()Se utiliza para modificar la información de cabecera horizontal y vertical. Después de modificar el elemento de datos, los modelos deben emitir la señal headerDataChanged() para informar a otros componentes del cambio.

Modelos redimensionables

Todos los tipos de modelos pueden admitir la inserción y eliminación de filas. Los modelos de tabla y los modelos jerárquicos también pueden admitir la inserción y eliminación de columnas. Es importante notificar a los demás componentes los cambios en las dimensiones del modelo antes y después de que se produzcan. Por ello, se pueden implementar las siguientes funciones para permitir que el modelo cambie de tamaño, pero las implementaciones deben asegurarse de que se llaman las funciones apropiadas para notificar a las vistas y delegados adjuntos:

insertRows()Se utiliza para añadir nuevas filas y elementos de datos a todos los tipos de modelo. Las implementaciones deben llamar a beginInsertRows() antes de insertar nuevas filas en cualquier estructura de datos subyacente, y llamar a endInsertRows() inmediatamente después.
removeRows()Se utiliza para eliminar filas y los elementos de datos que contienen de todos los tipos de modelo. Las implementaciones deben llamar a beginRemoveRows() antes de eliminar filas de cualquier estructura de datos subyacente, y llamar a endRemoveRows() inmediatamente después.
insertColumns()Se utiliza para añadir nuevas columnas y elementos de datos a modelos de tablas y modelos jerárquicos. Las implementaciones deben llamar a beginInsertColumns() antes de insertar nuevas columnas en cualquier estructura de datos subyacente, y llamar a endInsertColumns() inmediatamente después.
removeColumns()Se utiliza para eliminar columnas y los elementos de datos que contienen de los modelos de tabla y los modelos jerárquicos. Las implementaciones deben llamar a beginRemoveColumns() antes de eliminar columnas de cualquier estructura de datos subyacente, y llamar a endRemoveColumns() inmediatamente después.

Por lo general, estas funciones deben devolver true si la operación se ha realizado correctamente. Sin embargo, puede haber casos en los que la operación sólo haya tenido éxito parcialmente; por ejemplo, si se ha podido insertar un número de filas inferior al especificado. En tales casos, el modelo debe devolver false para indicar el fallo y permitir que cualquier componente adjunto pueda manejar la situación.

Las señales emitidas por las funciones a las que se llama en las implementaciones de la API de redimensionamiento dan a los componentes adjuntos la oportunidad de actuar antes de que los datos dejen de estar disponibles. La encapsulación de las operaciones de inserción y eliminación con funciones de inicio y fin también permite al modelo gestionar correctamente persistent model indexes.

Normalmente, las funciones begin y end son capaces de informar a otros componentes sobre cambios en la estructura subyacente del modelo. Para cambios más complejos en la estructura del modelo, que quizás impliquen una reorganización interna, una ordenación de los datos o cualquier otro cambio estructural, es necesario realizar la siguiente secuencia:

Esta secuencia puede utilizarse para cualquier actualización estructural en lugar de los métodos protegidos, más cómodos y de alto nivel. Por ejemplo, si un modelo de dos millones de filas necesita que se eliminen todas las filas impares, es decir, 1 millón de rangos discontinuos de 1 elemento cada uno. Sería posible utilizar beginRemoveRows y endRemoveRows 1 millón de veces, pero eso sería obviamente ineficiente. En su lugar, esto puede ser señalado como un único cambio de diseño que actualiza todos los índices persistentes necesarios a la vez.

Población perezosa de datos modelo

La población perezosa de datos del modelo permite que las peticiones de información sobre el modelo se aplacen hasta que las vistas la necesiten.

Algunos modelos necesitan obtener datos de fuentes remotas, o deben realizar operaciones que requieren mucho tiempo para obtener información sobre la forma en que están organizados los datos. Dado que las vistas suelen solicitar tanta información como sea posible para mostrar con precisión los datos del modelo, puede ser útil restringir la cantidad de información que se les devuelve para reducir las solicitudes de seguimiento de datos innecesarias.

En los modelos jerárquicos en los que encontrar el número de hijos de un elemento dado es una operación costosa, es útil asegurarse de que la implementación rowCount() del modelo sólo se llama cuando es necesario. En tales casos, la función hasChildren() puede reimplementarse para proporcionar una forma económica de que las vistas comprueben la presencia de hijos y, en el caso de QTreeView, dibujen la decoración apropiada para su elemento padre.

Tanto si la reimplementación de hasChildren() devuelve true como false, puede que no sea necesario que la vista llame a rowCount() para averiguar cuántos hijos hay presentes. Por ejemplo, QTreeView no necesita saber cuántos hijos hay si el elemento padre no se ha expandido para mostrarlos.

Si se sabe que muchos elementos tendrán hijos, reimplementar hasChildren() para que devuelva incondicionalmente true es a veces un enfoque útil. Esto garantiza que cada elemento pueda ser examinado posteriormente en busca de hijos, a la vez que hace que la población inicial de datos del modelo sea lo más rápida posible. La única desventaja es que los elementos sin hijos pueden mostrarse incorrectamente en algunas vistas hasta que el usuario intente ver los elementos hijos inexistentes.

Los modelos jerárquicos deben proporcionar funciones a las que las vistas puedan llamar para navegar por las estructuras arborescentes que exponen y obtener índices del modelo para los elementos.

Padres e hijos

Dado que la estructura expuesta a las vistas viene determinada por la estructura de datos subyacente, corresponde a cada subclase del modelo crear sus propios índices del modelo proporcionando implementaciones de las siguientes funciones:

index()Dado un índice de modelo para un elemento padre, esta función permite a las vistas y delegados acceder a los hijos de ese elemento. Si no se encuentra ningún elemento hijo válido que corresponda a la fila, columna e índice de modelo padre especificados, la función debe devolver QModelIndex(), que es un índice de modelo no válido.
parent()Proporciona un índice de modelo correspondiente al elemento padre de cualquier elemento hijo dado. Si el índice de modelo especificado corresponde a un elemento de nivel superior en el modelo, o si no hay ningún elemento padre válido en el modelo, la función debe devolver un índice de modelo no válido, creado con el constructor vacío QModelIndex().

Las dos funciones anteriores utilizan la función de fábrica createIndex() para generar índices que puedan utilizar otros componentes. Es normal que los modelos proporcionen algún identificador único a esta función para garantizar que el índice del modelo pueda volver a asociarse con su elemento correspondiente más adelante.

Arrastrar y soltar y gestión de tipos MIME

Las clases model/view soportan operaciones de arrastrar y soltar, proporcionando un comportamiento por defecto que es suficiente para muchas aplicaciones. Sin embargo, también es posible personalizar la forma en que se codifican los elementos durante las operaciones de arrastrar y soltar, si se copian o mueven por defecto, y cómo se insertan en los modelos existentes.

Además, las clases de vistas de conveniencia implementan un comportamiento especializado que debería seguir de cerca el esperado por los desarrolladores existentes. La sección Vistas de conveniencia proporciona una visión general de este comportamiento.

Datos MIME

Por defecto, los modelos y vistas incorporados utilizan un tipo MIME interno (application/x-qabstractitemmodeldatalist) para pasar información sobre índices de modelos. Esto especifica datos para una lista de elementos, conteniendo los números de fila y columna de cada elemento, e información sobre los roles que cada elemento soporta.

Los datos codificados utilizando este tipo MIME pueden obtenerse llamando a QAbstractItemModel::mimeData() con un QModelIndexList que contenga los elementos a serializar.

Al implementar el soporte de arrastrar y soltar en un modelo personalizado, es posible exportar elementos de datos en formatos especializados reimplementando la siguiente función:

mimeData()Esta función puede reimplementarse para devolver datos en formatos distintos del tipo MIME interno predeterminado application/x-qabstractitemmodeldatalist.

Las subclases pueden obtener el objeto QMimeData por defecto de la clase base y añadirle datos en formatos adicionales.

Para muchos modelos, es útil proporcionar el contenido de los elementos en un formato común representado por tipos MIME como text/plain y image/png. Tenga en cuenta que las imágenes, los colores y los documentos HTML pueden añadirse fácilmente a un objeto QMimeData con las funciones QMimeData::setImageData(), QMimeData::setColorData() y QMimeData::setHtml().

Aceptar datos soltados

Cuando se realiza una operación de arrastrar y soltar sobre una vista, se consulta el modelo subyacente para determinar qué tipos de operación admite y los tipos MIME que puede aceptar. Esta información la proporcionan las funciones QAbstractItemModel::supportedDropActions() y QAbstractItemModel::mimeTypes(). Los modelos que no sobrescriben las implementaciones proporcionadas por QAbstractItemModel admiten operaciones de copia y el tipo MIME interno predeterminado para los elementos.

Cuando los datos serializados de un elemento se sueltan en una vista, los datos se insertan en el modelo actual utilizando su implementación de QAbstractItemModel::dropMimeData(). La implementación predeterminada de esta función nunca sobrescribirá ningún dato del modelo; en su lugar, intenta insertar los elementos de datos como hermanos de un elemento o como hijos de ese elemento.

Para aprovechar la implementación por defecto de QAbstractItemModel para el tipo MIME incorporado, los nuevos modelos deben proporcionar reimplementaciones de las siguientes funciones:

insertRows()Estas funciones permiten al modelo insertar automáticamente nuevos datos utilizando la implementación existente proporcionada por QAbstractItemModel::dropMimeData().
insertColumns()
setData()Permite rellenar las nuevas filas y columnas con elementos.
setItemData()Esta función proporciona un soporte más eficiente para rellenar nuevos elementos.

Para aceptar otras formas de datos, estas funciones deben ser reimplementadas:

supportedDropActions()Se utiliza para devolver una combinación de drop actions, indicando los tipos de operaciones de arrastrar y soltar que acepta el modelo.
mimeTypes()Se utiliza para devolver una lista de tipos MIME que pueden ser descodificados y manejados por el modelo. Por lo general, los tipos MIME que se admiten para la entrada en el modelo son los mismos que puede utilizar cuando codifica datos para su uso por componentes externos.
dropMimeData()Realiza la descodificación real de los datos transferidos mediante operaciones de arrastrar y soltar, determina en qué parte del modelo se establecerán e inserta nuevas filas y columnas cuando es necesario. La forma de implementar esta función en las subclases depende de los requisitos de los datos expuestos por cada modelo.

Si la implementación de la función dropMimeData() cambia las dimensiones de un modelo insertando o eliminando filas o columnas, o si se modifican elementos de datos, hay que tener cuidado para asegurarse de que se emiten todas las señales pertinentes. Puede ser útil llamar simplemente a reimplementaciones de otras funciones de la subclase, como setData(), insertRows() y insertColumns(), para asegurarse de que el modelo se comporta de forma coherente.

Para garantizar que las operaciones de arrastre funcionan correctamente, es importante reimplementar las siguientes funciones que eliminan datos del modelo:

Para obtener más información sobre la función de arrastrar y soltar con vistas de elementos, consulte Uso de la función de arrastrar y soltar con vistas de elementos.

Vistas de conveniencia

Las vistas de conveniencia (QListWidget, QTableWidget, y QTreeWidget) anulan la funcionalidad predeterminada de arrastrar y soltar para proporcionar un comportamiento menos flexible, pero más natural, que resulta adecuado para muchas aplicaciones. Por ejemplo, dado que es más habitual soltar datos en las celdas de QTableWidget, sustituyendo el contenido existente por los datos que se transfieren, el modelo subyacente establecerá los datos de los elementos de destino en lugar de insertar nuevas filas y columnas en el modelo. Para obtener más información sobre la función de arrastrar y soltar en vistas de conveniencia, puede consultar Uso de la función de arrastrar y soltar con vistas de elementos.

Optimización del rendimiento para grandes cantidades de datos

La función canFetchMore() comprueba si el padre tiene más datos disponibles y devuelve true o false en consecuencia. La función fetchMore() obtiene los datos basándose en el padre especificado. Ambas funciones pueden combinarse, por ejemplo, en una consulta a una base de datos que incluya datos incrementales para rellenar un modelo QAbstractItemModel. Reimplementamos canFetchMore() para indicar si hay más datos que obtener y fetchMore() para rellenar el modelo según sea necesario.

Otro ejemplo serían los modelos de árbol poblados dinámicamente, en los que reimplementamos fetchMore() cuando se expande una rama del modelo de árbol.

Si tu reimplementación de fetchMore() añade filas al modelo, necesitas llamar a beginInsertRows() y endInsertRows(). Además, tanto canFetchMore() como fetchMore() deben reimplementarse, ya que su implementación por defecto devuelve false y no hace nada.

Las clases Modelo/Vista

Estas clases utilizan el patrón de diseño modelo/vista en el que los datos subyacentes (en el modelo) se mantienen separados de la forma en que los datos son presentados y manipulados por el usuario (en la vista).

QAbstractItemDelegate

Se utilizan para mostrar y editar elementos de datos de un modelo.

QAbstractItemModel

La interfaz abstracta de las clases del modelo de elementos

QAbstractItemView

Funcionalidad básica de las clases de vista de elementos

QAbstractListModel

Modelo abstracto que puede subclasificarse para crear modelos de listas unidimensionales

QAbstractProxyModel

Clase base para modelos de elementos proxy que pueden ordenar, filtrar u otras tareas de procesamiento de datos

QAbstractTableModel

Modelo abstracto que puede subclasificarse para crear modelos de tabla

QColumnView

Implementación modelo/vista de una vista de columna

QConcatenateTablesProxyModel

Proxy de múltiples modelos de origen, concatenando sus filas

QDataWidgetMapper

Mapeo entre una sección de un modelo de datos y los widgets

QFileSystemModel

Modelo de datos para el sistema de archivos local

QHeaderView

Fila de cabecera o columna de cabecera para vistas de elementos

QIdentityProxyModel

Proxy de su modelo de origen sin modificar

QItemDelegate

Facilidades de visualización y edición de elementos de datos de un modelo

QItemEditorCreator

Permite crear bases creadoras de editores de ítems sin subclase QItemEditorCreatorBase

QItemEditorCreatorBase

Clase base abstracta que debe subclasificarse al implementar nuevos creadores de editores de elementos

QItemEditorFactory

Widgets para editar datos de elementos en vistas y delegados

QItemSelection

Gestiona la información sobre los elementos seleccionados en un modelo

QItemSelectionModel

Realiza un seguimiento de los elementos seleccionados en una vista

QItemSelectionRange

Gestiona información sobre un rango de elementos seleccionados en un modelo

QListView

Vista de lista o icono en un modelo

QListWidget

Widget de lista basado en elementos

QListWidgetItem

Elemento para utilizar con la clase de vista de elementos QListWidget

QModelIndex

Se utiliza para localizar datos en un modelo de datos

QModelRoleData

Contiene un rol y los datos asociados a ese rol

QModelRoleDataSpan

Se extiende sobre objetos QModelRoleData

QPersistentModelIndex

Utilizado para localizar datos en un modelo de datos

QRangeModel

Implementa QAbstractItemModel para cualquier rango C++

QRangeModel::ItemAccess

La plantilla proporciona un punto de personalización para controlar cómo QRangeModel accede a los datos de rol de elementos individuales

QRangeModel::RowOptions

La plantilla proporciona un punto de personalización para controlar cómo QRangeModel representa los tipos utilizados como filas

QRangeModelAdapter

Acceso compatible con QAbstractItemModel a cualquier rango C++

QSortFilterProxyModel

Soporte para ordenar y filtrar datos pasados entre otro modelo y una vista

QStandardItem

Elemento para utilizar con la clase QStandardItemModel

QStandardItemEditorCreator

Posibilidad de registrar widgets sin tener que subclasificar QItemEditorCreatorBase

QStandardItemModel

Modelo genérico para almacenar datos personalizados

QStringListModel

Modelo que suministra cadenas a las vistas

QStyledItemDelegate

Facilidades de visualización y edición de elementos de datos de un modelo

QTableView

Implementación modelo/vista por defecto de una vista de tabla

QTableWidget

Vista de tabla basada en ítems con un modelo por defecto

QTableWidgetItem

Elemento para utilizar con la clase QTableWidget

QTableWidgetSelectionRange

Manera de interactuar con la selección en un modelo sin utilizar índices de modelo y un modelo de selección

QTreeView

Implementación modelo/vista por defecto de una vista de árbol

QTreeWidget

Vista de árbol que utiliza un modelo de árbol predefinido

QTreeWidgetItem

Elemento para utilizar con la clase de conveniencia QTreeWidget

QTreeWidgetItemIterator

Manera de iterar sobre los ítems en una instancia QTreeWidget

© 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.