Exemple de robot "glisser-déposer
Démontre comment glisser et déposer des éléments dans une vue graphique.
L'exemple Drag and Drop Robot montre comment mettre en œuvre la fonction Drag and Drop dans une sous-classe de QGraphicsItem, ainsi que comment animer des éléments à l'aide du cadre d'animation de Qt.

La vue graphique fournit la classe QGraphicsScene pour gérer et interagir avec un grand nombre d'éléments graphiques 2D personnalisés dérivés de la classe QGraphicsItem, et un widget QGraphicsView pour visualiser les éléments, avec prise en charge du zoom et de la rotation.
Cet exemple se compose d'une classe Robot, d'une classe ColorItem et d'une fonction principale : la classe Robot décrit un robot simple composé de plusieurs membres dérivés de RobotPart, dont RobotHead et RobotLimb, la classe ColorItem fournit une ellipse colorée que l'on peut faire glisser et la fonction main() fournit la fenêtre principale de l'application.
Nous examinerons d'abord la classe Robot pour voir comment assembler les différentes parties afin qu'elles puissent être tournées et animées individuellement à l'aide de QPropertyAnimation, puis nous examinerons la classe ColorItem pour montrer comment mettre en œuvre le glisser-déposer entre les éléments. Enfin, nous passerons en revue la fonction main() pour voir comment nous pouvons assembler toutes les pièces pour former l'application finale.
Définition des classes du robot
Le robot se compose de trois classes principales : RobotHead, RobotTorso, et RobotLimb, qui est utilisée pour les bras et les jambes supérieurs et inférieurs. Toutes les parties dérivent de la classe RobotPart, qui hérite à son tour de la classe QGraphicsObject. La classe Robot elle-même n'a pas d'apparence visuelle et sert uniquement de nœud racine pour le robot.
Commençons par la déclaration de la classe RobotPart.
class RobotPart : public QGraphicsObject { public: RobotPart(QGraphicsItem *parent = nullptr); protected: void dragEnterEvent(QGraphicsSceneDragDropEvent *event) override; void dragLeaveEvent(QGraphicsSceneDragDropEvent *event) override; void dropEvent(QGraphicsSceneDragDropEvent *event) override; QColor color = Qt::lightGray; bool dragOver = false; };
Cette classe de base hérite de QGraphicsObject. QGraphicsObject fournit des signaux et des slots en héritant de QObject, et déclare également les propriétés de QGraphicsItem à l'aide de Q_PROPERTY, ce qui rend les propriétés accessibles à QPropertyAnimation.
RobotPart met également en œuvre les trois gestionnaires d'événements les plus importants pour accepter les événements de chute : dragEnterEvent(), dragLeaveEvent() et dropEvent().
La couleur est stockée en tant que variable membre, avec la variable dragOver, que nous utiliserons plus tard pour indiquer visuellement que le membre peut accepter des couleurs qui sont glissées sur lui.
RobotPart::RobotPart(QGraphicsItem *parent) : QGraphicsObject(parent), color(Qt::lightGray) { setAcceptDrops(true); }
RobotPartLe constructeur de la classe initialise le membre dragOver et fixe la couleur à Qt::lightGray. Dans le corps du constructeur, nous activons la prise en charge des événements de chute en appelant setAcceptDrops(true).
Le reste de l'implémentation de cette classe consiste à prendre en charge le glisser-déposer.
void RobotPart::dragEnterEvent(QGraphicsSceneDragDropEvent *event) { if (event->mimeData()->hasColor()) { event->setAccepted(true); dragOver = true; update(); } else { event->setAccepted(false); } }
Le gestionnaire dragEnterEvent() est appelé lorsqu'un élément Glisser-Déposer est glissé dans la zone de la pièce du robot.
L'implémentation du gestionnaire détermine si cet élément dans son ensemble peut ou non accepter les données mimétiques associées à l'objet glissé entrant. RobotPart fournit un comportement de base pour toutes les parties qui accepte les gouttes de couleur. Ainsi, si l'objet glissant entrant contient une couleur, l'événement est accepté, nous définissons dragOver sur true et appelons update() pour aider à fournir un retour visuel positif à l'utilisateur ; sinon, l'événement est ignoré, ce qui permet à l'événement de se propager aux éléments parents.
void RobotPart::dragLeaveEvent(QGraphicsSceneDragDropEvent *event) { Q_UNUSED(event); dragOver = false; update(); }
Le gestionnaire dragLeaveEvent() est appelé lorsqu'un élément Drag and Drop est déplacé hors de la zone de la pièce du robot. Notre implémentation réinitialise simplement dragOver à false et appelle update() pour fournir un retour visuel indiquant que le glissement a quitté cet élément.
void RobotPart::dropEvent(QGraphicsSceneDragDropEvent *event) { dragOver = false; if (event->mimeData()->hasColor()) color = qvariant_cast<QColor>(event->mimeData()->colorData()); update(); }
Le gestionnaire dropEvent() est appelé lorsqu'un élément Drag and Drop est déposé sur un élément (c'est-à-dire lorsque le bouton de la souris est relâché au-dessus de l'élément pendant le glissement).
Nous réinitialisons dragOver à false, attribuons la nouvelle couleur de l'élément et appelons update().
La déclaration et l'implémentation de RobotHead, RobotTorso et RobotLimb sont pratiquement identiques. Nous examinerons RobotHead en détail, car cette classe présente une différence mineure, et nous laisserons les autres classes en guise d'exercice pour le lecteur.
class RobotHead : public RobotPart { public: RobotHead(QGraphicsItem *parent = nullptr); QRectF boundingRect() const override; void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; protected: void dragEnterEvent(QGraphicsSceneDragDropEvent *event) override; void dropEvent(QGraphicsSceneDragDropEvent *event) override; private: QPixmap pixmap; };
La classe RobotHead hérite de RobotPart et fournit les implémentations nécessaires de boundingRect() et paint(). Elle réimplémente également dragEnterEvent() et dropEvent() pour fournir une gestion spéciale des chutes d'images.
La classe contient un membre pixmap privé que nous pouvons utiliser pour mettre en œuvre la prise en charge de l'acceptation des chutes d'images.
RobotHead::RobotHead(QGraphicsItem *parent) : RobotPart(parent) { }
RobotHead a un constructeur plutôt simple qui renvoie simplement au constructeur de RobotPart.
La réimplémentation de boundingRect() renvoie les extensions de la tête. Comme nous voulons que le centre de rotation soit le centre inférieur de l'élément, nous avons choisi un rectangle de délimitation qui commence à (-15, -50) et s'étend jusqu'à 30 unités de largeur et 50 unités de hauteur. Lors de la rotation de la tête, le "cou" reste immobile tandis que le sommet de la tête bascule d'un côté à l'autre.
void RobotHead::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_UNUSED(option); Q_UNUSED(widget); if (pixmap.isNull()) { painter->setBrush(dragOver ? color.lighter(130) : color); painter->drawRoundedRect(-10, -30, 20, 30, 25, 25, Qt::RelativeSize); painter->setBrush(Qt::white); painter->drawEllipse(-7, -3 - 20, 7, 7); painter->drawEllipse(0, -3 - 20, 7, 7); painter->setBrush(Qt::black); painter->drawEllipse(-5, -1 - 20, 2, 2); painter->drawEllipse(2, -1 - 20, 2, 2); painter->setPen(QPen(Qt::black, 2)); painter->setBrush(Qt::NoBrush); painter->drawArc(-6, -2 - 20, 12, 15, 190 * 16, 160 * 16); } else { painter->scale(.2272, .2824); painter->drawPixmap(QPointF(-15 * 4.4, -50 * 3.54), pixmap); } }
Dans paint(), nous dessinons la tête réelle. L'implémentation est divisée en deux sections ; si une image a été déposée sur la tête, nous dessinons l'image, sinon nous dessinons une tête de robot ronde et rectangulaire avec de simples graphiques vectoriels.
Pour des raisons de performance, en fonction de la complexité de ce qui est peint, il peut souvent être plus rapide de dessiner la tête comme une image plutôt que d'utiliser une séquence d'opérations vectorielles.
void RobotHead::dragEnterEvent(QGraphicsSceneDragDropEvent *event) { if (event->mimeData()->hasImage()) { event->setAccepted(true); dragOver = true; update(); } else { RobotPart::dragEnterEvent(event); } }
La tête du robot peut accepter des chutes d'images. Pour ce faire, sa réimplémentation de dragEnterEvent() vérifie si l'objet drag contient des données d'image, et si c'est le cas, l'événement est accepté. Dans le cas contraire, nous revenons à l'implémentation de base de RobotPart.
void RobotHead::dropEvent(QGraphicsSceneDragDropEvent *event) { if (event->mimeData()->hasImage()) { dragOver = false; pixmap = qvariant_cast<QPixmap>(event->mimeData()->imageData()); update(); } else { RobotPart::dropEvent(event); } }
Pour assurer le suivi de la prise en charge des images, nous devons également implémenter dropEvent(). Nous vérifions si l'objet drag contient des données d'image, et si c'est le cas, nous stockons ces données en tant que pixmap membre et appelons update(). Cette pixmap est utilisée dans l'implémentation de paint() que nous avons examinée précédemment.
RobotTorso Comme les classes RobotHead et RobotLimb sont similaires à la classe , passons directement à la classe Robot.
class Robot : public RobotPart { public: Robot(QGraphicsItem *parent = nullptr); QRectF boundingRect() const override; void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; };
La classe Robot hérite également de RobotPart, et comme les autres parties, elle implémente également boundingRect() et paint(). Elle fournit cependant une implémentation assez spéciale :
QRectF Robot::boundingRect() const { return QRectF(); } void Robot::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_UNUSED(painter); Q_UNUSED(option); Q_UNUSED(widget); }
La classe Robot n'étant utilisée que comme nœud de base pour le reste du robot, elle n'a pas de représentation visuelle. Son implémentation boundingRect() peut donc renvoyer un QRectF nul, et sa fonction paint() ne fait rien.
Robot::Robot(QGraphicsItem *parent) : RobotPart(parent) { setFlag(ItemHasNoContents); QGraphicsObject *torsoItem = new RobotTorso(this); QGraphicsObject *headItem = new RobotHead(torsoItem); QGraphicsObject *upperLeftArmItem = new RobotLimb(torsoItem); QGraphicsObject *lowerLeftArmItem = new RobotLimb(upperLeftArmItem); QGraphicsObject *upperRightArmItem = new RobotLimb(torsoItem); QGraphicsObject *lowerRightArmItem = new RobotLimb(upperRightArmItem); QGraphicsObject *upperRightLegItem = new RobotLimb(torsoItem); QGraphicsObject *lowerRightLegItem = new RobotLimb(upperRightLegItem); QGraphicsObject *upperLeftLegItem = new RobotLimb(torsoItem); QGraphicsObject *lowerLeftLegItem = new RobotLimb(upperLeftLegItem);
Le constructeur commence par mettre le drapeau ItemHasNoContents, ce qui est une optimisation mineure pour les éléments qui n'ont pas d'apparence visuelle.
Nous construisons ensuite toutes les parties du robot (tête, torse, bras et jambes). L'ordre d'empilement est très important et nous utilisons la hiérarchie parent-enfant pour nous assurer que les éléments tournent et se déplacent correctement. Nous construisons d'abord le torse, qui est l'élément racine. Nous construisons ensuite la tête et passons le torse au constructeur de HeadItem. La tête devient ainsi un enfant du torse ; si vous faites pivoter le torse, la tête suivra. Le même schéma est appliqué au reste des membres.
headItem->setPos(0, -18); upperLeftArmItem->setPos(-15, -10); lowerLeftArmItem->setPos(30, 0); upperRightArmItem->setPos(15, -10); lowerRightArmItem->setPos(30, 0); upperRightLegItem->setPos(10, 32); lowerRightLegItem->setPos(30, 0); upperLeftLegItem->setPos(-10, 32); lowerLeftLegItem->setPos(30, 0);
Chaque partie du robot est soigneusement positionnée. Par exemple, le bras supérieur gauche est déplacé précisément vers la zone supérieure gauche du torse, et le bras supérieur droit est déplacé vers la zone supérieure droite.
QParallelAnimationGroup *animation = new QParallelAnimationGroup(this); QPropertyAnimation *headAnimation = new QPropertyAnimation(headItem, "rotation"); headAnimation->setStartValue(20); headAnimation->setEndValue(-20); QPropertyAnimation *headScaleAnimation = new QPropertyAnimation(headItem, "scale"); headScaleAnimation->setEndValue(1.1); animation->addAnimation(headAnimation); animation->addAnimation(headScaleAnimation);
La section suivante crée tous les objets d'animation. Cet extrait montre les deux animations qui agissent sur l'échelle et la rotation de la tête. Les deux instances QPropertyAnimation définissent simplement l'objet, la propriété et les valeurs respectives de début et de fin.
Toutes les animations sont contrôlées par un groupe d'animation parallèle de haut niveau. Les animations d'échelle et de rotation sont ajoutées à ce groupe.
Les autres animations sont définies de la même manière.
for (int i = 0; i < animation->animationCount(); ++i) { QPropertyAnimation *anim = qobject_cast<QPropertyAnimation *>(animation->animationAt(i)); anim->setEasingCurve(QEasingCurve::SineCurve); anim->setDuration(2000); } animation->setLoopCount(-1); animation->start();
Enfin, nous définissons une courbe d'assouplissement et une durée pour chaque animation, nous nous assurons que le groupe d'animation de niveau supérieur boucle indéfiniment et nous démarrons l'animation de niveau supérieur.
Définition de la classe ColorItem
La classe ColorItem représente un élément circulaire sur lequel on peut appuyer pour faire glisser des couleurs sur les pièces du robot.
class ColorItem : public QGraphicsItem { public: ColorItem(); QRectF boundingRect() const override; void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; protected: void mousePressEvent(QGraphicsSceneMouseEvent *event) override; void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; private: QColor color; };
Cette classe est très simple. Elle n'utilise pas d'animations et n'a pas besoin de propriétés, de signaux ni de slots. Pour économiser des ressources, il est donc plus naturel qu'elle hérite de QGraphicsItem (par opposition à QGraphicsObject).
Il déclare les fonctions obligatoires boundingRect() et paint(), et ajoute des réimplémentations de mousePressEvent(), mouseMoveEvent() et mouseReleaseEvent(). Elle contient un seul membre couleur privé.
Jetons un coup d'œil à son implémentation.
ColorItem::ColorItem() : color(QRandomGenerator::global()->bounded(256), QRandomGenerator::global()->bounded(256), QRandomGenerator::global()->bounded(256)) { setToolTip(QString("QColor(%1, %2, %3)\n%4") .arg(color.red()).arg(color.green()).arg(color.blue()) .arg("Click and drag this color onto the robot!")); setCursor(Qt::OpenHandCursor); setAcceptedMouseButtons(Qt::LeftButton); }
ColorItemLe constructeur de 's assigne une couleur aléatoire opaque à son membre color en utilisant QRandomGenerator. Pour améliorer la convivialité, il assigne une infobulle qui fournit un indice utile à l'utilisateur, et il met également en place un curseur approprié. Cela permet de s'assurer que le curseur s'affichera par hasard sur Qt::OpenHandCursor lorsque le pointeur de la souris survolera l'élément.
Enfin, nous appelons setAcceptedMouseButtons() pour nous assurer que cet élément ne peut traiter que Qt::LeftButton. Cela simplifie grandement les gestionnaires d'événements de la souris, car nous pouvons toujours supposer que seul le bouton gauche de la souris est enfoncé et relâché.
Le rectangle de délimitation de l'élément est un rectangle fixe de 30x30 unités centré sur l'origine de l'élément (0, 0), et ajusté de 0,5 unité dans toutes les directions pour permettre à un stylo extensible de dessiner son contour. Pour une touche visuelle finale, les limites compensent également avec quelques unités vers le bas et vers la droite pour faire de la place à une ombre portée simple.
void ColorItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_UNUSED(option); Q_UNUSED(widget); painter->setPen(Qt::NoPen); painter->setBrush(Qt::darkGray); painter->drawEllipse(-12, -12, 30, 30); painter->setPen(QPen(Qt::black, 1)); painter->setBrush(QBrush(color)); painter->drawEllipse(-15, -15, 30, 30); }
L'implémentation paint() dessine une ellipse avec un contour noir d'une unité, un remplissage de couleur unie et une ombre portée gris foncé.
void ColorItem::mousePressEvent(QGraphicsSceneMouseEvent *) { setCursor(Qt::ClosedHandCursor); }
Le gestionnaire mousePressEvent() est appelé lorsque vous appuyez sur le bouton de la souris dans la zone de l'élément. Notre implémentation place simplement le curseur sur Qt::ClosedHandCursor.
void ColorItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *) { setCursor(Qt::OpenHandCursor); }
Le gestionnaire mouseReleaseEvent() est appelé lorsque vous relâchez le bouton de la souris après l'avoir enfoncé dans la zone d'un élément. Notre implémentation replace le curseur sur Qt::OpenHandCursor. Les gestionnaires d'événements d'appui et de relâchement de la souris fournissent ensemble un retour visuel utile à l'utilisateur : lorsque vous déplacez le pointeur de la souris sur un élément CircleItem, le curseur prend la forme d'une main ouverte. Si vous appuyez sur l'élément, le curseur prend la forme d'une main fermée. En relâchant la pression, le curseur redevient une main ouverte.
void ColorItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { if (QLineF(event->screenPos(), event->buttonDownScreenPos(Qt::LeftButton)) .length() < QApplication::startDragDistance()) { return; } QDrag *drag = new QDrag(event->widget()); QMimeData *mime = new QMimeData; drag->setMimeData(mime);
Le gestionnaire mouseMoveEvent() est appelé lorsque vous déplacez la souris après avoir appuyé sur le bouton de la souris à l'intérieur de la zone ColorItem. Cette implémentation fournit l'élément logique le plus important pour CircleItem: le code qui démarre et gère les déplacements.
L'implémentation commence par vérifier si la souris a été déplacée suffisamment loin pour éliminer le bruit de la gigue de la souris. Nous ne voulons commencer un déplacement que si la souris a été déplacée plus loin que la distance de déplacement de départ de l'application.
Ensuite, nous créons un objet QDrag, en passant l'événement widget (c'est-à-dire la fenêtre QGraphicsView ) à son constructeur. Qt s'assurera que cet objet est supprimé au bon moment. Nous créons également une instance QMimeData qui peut contenir nos données de couleur ou d'image, et nous l'assignons à l'objet drag.
static int n = 0; if (n++ > 2 && QRandomGenerator::global()->bounded(3) == 0) { QImage image(":/images/head.png"); mime->setImageData(image); drag->setPixmap(QPixmap::fromImage(image).scaled(30, 40)); drag->setHotSpot(QPoint(15, 30));
Cet extrait a un résultat quelque peu aléatoire : de temps en temps, une image spéciale est assignée aux données mimétiques de l'objet drag. La pixmap est également affectée à la pixmap de l'objet "drag". Cela permet de s'assurer que l'image glissée est visible sous forme de pixmap sous le curseur de la souris.
} else { mime->setColorData(color); mime->setText(QString("#%1%2%3") .arg(color.red(), 2, 16, QLatin1Char('0')) .arg(color.green(), 2, 16, QLatin1Char('0')) .arg(color.blue(), 2, 16, QLatin1Char('0'))); QPixmap pixmap(34, 34); pixmap.fill(Qt::white); QPainter painter(&pixmap); painter.translate(15, 15); painter.setRenderHint(QPainter::Antialiasing); paint(&painter, nullptr, nullptr); painter.end(); pixmap.setMask(pixmap.createHeuristicMask()); drag->setPixmap(pixmap); drag->setHotSpot(QPoint(15, 20)); }
Sinon, et c'est le résultat le plus courant, une simple couleur est attribuée aux données mimétiques de l'objet glissé. Nous rendons cette ColorItem dans une nouvelle pixmap pour donner à l'utilisateur un retour visuel sur le fait que la couleur est "glissée".
drag->exec(); setCursor(Qt::OpenHandCursor); }
Enfin, nous exécutons le déplacement. QDrag::exec() réintègre la boucle d'événements et n'en sort que si le glissement a été déposé ou annulé. Dans tous les cas, nous réinitialisons le curseur à Qt::OpenHandCursor.
La fonction main()
Maintenant que les classes Robot et ColorItem sont complètes, nous pouvons assembler tous les éléments dans la fonction main().
int main(int argc, char **argv) { QApplication app(argc, argv);
Nous commençons par construire QApplication et initialiser le générateur de nombres aléatoires. Cela permet de s'assurer que les éléments de couleur ont des couleurs différentes à chaque fois que l'application démarre.
QGraphicsScene scene(-200, -200, 400, 400); for (int i = 0; i < 10; ++i) { ColorItem *item = new ColorItem; item->setPos(::sin((i * 6.28) / 10.0) * 150, ::cos((i * 6.28) / 10.0) * 150); scene.addItem(item); } Robot *robot = new Robot; robot->setTransform(QTransform::fromScale(1.2, 1.2), true); robot->setPos(0, -20); scene.addItem(robot);
Nous construisons une scène de taille fixe et créons 10 instances ColorItem disposées en cercle. Chaque élément est ajouté à la scène.
Au centre de ce cercle, nous créons une instance Robot. Le robot est mis à l'échelle et déplacé de quelques unités. Il est ensuite ajouté à la scène.
GraphicsView view(&scene); view.setRenderHint(QPainter::Antialiasing); view.setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate); view.setBackgroundBrush(QColor(230, 200, 167)); view.setWindowTitle("Drag and Drop Robot"); view.show(); return app.exec(); }
Enfin, nous créons une fenêtre QGraphicsView et lui assignons la scène.
Pour améliorer la qualité visuelle, nous activons l'anticrénelage. Nous choisissons également d'utiliser les mises à jour du rectangle englobant pour simplifier la gestion des mises à jour visuelles. La vue est dotée d'un arrière-plan fixe de couleur sable et d'un titre de fenêtre.
Nous affichons ensuite la vue. Les animations commencent immédiatement après l'entrée du contrôle dans la boucle d'événements.
© 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.