Exemple de robot « glisser-déposer »
Montre comment effectuer des opérations de glisser-déposer d'éléments dans une vue graphique.
L'exemple « Robot glisser-déposer » montre comment implémenter le glisser-déposer dans une sous-classe de ` QGraphicsItem `, ainsi que comment animer des éléments à l'aide du framework d'animation de Qt.

Graphics View 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 `, ainsi qu’un widget ` QGraphicsView ` permettant de visualiser ces é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 `main` : la classe ` Robot ` décrit un robot simple constitué de plusieurs membres dérivés de ` RobotPart `, notamment ` RobotHead ` et ` RobotLimb` ; la classe ` ColorItem ` fournit une ellipse colorée pouvant être déplacée ; et la fonction ` main() ` fournit la fenêtre principale de l'application.
Nous allons d'abord examiner la classe ` Robot ` pour voir comment assembler les différentes parties afin qu'elles puissent être pivotées et animées individuellement à l'aide de ` QPropertyAnimation`, puis nous examinerons la classe ` ColorItem ` pour montrer comment implémenter le glisser-déposer entre les éléments. Enfin, nous examinerons la fonction `main()` pour voir comment assembler tous les éléments afin de former l'application finale.
Définition de la classe Robot
Le robot se compose de trois classes principales : RobotHead, RobotTorso et RobotLimb, qui sont utilisées respectivement pour les bras supérieurs, les bras inférieurs et les jambes. Toutes ces parties dérivent de la classe RobotPart, qui hérite elle-même de QGraphicsObject. La classe Robot 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 grâce à l’héritage de QObject, et elle déclare également les propriétés de QGraphicsItem à l’aide de Q_PROPERTY, ce qui rend ces propriétés accessibles à QPropertyAnimation.
RobotPart implémente également les trois gestionnaires d’événements les plus importants pour accepter les événements de dépôt : dragEnterEvent(), dragLeaveEvent() et dropEvent().
La couleur est stockée sous forme de variable membre, tout comme la variable dragOver, que nous utiliserons plus tard pour indiquer visuellement que le membre peut accepter les couleurs qui y sont glissées.
RobotPart::RobotPart(QGraphicsItem *parent)
: QGraphicsObject(parent), color(Qt::lightGray)
{
setAcceptDrops(true);
}RobotPartLe constructeur de la classe initialise le membre `dragOver` et définit la couleur sur ` Qt::lightGray`. Dans le corps du constructeur, nous activons la prise en charge des événements de dépôt en appelant ` setAcceptDrops(true)`.
Le reste de l’implémentation de cette classe sert à 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 de glisser-déposer est déplacé dans la zone de la pièce « robot ».
L'implémentation de ce gestionnaire détermine si cet élément dans son ensemble peut accepter ou non les données MIME associées à l’objet glissé entrant. La méthode RobotPart fournit un comportement de base pour toutes les pièces qui acceptent les déposes de couleur. Ainsi, si l’objet glissé entrant contient une couleur, l’événement est accepté : nous définissons dragOver sur true et appelons update() afin de fournir un retour visuel positif à l’utilisateur ; dans le cas contraire, l’événement est ignoré, ce qui permet à l’événement de se propager vers les éléments parents.
void RobotPart::dragLeaveEvent(QGraphicsSceneDragDropEvent *event)
{
Q_UNUSED(event);
dragOver = false;
update();
}Le gestionnaire dragLeaveEvent() est appelé lorsqu’un élément de glisser-déposer est déplacé hors de la zone de la pièce robot. Notre implémentation se contente de réinitialiser dragOver à false et d’appeler update() afin de fournir un retour visuel indiquant que l’objet glissé 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 « glisser-déposer » 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 remettons dragOver à « false », attribuons la nouvelle couleur à 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 laisserons les autres classes à l’appréciation du 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() afin d'assurer une gestion spécifique des glisser-déposer d'images.
La classe contient un membre privé de type pixmap que nous pouvons utiliser pour implémenter la prise en charge du dépôt d’images.
RobotHead::RobotHead(QGraphicsItem *parent)
: RobotPart(parent)
{
}RobotHead Elle dispose d’un constructeur assez simple qui se contente de rediriger vers le constructeur de ` RobotPart`.
La réimplémentation de boundingRect() renvoie les dimensions de la tête. Comme nous souhaitons que le centre de rotation corresponde au centre inférieur de l’élément, nous avons choisi un rectangle de délimitation qui commence à (-15, -50) et s’étend sur 30 unités de large et 50 unités de haut. Lors de la rotation de la tête, le « cou » reste immobile tandis que le sommet de la tête s'incline 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 la méthode ` paint()``, nous dessinons la tête proprement dite. L’implémentation est divisée en deux parties : si une image a été déposée sur la tête, nous dessinons cette image ; sinon, nous dessinons une tête de robot rectangulaire arrondie à l’aide de graphiques vectoriels simples.
Pour des raisons de performances, et en fonction de la complexité de ce qui est dessiné, il est souvent plus rapide de dessiner la tête sous forme d’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 le glisser-déposer d’images. Pour prendre en charge cette fonctionnalité, sa réimplémentation de la méthode ` dragEnterEvent()` vérifie si l’objet glissé contient des données d’image ; si c’est le cas, l’événement est accepté. Sinon, on revient à 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 compléter la prise en charge des images, nous devons également implémenter dropEvent(). Nous vérifions si l’objet glissé contient des données d’image ; si c’est le cas, nous stockons ces données sous la forme d’un member pixmap et appelons update(). Ce pixmap est utilisé au sein de l’implémentation de paint() que nous avons examinée précédemment.
RobotTorso et RobotLimb sont similaires à RobotHead; passons donc 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 composants, elle implémente aussi les méthodes boundingRect() et paint(). Elle propose toutefois une implémentation assez particulière :
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);
}Comme la classe ` Robot ` n’est utilisée que comme nœud de base pour le reste du robot, elle n’a pas de représentation visuelle. Son implémentation de ` 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 définir l’indicateur ` ItemHasNoContents`, ce qui constitue une optimisation mineure pour les éléments dépourvus d’apparence visuelle.
Nous construisons ensuite toutes les parties du robot (tête, torse, bras et jambes supérieurs/inférieurs). L'ordre d'empilement est très important, et nous utilisons la hiérarchie parent-enfant pour garantir que les éléments tournent et se déplacent correctement. Nous construisons d'abord le torse, car il s'agit de l'élément racine. Nous construisons ensuite la tête et transmettons 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 principe s'applique 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, la partie supérieure du bras gauche est déplacée précisément vers la zone supérieure gauche du torse, et la partie supérieure du bras droit 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 de code présente les deux animations qui agissent sur l'échelle et la rotation de la tête. Les deux instances d'QPropertyAnimation s se contentent de définir l'objet, la propriété, ainsi que les valeurs de début et de fin respectives.
Toutes les animations sont contrôlées par un groupe d’animation parallèle de niveau supérieur. Les animations d’échelle et de rotation sont ajoutées à ce groupe.
Les autres animations sont définies de manière similaire.
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'accélération et une durée pour chaque animation, nous nous assurons que le groupe d'animation de niveau supérieur s'exécute en boucle indéfiniment, puis nous lançons 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 besoin ni de propriétés, ni de signaux ni de slots ; afin d'économiser des ressources, il est donc tout à fait naturel qu'elle hérite de QGraphicsItem (plutôt que de QGraphicsObject).
Elle déclare les fonctions obligatoires ` boundingRect()` et ` paint()`, et ajoute des réimplémentations de ` mousePressEvent()`, ` mouseMoveEvent()` et ` mouseReleaseEvent()`. Elle contient un seul membre privé de type couleur.
Examinons 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 cet élément attribue une couleur aléatoire opaque à son membre « color » en utilisant la fonction QRandomGenerator. Pour une meilleure ergonomie, il attribue une info-bulle qui fournit une indication utile à l'utilisateur, et il définit également un curseur adapté. Cela garantit que le curseur prendra l'apparence de Qt::OpenHandCursor lorsque le pointeur de la souris survolera l'élément.
Enfin, nous appelons ` setAcceptedMouseButtons()` pour garantir que cet élément ne puisse traiter que des clics de souris ( Qt::LeftButton). Cela simplifie considérablement les gestionnaires d’événements de souris, car nous pouvons toujours supposer que seul le bouton gauche de la souris est enfoncé puis relâché.
Le rectangle de délimitation de l’élément est un rectangle fixe de 30 × 30 unités centré sur l’origine (0, 0) de l’élément, et décalé de 0,5 unité dans toutes les directions pour permettre à un stylo redimensionnable de tracer son contour. Pour une touche visuelle finale, les limites sont également décalées de quelques unités vers le bas et vers la droite afin de laisser de la place à une simple ombre portée.
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 de 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 cliquez avec la souris à l’intérieur de la zone de l’élément. Notre implémentation se contente de définir 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 cliqué à l'intérieur de la zone d'un élément. Notre implémentation rétablit le curseur sur Qt::OpenHandCursor. Les gestionnaires d’événements de clic 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 CircleItem, le curseur se transforme en main ouverte. En cliquant sur l’élément, un curseur en forme de main fermée s’affiche. En relâchant le bouton, 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 cliqué à l’intérieur de la zone de l’élément ` ColorItem`. Cette implémentation fournit l’élément logique le plus important pour ` CircleItem` : le code qui lance et gère les glissements.
L’implémentation commence par vérifier si la souris a été déplacée suffisamment loin pour éliminer le bruit de tremblement de la souris. Nous ne souhaitons lancer un glissement que si la souris a été déplacée au-delà de la distance de début de glissement définie par l’application.
Ensuite, nous créons un objet ` QDrag `, en passant l’événement ` widget ` (c’est-à-dire la fenêtre d’affichage de ` QGraphicsView `) à son constructeur. Qt veillera à ce que cet objet soit supprimé au bon moment. Nous créons également une instance de ` QMimeData ` pouvant contenir nos données de couleur ou d’image, et l’attribuons à l’objet de glisser-déposer.
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 de code donne un résultat quelque peu aléatoire : de temps en temps, une image spéciale est attribuée aux données MIME de l’objet de glisser-déposer. La pixmap est également attribuée comme pixmap de l’objet de glisser-déposer. Cela vous permettra de voir l’image en cours de glissement 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 MIME de l’objet à glisser. Nous convertissons cette « ColorItem » en un nouveau pixmap afin de fournir à l’utilisateur un retour visuel indiquant que la couleur est en cours de « glissement ».
drag->exec();
setCursor(Qt::OpenHandCursor);
}Enfin, nous exécutons le glissement. La fonction ` QDrag::exec()` réintègre la boucle d’événements et ne s’en sort que si le glissement a été soit déposé, soit 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 au sein de la fonction `main()`.
int main(int argc, char **argv)
{
QApplication app(argc, argv);Nous commençons par créer des objets ` QApplication` et par initialiser le générateur de nombres aléatoires. Cela garantit que les éléments de couleur auront des couleurs différentes à chaque démarrage de l’application.
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 créons une scène de taille fixe, puis nous créons 10 instances de ` ColorItem ` disposées en cercle. Chaque élément est ajouté à la scène.
Au centre de ce cercle, nous créons une instance d’ Robot. Le robot est redimensionné et déplacé de quelques unités vers le haut. 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 y 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 afin de simplifier la gestion des mises à jour visuelles. La vue se voit attribuer un arrière-plan fixe de couleur sable et un titre de fenêtre.
Nous affichons ensuite la vue. Les animations démarrent dès que le contrôle entre 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.