Ejemplo de desglose
El ejemplo Drill Down muestra cómo leer datos de una base de datos así como enviar cambios, utilizando las clases QSqlRelationalTableModel y QDataWidgetMapper.

Al ejecutar la aplicación de ejemplo, un usuario puede recuperar información sobre cada elemento haciendo clic en la imagen correspondiente. La aplicación abre una ventana de información que muestra los datos y permite a los usuarios modificar la descripción y la imagen. La vista principal se actualizará cuando los usuarios envíen sus cambios.
El ejemplo consta de tres clases:
ImageItemes una clase de elemento gráfico personalizado que se utiliza para mostrar las imágenes.Viewes el widget principal de la aplicación que permite al usuario navegar por los distintos elementos.InformationWindowmuestra la información solicitada, permitiendo a los usuarios modificarla y enviar sus cambios a la base de datos.
Primero echaremos un vistazo a la clase InformationWindow para ver cómo se pueden leer y modificar datos de una base de datos. Después revisaremos el widget principal de la aplicación, es decir, la clase View, y la clase asociada ImageItem.
Definición de la clase InformationWindow
La clase InformationWindow es un widget personalizado que hereda de QWidget:
class InformationWindow : public QDialog { Q_OBJECT public: InformationWindow(int id, QSqlRelationalTableModel *items, QWidget *parent = nullptr); int id() const; Q_SIGNALS: void imageChanged(int id, const QString &fileName);
Cuando creamos una ventana de información, pasamos el ID del elemento asociado, un puntero al modelo y un padre al constructor. Utilizaremos el puntero al modelo para rellenar nuestra ventana con datos, mientras pasamos el parámetro padre a la clase base. El ID se almacena para futuras referencias.
Una vez creada la ventana, utilizaremos la función pública id() para localizarla siempre que se solicite información sobre la ubicación dada. También utilizaremos el ID para actualizar el widget principal de la aplicación cuando los usuarios envíen sus cambios a la base de datos, es decir, emitiremos una señal con el ID y el nombre del archivo como parámetros cada vez que los usuarios cambien la imagen asociada.
private Q_SLOTS: void revert(); void submit(); void enableButtons(bool enable);
Dado que permitimos a los usuarios alterar algunos de los datos, debemos proporcionar una funcionalidad para revertir y enviar sus cambios. La ranura enableButtons() se proporciona por conveniencia para activar y desactivar los distintos botones cuando sea necesario.
private: void createButtons(); int itemId; QString displayedImage; QComboBox *imageFileEditor = nullptr; QLabel *itemText = nullptr; QTextEdit *descriptionEditor = nullptr; QPushButton *closeButton = nullptr; QPushButton *submitButton = nullptr; QPushButton *revertButton = nullptr; QDialogButtonBox *buttonBox = nullptr; QDataWidgetMapper *mapper = nullptr; };
La función createButtons() es también una función de conveniencia, proporcionada para simplificar el constructor. Como se mencionó anteriormente, almacenamos el ID del elemento para futuras referencias. También almacenamos el nombre del archivo de imagen mostrado actualmente para poder determinar cuándo emitir la señal imageChanged().
La ventana de información utiliza la clase QLabel para mostrar el nombre de un elemento. El archivo de imagen asociado se muestra mediante una instancia de QComboBox, mientras que la descripción se muestra mediante QTextEdit. Además, la ventana tiene tres botones para controlar el flujo de datos y si la ventana se muestra o no.
Por último, declaramos un mapeador. La clase QDataWidgetMapper proporciona un mapeo entre una sección de un modelo de datos y los widgets. Utilizaremos el mapeador para extraer datos de la base de datos dada, actualizando la base de datos cada vez que el usuario modifique los datos.
Implementación de la clase InformationWindow
El constructor toma tres argumentos: un ID de elemento, un puntero a la base de datos y un widget padre. El puntero a la base de datos es en realidad un puntero a un objeto QSqlRelationalTableModel que proporciona un modelo de datos editable (con soporte de claves externas) para nuestra tabla de base de datos.
InformationWindow::InformationWindow(int id, QSqlRelationalTableModel *items, QWidget *parent) : QDialog(parent) { QLabel *itemLabel = new QLabel(tr("Item:")); QLabel *descriptionLabel = new QLabel(tr("Description:")); QLabel *imageFileLabel = new QLabel(tr("Image file:")); createButtons(); itemText = new QLabel; descriptionEditor = new QTextEdit;
Primero creamos los distintos widgets necesarios para mostrar los datos contenidos en la base de datos. La mayoría de los widgets se crean de forma sencilla. Pero observe el combobox que muestra el nombre del archivo de imagen:
imageFileEditor = new QComboBox; imageFileEditor->setModel(items->relationModel(1)); imageFileEditor->setModelColumn(items->relationModel(1)->fieldIndex("file"));
En este ejemplo, la información sobre los artículos se almacena en una tabla de la base de datos llamada "artículos". Al crear el modelo, utilizaremos una clave externa para establecer una relación entre esta tabla y una segunda tabla de la base de datos, "imágenes", que contiene los nombres de los archivos de imagen disponibles. Volveremos a ver cómo se hace esto cuando revisemos la clase View. La razón para crear esta relación es que queremos asegurarnos de que el usuario sólo pueda elegir entre archivos de imagen predefinidos.
El modelo correspondiente a la tabla "images" de la base de datos está disponible a través de la función relationModel() de QSqlRelationalTableModel, que requiere la clave externa (en este caso, el número de columna "imagefile") como argumento. Utilizamos la función QComboBox's setModel() para que el combobox utilice el modelo "images". Y, como este modelo tiene dos columnas ("itemid" y "file"), también especificamos qué columna queremos que sea visible utilizando la función QComboBox::setModelColumn().
mapper = new QDataWidgetMapper(this); mapper->setModel(items); mapper->setSubmitPolicy(QDataWidgetMapper::ManualSubmit); mapper->setItemDelegate(new QSqlRelationalDelegate(mapper)); mapper->addMapping(imageFileEditor, 1); mapper->addMapping(itemText, 2, "text"); mapper->addMapping(descriptionEditor, 3); mapper->setCurrentIndex(id);
A continuación, creamos el mapper. La clase QDataWidgetMapper nos permite crear widgets conscientes de los datos asignándolos a secciones de un modelo de artículos.
La función addMapping() añade un mapeo entre el widget dado y la sección especificada del modelo. Si la orientación del mapeador es horizontal (por defecto), la sección es una columna del modelo; en caso contrario, es una fila. Llamamos a la función setCurrentIndex() para inicializar los widgets con los datos asociados al ID del elemento dado. Cada vez que cambia el índice actual, todos los widgets se actualizan con el contenido del modelo.
También establecemos la política de envío del mapeador en QDataWidgetMapper::ManualSubmit. Esto significa que no se envían datos a la base de datos hasta que el usuario solicite explícitamente un envío (la alternativa es QDataWidgetMapper::AutoSubmit, que envía automáticamente los cambios cuando el widget correspondiente pierde el foco). Por último, especificamos el elemento delegado que la vista del mapeador debe utilizar para sus elementos. La clase QSqlRelationalDelegate representa un delegado que, a diferencia del delegado por defecto, permite la funcionalidad combobox para campos que son claves externas en otras tablas (como "imagefile" en nuestra tabla "items").
connect(descriptionEditor, &QTextEdit::textChanged, this, [this]() { enableButtons(true); }); connect(imageFileEditor, &QComboBox::currentIndexChanged, this, [this]() { enableButtons(true); }); QFormLayout *formLayout = new QFormLayout; formLayout->addRow(itemLabel, itemText); formLayout->addRow(imageFileLabel, imageFileEditor); formLayout->addRow(descriptionLabel, descriptionEditor); QVBoxLayout *layout = new QVBoxLayout; layout->addLayout(formLayout); layout->addWidget(buttonBox); setLayout(layout); itemId = id; displayedImage = imageFileEditor->currentText(); setWindowFlags(Qt::Window); enableButtons(false); setWindowTitle(itemText->text()); }
Por último, conectamos las señales de "algo ha cambiado" en los editores a nuestra ranura personalizada enableButtons, permitiendo a los usuarios enviar o revertir sus cambios. Necesitamos usar lambdas para conectar la ranura enableButtons porque su firma no coincide con QTextEdit::textChanged y QComboBox::currentIndexChanged.
Añadimos todos los widgets a un diseño, almacenamos el ID del elemento y el nombre del archivo de imagen mostrado para futuras referencias, y establecemos el título de la ventana y el tamaño inicial.
Tenga en cuenta que también establecemos la bandera de ventana Qt::Window para indicar que nuestro widget es de hecho una ventana, con un marco de sistema de ventana y una barra de título.
int InformationWindow::id() const { return itemId; }
Cuando se crea una ventana, no se elimina hasta que la aplicación principal sale (es decir, si el usuario cierra la ventana de información, sólo se oculta). Por esta razón no queremos crear más de un objeto InformationWindow para cada posición, y proporcionamos la función pública id() para poder determinar si ya existe una ventana para una posición dada cuando el usuario solicita información sobre ella.
void InformationWindow::revert() { mapper->revert(); enableButtons(false); }
El espacio revert() se activa cada vez que el usuario pulsa el botón Revert.
Dado que establecemos la política de envío QDataWidgetMapper::ManualSubmit, ninguno de los cambios del usuario se vuelve a escribir en el modelo a menos que el usuario elija explícitamente enviarlos todos. Sin embargo, podemos utilizar la ranura QDataWidgetMapper's revert() para reiniciar los widgets del editor, repoblando todos los widgets con los datos actuales del modelo.
void InformationWindow::submit() { QString newImage(imageFileEditor->currentText()); if (displayedImage != newImage) { displayedImage = newImage; emit imageChanged(itemId, newImage); } mapper->submit(); mapper->setCurrentIndex(itemId); enableButtons(false); }
Del mismo modo, la ranura submit() se activa cada vez que los usuarios deciden enviar sus cambios pulsando el botón Submit.
Utilizamos QDataWidgetMapper's submit() slot para enviar todos los cambios de los widgets mapeados al modelo, es decir, a la base de datos. Para cada sección asignada, el delegado de elementos leerá el valor actual del widget y lo establecerá en el modelo. Por último, se invoca la función submit() del modelo para que éste sepa que debe enviar lo que haya almacenado en caché al almacenamiento permanente.
Ten en cuenta que antes de enviar cualquier dato, comprobamos si el usuario ha elegido otro archivo de imagen utilizando como referencia la variable displayedImage almacenada previamente. Si el nombre del archivo actual y el almacenado difieren, almacenamos el nuevo nombre de archivo y emitimos la señal imageChanged().
void InformationWindow::createButtons() { closeButton = new QPushButton(tr("&Close")); revertButton = new QPushButton(tr("&Revert")); submitButton = new QPushButton(tr("&Submit")); closeButton->setDefault(true); connect(closeButton, &QPushButton::clicked, this, &InformationWindow::close); connect(revertButton, &QPushButton::clicked, this, &InformationWindow::revert); connect(submitButton, &QPushButton::clicked, this, &InformationWindow::submit);
La función createButtons() se proporciona por comodidad, es decir, para simplificar el constructor.
Hacemos que el botón Close sea el botón por defecto, es decir, el botón que se pulsa cuando el usuario pulsa Enter, y conectamos su señal clicked() a la ranura close() del widget. Como se ha mencionado anteriormente, al cerrar la ventana sólo se oculta el widget; no se borra. También conectamos los botones Submit y Revert a las ranuras submit() y revert() correspondientes.
buttonBox = new QDialogButtonBox(this); buttonBox->addButton(submitButton, QDialogButtonBox::AcceptRole); buttonBox->addButton(revertButton, QDialogButtonBox::ResetRole); buttonBox->addButton(closeButton, QDialogButtonBox::RejectRole); }
La clase QDialogButtonBox es un widget que presenta los botones en un diseño apropiado para el estilo de widget actual. Los cuadros de diálogo, como nuestra ventana de información, suelen presentar los botones con un diseño que se ajusta a las directrices de la interfaz de la plataforma en cuestión. Invariablemente, las diferentes plataformas tienen diferentes diseños para sus diálogos. QDialogButtonBox nos permite añadir botones, utilizando automáticamente el diseño apropiado para el entorno de escritorio del usuario.
La mayoría de los botones para un diálogo siguen ciertos roles. Damos a los botones Submit y Revert el rol reset, es decir, indicando que al pulsar el botón se restablecen los campos a los valores por defecto (en nuestro caso la información contenida en la base de datos). El rol reject indica que al pulsar el botón se rechaza el diálogo. Por otro lado, dado que sólo ocultamos la ventana de información, cualquier cambio que el usuario haya realizado se conservará hasta que el usuario lo revierta o lo envíe explícitamente.
void InformationWindow::enableButtons(bool enable) { revertButton->setEnabled(enable); submitButton->setEnabled(enable); }
La ranura enableButtons() se llama para habilitar los botones cada vez que el usuario cambia los datos presentados. Del mismo modo, cuando el usuario decide enviar los cambios, los botones se desactivan para indicar que los datos actuales se almacenan en la base de datos.
Esto completa la clase InformationWindow. Veamos cómo la hemos utilizado en nuestra aplicación de ejemplo.
Definición de la clase View
La clase View representa la ventana principal de la aplicación y hereda de QGraphicsView:
class View : public QGraphicsView { Q_OBJECT public: View(const QString &items, const QString &images, QWidget *parent = nullptr); protected: void mouseReleaseEvent(QMouseEvent *event) override; private Q_SLOTS: void updateImage(int id, const QString &fileName);
La clase QGraphicsView es parte del Graphics View Framework que utilizaremos para mostrar las imágenes. Para poder responder a la interacción del usuario mostrando la ventana de información apropiada cuando se hace clic en la imagen, reimplementamos la función mouseReleaseEvent() de QGraphicsView.
Observe que el constructor espera los nombres de dos tablas de la base de datos: Una que contiene la información detallada sobre los elementos, y otra que contiene los nombres de los archivos de imagen disponibles. También proporcionamos una ranura privada updateImage() para capturar la señal imageChanged() de InformationWindow que se emite cada vez que el usuario cambia una imagen asociada al elemento.
private: void addItems(); InformationWindow *findWindow(int id) const; void showInformation(ImageItem *image); QGraphicsScene *scene; QList<InformationWindow *> informationWindows;
La función addItems() es una función de conveniencia proporcionada para simplificar el constructor. Sólo se llama una vez, creando los distintos elementos y añadiéndolos a la vista.
En cambio, la función findWindow() se utiliza con frecuencia. Se llama desde la función showInformation() para determinar si ya se ha creado una ventana para el elemento dado (siempre que creamos un objeto InformationWindow, almacenamos una referencia a él en la lista informationWindows ). Esta última función es llamada a su vez desde nuestra implementación personalizada de mouseReleaseEvent().
QSqlRelationalTableModel *itemTable; };
Por último, declaramos un puntero QSqlRelationalTableModel. Como se mencionó anteriormente, la clase QSqlRelationalTableModel proporciona un modelo de datos editable con soporte para claves externas. Hay un par de cosas que debes tener en cuenta cuando utilices la clase QSqlRelationalTableModel: La tabla debe tener una clave primaria declarada y esta clave no puede contener una relación con otra tabla, es decir, no puede ser una clave foránea. Ten en cuenta también que si una tabla relacional contiene claves que hacen referencia a filas inexistentes en la tabla referenciada, las filas que contienen las claves no válidas no se expondrán a través del modelo. Es responsabilidad del usuario o de la base de datos mantener la integridad referencial.
Implementación de la clase View
Aunque el constructor solicita los nombres tanto de la tabla que contiene los detalles de la oficina como de la tabla que contiene los nombres de los archivos de imagen disponibles, sólo tenemos que crear un objeto QSqlRelationalTableModel para la tabla "items":
View::View(const QString &items, const QString &images, QWidget *parent) : QGraphicsView(parent) { itemTable = new QSqlRelationalTableModel(this); itemTable->setTable(items); itemTable->setRelation(1, QSqlRelation(images, "itemid", "file")); itemTable->select();
La razón es que una vez que tenemos un modelo con los detalles de los artículos, podemos crear una relación con los archivos de imagen disponibles utilizando QSqlRelationalTableModel's setRelation() function. Esta función crea una clave externa para la columna del modelo. La clave se especifica mediante el objeto QSqlRelation proporcionado, construido por el nombre de la tabla a la que se refiere la clave, el campo al que se asigna la clave y el campo que debe presentarse al usuario.
Nótese que establecer la tabla sólo especifica sobre qué tabla opera el modelo, es decir, debemos llamar explícitamente a la función select() del modelo para rellenar nuestro modelo.
scene = new QGraphicsScene(this); scene->setSceneRect(0, 0, 465, 365); setScene(scene); addItems(); setMinimumSize(470, 370); setMaximumSize(470, 370); QLinearGradient gradient(QPointF(0, 0), QPointF(0, 370)); gradient.setColorAt(0, QColor("#868482")); gradient.setColorAt(1, QColor("#5d5b59")); setBackgroundBrush(gradient); }
A continuación, creamos el contenido de nuestra vista, es decir, la escena y sus elementos. Las etiquetas son objetos normales de QGraphicsTextItem, mientras que las imágenes son instancias de la clase ImageItem, derivada de QGraphicsPixmapItem. Volveremos a esto en breve al revisar la función addItems().
Por último, establecemos las restricciones de tamaño del widget principal de la aplicación y el título de la ventana.
void View::addItems() { int itemCount = itemTable->rowCount(); int imageOffset = 150; int leftMargin = 70; int topMargin = 40; for (int i = 0; i < itemCount; i++) { QSqlRecord record = itemTable->record(i); int id = record.value("id").toInt(); QString file = record.value("file").toString(); QString item = record.value("itemtype").toString(); int columnOffset = ((i % 2) * 37); int x = ((i % 2) * imageOffset) + leftMargin + columnOffset; int y = ((i / 2) * imageOffset) + topMargin; ImageItem *image = new ImageItem(id, QPixmap(":/" + file)); image->setData(0, i); image->setPos(x, y); scene->addItem(image); QGraphicsTextItem *label = scene->addText(item); label->setDefaultTextColor(QColor("#d7d6d5")); QPointF labelOffset((120 - label->boundingRect().width()) / 2, 120.0); label->setPos(QPointF(x, y) + labelOffset); } }
La función addItems() sólo se ejecuta una vez al crear la ventana principal de la aplicación. Para cada fila de la tabla de la base de datos, extraemos primero el registro correspondiente utilizando la función record() del modelo. La clase QSqlRecord encapsula tanto la funcionalidad como las características de un registro de base de datos, y permite añadir y eliminar campos, así como establecer y recuperar valores de campo. La función QSqlRecord::value() devuelve el valor del campo con el nombre o índice dado como un objeto QVariant.
Para cada registro, creamos un elemento de etiqueta y un elemento de imagen, calculamos su posición y los añadimos a la escena. Los elementos de imagen están representados por instancias de la clase ImageItem. La razón por la que debemos crear una clase de ítem personalizada es que queremos capturar los eventos hover del ítem, animando el ítem cuando el cursor del ratón pasa por encima de la imagen (por defecto, ningún ítem acepta eventos hover). Por favor, consulta la documentación de Graphics View Framework y los ejemplos de Graphics View para más detalles.
void View::mouseReleaseEvent(QMouseEvent *event) { if (QGraphicsItem *item = itemAt(event->position().toPoint())) { if (ImageItem *image = qgraphicsitem_cast<ImageItem *>(item)) showInformation(image); } QGraphicsView::mouseReleaseEvent(event); }
Reimplementamos el manejador de eventos mouseReleaseEvent() de QGraphicsView para responder a la interacción del usuario. Si el usuario hace clic en cualquiera de los elementos de la imagen, esta función llama a la función privada showInformation() para que aparezca la ventana de información asociada.
El Graphics View Framework proporciona la función qgraphicsitem_cast() para determinar si la instancia QGraphicsItem dada es de un tipo determinado. Tenga en cuenta que si el evento no está relacionado con ninguno de nuestros elementos de imagen, lo pasamos a la implementación de la clase base.
void View::showInformation(ImageItem *image) { int id = image->id(); if (id < 0 || id >= itemTable->rowCount()) return; InformationWindow *window = findWindow(id); if (!window) { window = new InformationWindow(id, itemTable, this); connect(window, &InformationWindow::imageChanged, this, &View::updateImage); window->move(pos() + QPoint(20, 40)); window->show(); informationWindows.append(window); } if (window->isVisible()) { window->raise(); window->activateWindow(); } else window->show(); }
La función showInformation() recibe un objeto ImageItem como argumento, y comienza extrayendo el ID del elemento.
A continuación, determina si ya existe una ventana de información para esta ubicación. Si no existe ninguna ventana para la localización dada, creamos una pasando el ID del ítem, un puntero al modelo, y nuestra vista como padre, al constructor InformationWindow. Ten en cuenta que conectamos la señal imageChanged() de la ventana de información a la ranura updateImage() de este wid get, antes de darle una posición adecuada y añadirla a la lista de ventanas existentes. Si existe una ventana para la posición dada, y esa ventana es visible, se asegura que la ventana se eleva a la parte superior de la pila del widget y se activa. Si está oculta, al llamar a su ranura show() se obtiene el mismo resultado.
void View::updateImage(int id, const QString &fileName) { QList<QGraphicsItem *> items = scene->items(); while(!items.empty()) { QGraphicsItem *item = items.takeFirst(); if (ImageItem *image = qgraphicsitem_cast<ImageItem *>(item)) { if (image->id() == id){ image->setPixmap(QPixmap(":/" +fileName)); image->adjust(); break; } } } }
La ranura updateImage() toma un ID de elemento y el nombre de un archivo de imagen como argumentos. Filtra los elementos de imagen y actualiza el que corresponde al ID de elemento dado con el archivo de imagen proporcionado.
InformationWindow *View::findWindow(int id) const { for (auto window : informationWindows) { if (window && (window->id() == id)) return window; } return nullptr; }
La función findWindow() simplemente busca en la lista de ventanas existentes, devolviendo un puntero a la ventana que coincide con el ID de elemento dado, o nullptr si la ventana no existe.
Por último, echemos un vistazo rápido a nuestra clase personalizada ImageItem:
Definición de la clase ImageItem
La clase ImageItem se proporciona para facilitar la animación de los elementos de imagen. Hereda de QGraphicsPixmapItem y reimplementa sus manejadores de eventos hover:
class ImageItem : public QObject, public QGraphicsPixmapItem { Q_OBJECT public: enum { Type = UserType + 1 }; ImageItem(int id, const QPixmap &pixmap, QGraphicsItem *parent = nullptr); int type() const override { return Type; } void adjust(); int id() const; protected: void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override; void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override; private Q_SLOTS: void setFrame(int frame); void updateItemPosition(); private: QTimeLine timeLine; int recordId; double z; };
Declaramos un valor enum Type para nuestro elemento personalizado y reimplementamos type(). Esto se hace para que podamos utilizar con seguridad qgraphicsitem_cast(). Además, implementamos una función pública id() para poder identificar la ubicación asociada y una función pública adjust() que puede ser llamada para asegurar que el elemento de imagen recibe el tamaño preferido independientemente del archivo de imagen original.
La animación se implementa utilizando la clase QTimeLine junto con los manejadores de eventos y el slot privado setFrame(): El elemento de imagen se expandirá cuando el cursor del ratón pase sobre él, volviendo a su tamaño original cuando el cursor abandone sus bordes.
Por último, almacenamos el ID del elemento al que está asociado este registro en particular, así como un valor z. En Graphics View Framework, el valor z de un elemento determina su posición en la pila de elementos. Un elemento con un valor z alto se dibujará encima de un elemento con un valor z más bajo si comparten el mismo elemento padre. También proporcionamos una función updateItemPosition() para refrescar la vista cuando sea necesario.
Implementación de la clase ImageItem
La clase ImageItem es en realidad sólo un QGraphicsPixmapItem con algunas características adicionales, es decir, podemos pasar la mayoría de los argumentos del constructor (el pixmap, el padre y la escena) al constructor de la clase base:
ImageItem::ImageItem(int id, const QPixmap &pixmap, QGraphicsItem *parent) : QGraphicsPixmapItem(pixmap, parent) { recordId = id; setAcceptHoverEvents(true); timeLine.setDuration(150); timeLine.setFrameRange(0, 150); connect(&timeLine, &QTimeLine::frameChanged, this, &ImageItem::setFrame); connect(&timeLine, &QTimeLine::finished, this, &ImageItem::updateItemPosition); adjust(); }
A continuación, almacenamos el ID para futuras referencias, y nos aseguramos de que nuestro elemento de imagen aceptará eventos hover. Los eventos hover se envían cuando no hay ningún elemento actual que se pueda agarrar con el ratón. Se envían cuando el cursor del ratón entra en un elemento, cuando se mueve dentro del elemento, y cuando el cursor sale de un elemento. Como hemos mencionado antes, ninguno de los elementos de Graphics View Framework acepta eventos hover por defecto.
La clase QTimeLine proporciona una línea de tiempo para controlar las animaciones. Su propiedad duration contiene la duración total de la línea de tiempo en milisegundos. Por defecto, la línea de tiempo se ejecuta una vez desde el principio y hacia el final. La función QTimeLine::setFrameRange() establece el contador de fotogramas de la línea de tiempo; cuando la línea de tiempo se está ejecutando, la señal frameChanged() se emite cada vez que cambia el fotograma. Establecemos la duración y el rango de fotogramas para nuestra animación, y conectamos las señales frameChanged() y finished() de la línea de tiempo a nuestras ranuras privadas setFrame() y updateItemPosition().
Por último, llamamos a adjust() para asegurarnos de que el elemento recibe el tamaño preferido.
void ImageItem::hoverEnterEvent(QGraphicsSceneHoverEvent * /*event*/) { timeLine.setDirection(QTimeLine::Forward); if (z != 1.0) { z = 1.0; updateItemPosition(); } if (timeLine.state() == QTimeLine::NotRunning) timeLine.start(); } void ImageItem::hoverLeaveEvent(QGraphicsSceneHoverEvent * /*event*/) { timeLine.setDirection(QTimeLine::Backward); if (z != 0.0) z = 0.0; if (timeLine.state() == QTimeLine::NotRunning) timeLine.start(); }
Cada vez que el cursor del ratón entra o sale del elemento de la imagen, se activan los controladores de eventos correspondientes: Primero establecemos la dirección de la línea de tiempo, haciendo que el elemento se expanda o se encoja, respectivamente. A continuación, modificamos el valor z del elemento, si aún no se ha establecido en el valor esperado.
En el caso de los eventos hover enter, actualizamos inmediatamente la posición del elemento, ya que queremos que aparezca encima de todos los demás elementos en cuanto empiece a expandirse. En el caso de los eventos hover leave, por otro lado, posponemos la actualización real para conseguir el mismo resultado. Pero recuerda que cuando construimos nuestro ítem, conectamos la señal finished() de la línea de tiempo a la ranura updateItemPosition(). De esta forma el ítem recibe la posición correcta en la pila de ítems una vez que la animación se ha completado. Finalmente, si la línea de tiempo no está en marcha, la iniciamos.
void ImageItem::setFrame(int frame) { adjust(); QPointF center = boundingRect().center(); setTransform(QTransform::fromTranslate(center.x(), center.y()), true); setTransform(QTransform::fromScale(1 + frame / 300.0, 1 + frame / 300.0), true); setTransform(QTransform::fromTranslate(-center.x(), -center.y()), true); }
Cuando la línea de tiempo se está ejecutando, activa la ranura setFrame() cada vez que el fotograma actual cambia debido a la conexión que creamos en el constructor del ítem. Es esta ranura la que controla la animación, expandiendo o encogiendo el elemento de la imagen paso a paso.
Primero llamamos a la función adjust() para asegurarnos de que empezamos con el tamaño original del elemento. Luego escalamos el ítem con un factor que depende del progreso de la animación (usando el parámetro frame ). Tenga en cuenta que, por defecto, la transformación será relativa a la esquina superior izquierda del elemento. Como queremos que el elemento se transforme con respecto a su centro, debemos trasladar el sistema de coordenadas antes de escalar el elemento.
Al final, sólo quedan las siguientes funciones de conveniencia:
void ImageItem::adjust() { setTransform(QTransform::fromScale(120.0 / boundingRect().width(), 120.0 / boundingRect().height())); } int ImageItem::id() const { return recordId; } void ImageItem::updateItemPosition() { setZValue(z); }
La función adjust() define y aplica una matriz de transformación, asegurando que nuestro elemento de imagen aparezca con el tamaño preferido independientemente del tamaño de la imagen de origen. La función id() es trivial, y se proporciona simplemente para poder identificar el elemento. En la ranura updateItemPosition() llamamos a la función QGraphicsItem::setZValue(), estableciendo la elevación del elemento.
© 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.