Exemple d'extraction
L'exemple Drill Down montre comment lire des données à partir d'une base de données et comment soumettre des modifications à l'aide des classes QSqlRelationalTableModel et QDataWidgetMapper.

Lors de l'exécution de l'application d'exemple, un utilisateur peut récupérer des informations sur chaque élément en cliquant sur l'image correspondante. L'application fait apparaître une fenêtre d'information affichant les données et permettant aux utilisateurs de modifier la description ainsi que l'image. La vue principale sera mise à jour lorsque les utilisateurs soumettront leurs modifications.
L'exemple se compose de trois classes :
ImageItemune classe d'éléments graphiques personnalisés utilisée pour afficher les images.Viewest le widget principal de l'application permettant à l'utilisateur de parcourir les différents éléments.InformationWindowaffiche les informations demandées, permettant aux utilisateurs de les modifier et de soumettre leurs changements à la base de données.
Nous allons d'abord examiner la classe InformationWindow pour voir comment vous pouvez lire et modifier les données d'une base de données. Nous examinerons ensuite le widget principal de l'application, c'est-à-dire la classe View, et la classe ImageItem qui lui est associée.
Définition de la classe InformationWindow
La classe InformationWindow est un widget personnalisé qui hérite 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);
Lorsque nous créons une fenêtre d'information, nous transmettons au constructeur l'ID de l'élément associé, un pointeur sur le modèle et un parent. Nous utiliserons le pointeur sur le modèle pour remplir notre fenêtre de données, tout en transmettant le paramètre parent à la classe de base. L'identifiant est stocké pour référence ultérieure.
Une fois la fenêtre créée, nous utiliserons la fonction publique id() pour la localiser chaque fois que des informations sur l'emplacement donné seront demandées. Nous utiliserons également l'ID pour mettre à jour le widget de l'application principale lorsque les utilisateurs soumettront leurs modifications à la base de données, c'est-à-dire que nous émettrons un signal contenant l'ID et le nom du fichier en tant que paramètres chaque fois que les utilisateurs modifieront l'image associée.
private Q_SLOTS: void revert(); void submit(); void enableButtons(bool enable);
Puisque nous permettons aux utilisateurs de modifier certaines données, nous devons fournir une fonctionnalité permettant d'annuler et de soumettre leurs modifications. L'emplacement enableButtons() est prévu pour permettre d'activer et de désactiver les différents boutons en cas de besoin.
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 fonction createButtons() est également une fonction de commodité, fournie pour simplifier le constructeur. Comme indiqué plus haut, nous stockons l'identifiant de l'élément pour référence ultérieure. Nous stockons également le nom du fichier image actuellement affiché afin de pouvoir déterminer quand émettre le signal imageChanged().
La fenêtre d'information utilise la classe QLabel pour afficher le nom d'un article. Le fichier image associé est affiché à l'aide d'une instance QComboBox tandis que la description est affichée à l'aide de QTextEdit. En outre, la fenêtre comporte trois boutons permettant de contrôler le flux de données et l'affichage ou non de la fenêtre.
Enfin, nous déclarons un mappeur. La classe QDataWidgetMapper fournit une correspondance entre une section d'un modèle de données et des widgets. Nous utiliserons le mappeur pour extraire des données de la base de données donnée, en mettant à jour la base de données chaque fois que l'utilisateur modifie les données.
Mise en œuvre de la classe InformationWindow
Le constructeur prend trois arguments : un ID d'élément, un pointeur de base de données et un widget parent. Le pointeur de base de données est en fait un pointeur vers un objet QSqlRelationalTableModel fournissant un modèle de données modifiable (avec prise en charge des clés étrangères) pour notre table de base de données.
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;
Nous commençons par créer les différents widgets nécessaires à l'affichage des données contenues dans la base de données. La plupart des widgets sont créés de manière simple. Notez toutefois la boîte combobox qui affiche le nom du fichier image :
imageFileEditor = new QComboBox; imageFileEditor->setModel(items->relationModel(1)); imageFileEditor->setModelColumn(items->relationModel(1)->fieldIndex("file"));
Dans cet exemple, les informations relatives aux articles sont stockées dans une table de la base de données appelée "articles". Lors de la création du modèle, nous utiliserons une clé étrangère pour établir une relation entre cette table et une deuxième table de la base de données, "images", contenant les noms des fichiers images disponibles. Nous reviendrons sur la manière de procéder lors de l'examen de la classe View. La raison de la création d'une telle relation est que nous voulons nous assurer que l'utilisateur ne peut choisir qu'entre des fichiers d'images prédéfinis.
Le modèle correspondant à la table de base de données "images" est disponible via la fonction relationModel() de QSqlRelationalTableModel, qui requiert la clé étrangère (dans ce cas, le numéro de la colonne "imagefile") comme argument. Nous utilisons la fonction setModel() de QComboBox pour que le combobox utilise le modèle "images". Et comme ce modèle a deux colonnes ("itemid" et "file"), nous spécifions également quelle colonne nous voulons voir à l'aide de la fonction 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);
Nous créons ensuite le mappeur. La classe QDataWidgetMapper nous permet de créer des widgets sensibles aux données en les associant à des sections d'un modèle d'article.
La fonction addMapping() ajoute une correspondance entre le widget donné et la section spécifiée du modèle. Si l'orientation du mappeur est horizontale (par défaut), la section est une colonne du modèle, sinon c'est une ligne. Nous appelons la fonction setCurrentIndex() pour initialiser les widgets avec les données associées à l'ID de l'élément donné. Chaque fois que l'index actuel change, tous les widgets sont mis à jour avec le contenu du modèle.
Nous définissons également la politique de soumission du mappeur à QDataWidgetMapper::ManualSubmit, ce qui signifie qu'aucune donnée n'est soumise à la base de données tant que l'utilisateur n'a pas explicitement demandé une soumission (l'alternative est QDataWidgetMapper::AutoSubmit, qui soumet automatiquement les modifications lorsque le widget correspondant perd le focus). Enfin, nous spécifions le délégué d'élément que la vue mappeur doit utiliser pour ses éléments. La classe QSqlRelationalDelegate représente un délégué qui, contrairement au délégué par défaut, permet la fonctionnalité de combobox pour les champs qui sont des clés étrangères dans d'autres tables (comme "imagefile" dans notre table "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()); }
Enfin, nous connectons les signaux "quelque chose a changé" dans les éditeurs à notre slot enableButtons personnalisé, ce qui permet aux utilisateurs de soumettre ou d'annuler leurs modifications. Nous devons utiliser des lambdas pour connecter le slot enableButtons car sa signature ne correspond pas à QTextEdit::textChanged et QComboBox::currentIndexChanged.
Nous ajoutons tous les widgets dans une disposition, stockons l'ID de l'élément et le nom du fichier image affiché pour référence ultérieure, et définissons le titre de la fenêtre et sa taille initiale.
Notez que nous avons également défini le drapeau de fenêtre Qt::Window pour indiquer que notre widget est en fait une fenêtre, avec un cadre de système de fenêtre et une barre de titre.
int InformationWindow::id() const { return itemId; }
Lorsqu'une fenêtre est créée, elle n'est pas supprimée tant que l'application principale n'est pas quittée (c'est-à-dire que si l'utilisateur ferme la fenêtre d'information, elle est seulement cachée). Pour cette raison, nous ne voulons pas créer plus d'un objet InformationWindow pour chaque élément, et nous fournissons la fonction publique id() pour pouvoir déterminer si une fenêtre existe déjà pour un emplacement donné lorsque l'utilisateur demande des informations à ce sujet.
void InformationWindow::revert() { mapper->revert(); enableButtons(false); }
Le slot revert() est déclenché chaque fois que l'utilisateur appuie sur le bouton Revert.
Comme nous avons défini la politique de soumission de QDataWidgetMapper::ManualSubmit, aucune des modifications apportées par l'utilisateur n'est réécrite dans le modèle à moins que l'utilisateur ne choisisse explicitement de les soumettre toutes. Néanmoins, nous pouvons utiliser le slot revert() de QDataWidgetMapper pour réinitialiser les widgets de l'éditeur, en remplissant tous les widgets avec les données actuelles du modèle.
void InformationWindow::submit() { QString newImage(imageFileEditor->currentText()); if (displayedImage != newImage) { displayedImage = newImage; emit imageChanged(itemId, newImage); } mapper->submit(); mapper->setCurrentIndex(itemId); enableButtons(false); }
De même, le slot submit() est déclenché chaque fois que les utilisateurs décident de soumettre leurs modifications en appuyant sur le bouton Submit.
Nous utilisons le slot submit() de QDataWidgetMapper pour soumettre toutes les modifications des widgets mappés au modèle, c'est-à-dire à la base de données. Pour chaque section mappée, le délégué de l'élément lira alors la valeur actuelle du widget et la définira dans le modèle. Enfin, la fonction submit() du modèle est invoquée pour indiquer au modèle qu'il doit soumettre ce qu'il a mis en cache au stockage permanent.
Notez qu'avant de soumettre des données, nous vérifions si l'utilisateur a choisi un autre fichier image en utilisant la variable displayedImage précédemment stockée comme référence. Si les noms de fichiers actuels et stockés diffèrent, nous stockons le nouveau nom de fichier et émettons le signal 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 fonction createButtons() est fournie par commodité, c'est-à-dire pour simplifier le constructeur.
Nous faisons du bouton Close le bouton par défaut, c'est-à-dire le bouton qui est pressé lorsque l'utilisateur appuie sur Enter, et nous connectons son signal clicked() à l'emplacement close() du widget. Comme indiqué ci-dessus, la fermeture de la fenêtre ne fait que cacher le widget ; il n'est pas supprimé. Nous connectons également les boutons Submit et Revert aux emplacements submit() et revert() correspondants.
buttonBox = new QDialogButtonBox(this); buttonBox->addButton(submitButton, QDialogButtonBox::AcceptRole); buttonBox->addButton(revertButton, QDialogButtonBox::ResetRole); buttonBox->addButton(closeButton, QDialogButtonBox::RejectRole); }
La classe QDialogButtonBox est un widget qui présente les boutons dans une disposition appropriée au style du widget actuel. Les boîtes de dialogue telles que notre fenêtre d'information présentent généralement les boutons dans une disposition conforme aux directives d'interface de la plate-forme concernée. Invariablement, les différentes plateformes ont des dispositions différentes pour leurs boîtes de dialogue. QDialogButtonBox nous permet d'ajouter des boutons, en utilisant automatiquement la disposition appropriée pour l'environnement de bureau de l'utilisateur.
La plupart des boutons d'une boîte de dialogue suivent certains rôles. Nous donnons aux boutons Submit et Revert le rôle reset, c'est-à-dire qu'ils indiquent que le fait d'appuyer sur le bouton réinitialise les champs aux valeurs par défaut (dans notre cas, les informations contenues dans la base de données). Le rôle reject indique que le fait de cliquer sur le bouton entraîne le rejet du dialogue. D'autre part, comme nous ne masquons que la fenêtre d'information, toutes les modifications apportées par l'utilisateur seront conservées jusqu'à ce que l'utilisateur les annule ou les soumette explicitement.
void InformationWindow::enableButtons(bool enable) { revertButton->setEnabled(enable); submitButton->setEnabled(enable); }
Le slot enableButtons() est appelé pour activer les boutons chaque fois que l'utilisateur modifie les données présentées. De même, lorsque l'utilisateur choisit de soumettre les modifications, les boutons sont désactivés pour indiquer que les données actuelles sont stockées dans la base de données.
Ceci complète la classe InformationWindow. Voyons maintenant comment nous l'avons utilisée dans notre exemple d'application.
Définition de la classe View
La classe View représente la fenêtre principale de l'application et hérite 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 classe QGraphicsView fait partie du cadre de visualisation graphique que nous utiliserons pour afficher les images. Pour pouvoir répondre à l'interaction de l'utilisateur en affichant la fenêtre d'information appropriée lorsqu'il clique sur l'image, nous réimplémentons la fonction mouseReleaseEvent() de QGraphicsView.
Notez que le constructeur attend les noms de deux tables de la base de données : L'une contenant les informations détaillées sur les articles, et l'autre contenant les noms des fichiers d'images disponibles. Nous fournissons également un emplacement privé updateImage() pour capter le signal imageChanged() de InformationWindow qui est émis chaque fois que l'utilisateur modifie une image associée à l'article.
private: void addItems(); InformationWindow *findWindow(int id) const; void showInformation(ImageItem *image); QGraphicsScene *scene; QList<InformationWindow *> informationWindows;
La fonction addItems() est une fonction de commodité fournie pour simplifier le constructeur. Elle n'est appelée qu'une seule fois, pour créer les différents éléments et les ajouter à la vue.
La fonction findWindow(), en revanche, est fréquemment utilisée. Elle est appelée par la fonction showInformation() pour déterminer si une fenêtre a déjà été créée pour l'élément donné (chaque fois que nous créons un objet InformationWindow, nous stockons une référence à cet objet dans la liste informationWindows ). Cette dernière fonction est à son tour appelée par notre implémentation personnalisée de mouseReleaseEvent().
QSqlRelationalTableModel *itemTable; };
Enfin, nous déclarons un pointeur QSqlRelationalTableModel. Comme indiqué précédemment, la classe QSqlRelationalTableModel fournit un modèle de données modifiable avec prise en charge des clés étrangères. Il y a deux choses que vous devez garder à l'esprit lorsque vous utilisez la classe QSqlRelationalTableModel: La table doit avoir une clé primaire déclarée et cette clé ne peut pas contenir une relation avec une autre table, c'est-à-dire qu'elle ne peut pas être une clé étrangère. Notez également que si une table relationnelle contient des clés qui renvoient à des lignes inexistantes dans la table référencée, les lignes contenant les clés non valides ne seront pas exposées par le modèle. Il incombe à l'utilisateur ou à la base de données de maintenir l'intégrité référentielle.
Mise en œuvre de la classe de vue
Bien que le constructeur demande les noms de la table contenant les détails du bureau et de la table contenant les noms des fichiers d'images disponibles, nous ne devons créer qu'un objet QSqlRelationalTableModel pour la table "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();
En effet, une fois que nous disposons d'un modèle contenant les détails des articles, nous pouvons créer une relation avec les fichiers d'images disponibles à l'aide de la fonction setRelation() de QSqlRelationalTableModel. Cette fonction crée une clé étrangère pour la colonne de modèle donnée. La clé est spécifiée par l'objet QSqlRelation fourni, construit à partir du nom de la table à laquelle la clé fait référence, du champ auquel la clé est associée et du champ qui doit être présenté à l'utilisateur.
Notez que la définition de la table ne fait que spécifier la table sur laquelle le modèle opère, c'est-à-dire que nous devons appeler explicitement la fonction select() du modèle pour remplir notre modèle.
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); }
Nous créons ensuite le contenu de notre vue, c'est-à-dire la scène et ses éléments. Les étiquettes sont des objets QGraphicsTextItem ordinaires, tandis que les images sont des instances de la classe ImageItem, dérivée de QGraphicsPixmapItem. Nous reviendrons sur ce point lors de l'examen de la fonction addItems().
Enfin, nous définissons les contraintes de taille et le titre de la fenêtre du widget de l'application principale.
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 fonction addItems() n'est appelée qu'une seule fois lors de la création de la fenêtre de l'application principale. Pour chaque ligne de la table de la base de données, nous commençons par extraire l'enregistrement correspondant à l'aide de la fonction record() du modèle. La classe QSqlRecord encapsule à la fois la fonctionnalité et les caractéristiques d'un enregistrement de base de données, et prend en charge l'ajout et la suppression de champs ainsi que la définition et la récupération de valeurs de champs. La fonction QSqlRecord::value() renvoie la valeur du champ portant le nom ou l'index donné sous la forme d'un objet QVariant.
Pour chaque enregistrement, nous créons un élément d'étiquette et un élément d'image, nous calculons leur position et nous les ajoutons à la scène. Les éléments d'image sont représentés par des instances de la classe ImageItem. La raison pour laquelle nous devons créer une classe d'élément personnalisée est que nous voulons capturer les événements de survol de l'élément, en animant l'élément lorsque le curseur de la souris survole l'image (par défaut, aucun élément n'accepte les événements de survol). Pour plus de détails, veuillez consulter la documentation sur le cadre de travail des vues graphiques et les exemples de vues graphiques.
void View::mouseReleaseEvent(QMouseEvent *event) { if (QGraphicsItem *item = itemAt(event->position().toPoint())) { if (ImageItem *image = qgraphicsitem_cast<ImageItem *>(item)) showInformation(image); } QGraphicsView::mouseReleaseEvent(event); }
Nous réimplémentons le gestionnaire d'événements mouseReleaseEvent() de QGraphicsView pour répondre à l'interaction de l'utilisateur. Si l'utilisateur clique sur l'un des éléments de l'image, cette fonction appelle la fonction privée showInformation() pour faire apparaître la fenêtre d'information associée.
Le cadre de visualisation graphique fournit la fonction qgraphicsitem_cast() pour déterminer si l'instance QGraphicsItem donnée est d'un type donné. Notez que si l'événement n'est lié à aucun de nos éléments d'image, nous le transmettons à l'implémentation de la classe de 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 fonction showInformation() reçoit un objet ImageItem en argument et commence par extraire l'ID de l'élément.
Elle détermine ensuite si une fenêtre d'information a déjà été créée pour cet emplacement. Si aucune fenêtre n'existe pour l'emplacement donné, nous en créons une en passant l'ID de l'élément, un pointeur vers le modèle et notre vue en tant que parent, au constructeur InformationWindow. Notez que nous connectons le signal imageChanged() de la fenêtre d'information à l'emplacement updateImage() de ce widget, avant de lui donner une position appropriée et de l'ajouter à la liste des fenêtres existantes. S'il existe une fenêtre pour l'emplacement donné, et que cette fenêtre est visible, le constructeur s'assure que la fenêtre est élevée au sommet de la pile de widgets et activée. Si elle est cachée, l'appel à son slot show() donne le même résultat.
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; } } } }
Le slot updateImage() prend comme arguments un ID d'élément et le nom d'un fichier image. Elle filtre les éléments d'image et met à jour celui qui correspond à l'ID d'élément donné, avec le fichier d'image fourni.
InformationWindow *View::findWindow(int id) const { for (auto window : informationWindows) { if (window && (window->id() == id)) return window; } return nullptr; }
La fonction findWindow() recherche simplement dans la liste des fenêtres existantes et renvoie un pointeur vers la fenêtre qui correspond à l'ID d'élément donné, ou nullptr si la fenêtre n'existe pas.
Enfin, jetons un coup d'œil rapide à notre classe personnalisée ImageItem:
Définition de la classe ImageItem
La classe ImageItem est fournie pour faciliter l'animation des éléments d'image. Elle hérite de QGraphicsPixmapItem et réimplémente ses gestionnaires d'événements de survol :
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; };
Nous déclarons une valeur enum Type pour notre élément personnalisé et réimplémentons type(). Nous déclarons une valeur d'énumération qgraphicsitem_cast pour notre élément personnalisé et réimplémentons (), afin de pouvoir utiliser () en toute sécurité. En outre, nous implémentons une fonction publique id() pour pouvoir identifier l'emplacement associé et une fonction publique adjust() qui peut être appelée pour s'assurer que l'élément d'image reçoit la taille préférée, quel que soit le fichier d'image d'origine.
L'animation est mise en œuvre à l'aide de la classe QTimeLine, des gestionnaires d'événements et de l'emplacement privé setFrame(): L'élément d'image s'agrandit lorsque le curseur de la souris le survole et revient à sa taille d'origine lorsque le curseur quitte ses limites.
Enfin, nous stockons l'ID de l'élément auquel cet enregistrement particulier est associé ainsi qu'une valeur z. Dans le cadre de la vue graphique, la valeur z d'un élément détermine sa position dans la pile d'éléments. Un élément ayant une valeur z élevée sera dessiné au-dessus d'un élément ayant une valeur z plus faible s'ils partagent le même élément parent. Nous fournissons également une fonction updateItemPosition() pour rafraîchir la vue si nécessaire.
Mise en œuvre de la classe ImageItem
La classe ImageItem n'est en fait qu'une QGraphicsPixmapItem avec quelques fonctionnalités supplémentaires, c'est-à-dire que nous pouvons passer la plupart des arguments du constructeur (la pixmap, le parent et la scène) au constructeur de la classe de 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(); }
Nous stockons ensuite l'identifiant pour référence ultérieure et nous nous assurons que notre élément d'image acceptera les événements de survol. Les événements de survol sont délivrés lorsqu'il n'y a pas d'élément d'accroche de la souris en cours. Ils sont envoyés lorsque le curseur de la souris entre dans un élément, lorsqu'il se déplace à l'intérieur de l'élément et lorsque le curseur quitte l'élément. Comme nous l'avons mentionné précédemment, aucun des éléments du Graphics View Framework n'accepte par défaut les événements de survol.
La classe QTimeLine fournit une ligne de temps pour contrôler les animations. Sa propriété duration indique la durée totale de la ligne temporelle en millisecondes. Par défaut, la ligne temporelle se déroule une fois du début vers la fin. La fonction QTimeLine::setFrameRange() définit le compteur d'images de la ligne de temps ; lorsque la ligne de temps est en cours d'exécution, le signal frameChanged() est émis à chaque changement d'image. Nous définissons la durée et la plage d'images de notre animation et connectons les signaux frameChanged() et finished() de la ligne temporelle à nos emplacements privés setFrame() et updateItemPosition().
Enfin, nous appelons adjust() pour nous assurer que l'élément a la taille souhaitée.
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(); }
Chaque fois que le curseur de la souris entre ou sort de l'élément d'image, les gestionnaires d'événements correspondants sont déclenchés : Nous commençons par définir la direction de la ligne de temps, ce qui a pour effet d'agrandir ou de rétrécir l'élément, respectivement. Ensuite, nous modifions la valeur z de l'élément si elle n'est pas déjà réglée sur la valeur attendue.
Dans le cas des événements d'entrée au survol, nous mettons immédiatement à jour la position de l'élément, car nous voulons que l'élément apparaisse au-dessus de tous les autres dès qu'il commence à s'étendre. Dans le cas des événements "hover leave ", en revanche, nous reportons la mise à jour effective pour obtenir le même résultat. Mais rappelez-vous que lorsque nous avons construit notre élément, nous avons connecté le signal finished() de la ligne de temps à l'emplacement updateItemPosition(). De cette manière, l'objet se voit attribuer la position correcte dans la pile d'objets une fois l'animation terminée. Enfin, si la ligne de temps n'est pas déjà en cours d'exécution, nous la démarrons.
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); }
Lorsque la ligne de temps est en cours d'exécution, elle déclenche le slot setFrame() à chaque fois que l'image courante change en raison de la connexion que nous avons créée dans le constructeur de l'élément. C'est ce slot qui contrôle l'animation, en agrandissant ou en rétrécissant l'élément d'image étape par étape.
Nous appelons d'abord la fonction adjust() pour nous assurer que nous commençons avec la taille originale de l'élément. Ensuite, nous redimensionnons l'élément avec un facteur dépendant de la progression de l'animation (à l'aide du paramètre frame ). Notez que par défaut, la transformation sera relative au coin supérieur gauche de l'élément. Puisque nous voulons que l'élément soit transformé par rapport à son centre, nous devons translater le système de coordonnées avant de mettre l'élément à l'échelle.
Au final, il ne reste que les fonctions de commodité suivantes :
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 fonction adjust() définit et applique une matrice de transformation, garantissant que notre élément d'image apparaît avec la taille préférée, quelle que soit la taille de l'image source. La fonction id() est triviale et permet simplement d'identifier l'élément. Dans le slot updateItemPosition(), nous appelons la fonction QGraphicsItem::setZValue(), qui définit l'élévation de l'élément.
© 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.