Exemple d'utilisation du framework d'annulation
Cet exemple montre comment implémenter les fonctionnalités d'annulation et de rétablissement à l'aide du framework d'annulation Qt.

Dans le framework d'annulation de Qt, toutes les actions effectuées par l'utilisateur sont implémentées dans des classes qui héritent de ` QUndoCommand`. Une classe de commande d'annulation sait à la fois comment annuler une action ( redo()) – ou simplement l'exécuter la première fois – et comment la répéter ( undo()). Pour chaque action effectuée par l’utilisateur, une commande est placée dans une pile d’annulation ( QUndoStack). Comme cette pile contient toutes les commandes exécutées (empilées par ordre chronologique) sur le document, elle permet de faire défiler l’état du document vers l’avant ou vers l’arrière en annulant et en rétablissant ces commandes. Consultez le document de présentation pour une introduction générale au framework d’annulation.
L'exemple d'annulation implémente une application de diagramme simple. Il est possible d'ajouter et de supprimer des éléments, qui peuvent être de forme carrée ou rectangulaire, et de les déplacer en les faisant glisser avec la souris. La pile d'annulation est affichée dans une QUndoView, qui est une liste dans laquelle les commandes apparaissent sous forme d'éléments de liste. Les fonctions « Annuler » et « Refaire » sont accessibles via le menu Édition. L’utilisateur peut également sélectionner une commande dans la vue « Annuler ».
Nous utilisons le framework de vue graphique pour implémenter le diagramme. Nous n'aborderons que brièvement le code associé, car le framework propose ses propres exemples (par exemple, l'exemple « Diagram Scene »).
L’exemple se compose des classes suivantes :
MainWindowest la fenêtre principale et organise les widgets de l’exemple. Elle crée les commandes en fonction des entrées de l’utilisateur et les conserve dans la pile de commandes.AddCommandajoute un élément à la scène.DeleteCommandsupprime un élément de la scène.MoveCommandLorsqu’un élément est déplacé, la classe `MoveCommand` enregistre les positions de départ et d’arrivée du déplacement, et déplace l’élément en fonction de celles-ci lorsque les méthodes `redo()` et `undo()` sont appelées.DiagramSceneHérite de ` QGraphicsScene ` et émet des signaux vers `MoveComands` lorsqu’un élément est déplacé.DiagramItemHérite de QGraphicsPolygonItem et représente un élément dans le diagramme.
Définition de la classe MainWindow
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow();
public slots:
void itemMoved(DiagramItem *movedDiagram, const QPointF &moveStartPosition);
private slots:
void deleteItem();
void addBox();
void addTriangle();
void about();
void updateActions();
private:
void createActions();
void createMenus();
void createToolBars();
void createUndoView();
QAction *deleteAction = nullptr;
QAction *addBoxAction = nullptr;
QAction *addTriangleAction = nullptr;
QAction *undoAction = nullptr;
QAction *redoAction = nullptr;
QAction *exitAction = nullptr;
QAction *aboutAction = nullptr;
QMenu *fileMenu = nullptr;
QMenu *editMenu = nullptr;
QMenu *itemMenu = nullptr;
QMenu *helpMenu = nullptr;
DiagramScene *diagramScene = nullptr;
QUndoStack *undoStack = nullptr;
QUndoView *undoView = nullptr;
};La classe ` MainWindow ` gère la pile d’annulation, c’est-à-dire qu’elle crée des objets ` QUndoCommand`, les ajoute à la pile et les en retire lorsqu’elle reçoit le signal ` triggered() ` provenant de ` undoAction ` et ` redoAction`.
Implémentation de la classe MainWindow
Commençons par examiner le constructeur :
MainWindow::MainWindow()
{
undoStack = new QUndoStack(this);
diagramScene = new DiagramScene();
const QBrush pixmapBrush(QPixmap(":/icons/cross.png").scaled(30, 30));
diagramScene->setBackgroundBrush(pixmapBrush);
diagramScene->setSceneRect(QRect(0, 0, 500, 500));
createActions();
createMenus();
createToolBars();
createUndoView();
connect(diagramScene, &DiagramScene::itemMoved,
this, &MainWindow::itemMoved);
connect(diagramScene, &DiagramScene::selectionChanged,
this, &MainWindow::updateActions);
setWindowTitle("Undo Framework");
QGraphicsView *view = new QGraphicsView(diagramScene);
setCentralWidget(view);
adjustSize();
}Dans le constructeur, nous configurons la DiagramScene et l’ QGraphicsView. Nous souhaitons que l’ deleteAction ne soit activée que lorsque nous avons sélectionné un élément ; nous connectons donc le signal « selectionChanged() » de la scène au slot « updateActions() ».
Voici la fonction createUndoView():
void MainWindow::createUndoView()
{
QDockWidget *undoDockWidget = new QDockWidget;
undoDockWidget->setWindowTitle(tr("Command List"));
undoDockWidget->setWidget(new QUndoView(undoStack));
addDockWidget(Qt::RightDockWidgetArea, undoDockWidget);
}QUndoView est un widget qui affiche, sous forme de liste, le texte défini par la fonction setText() pour chaque QUndoCommand de la pile d'annulation. Nous l'avons placé dans un widget d'ancrage.
Voici la fonction ` createActions() ` :
void MainWindow::createActions()
{
deleteAction = new QAction(QIcon(":/icons/remove.png"), tr("&Delete Item"), this);
deleteAction->setShortcut(tr("Del"));
connect(deleteAction, &QAction::triggered, this, &MainWindow::deleteItem);
...
undoAction = undoStack->createUndoAction(this, tr("&Undo"));
undoAction->setIcon(QIcon(":/icons/undo.png"));
undoAction->setShortcuts(QKeySequence::Undo);
redoAction = undoStack->createRedoAction(this, tr("&Redo"));
redoAction->setIcon(QIcon(":/icons/redo.png"));
redoAction->setShortcuts(QKeySequence::Redo);La fonction createActions() configure toutes les actions d’exemple de la manière indiquée ci-dessus. Les méthodes createUndoAction() et createRedoAction() nous permettent de créer des actions qui sont désactivées ou activées en fonction de l’état de la pile. De plus, le texte de l’action sera mis à jour automatiquement en fonction de la méthode text() des commandes d’annulation. Pour les autres actions, nous avons implémenté des slots dans la classe MainWindow.
...
updateActions();
}
void MainWindow::updateActions()
{
deleteAction->setEnabled(!diagramScene->selectedItems().isEmpty());
}Une fois toutes les actions créées, nous mettons à jour leur état en appelant la même fonction qui est connectée au signal selectionChanged de la scène.
Les fonctions ` createMenus() ` et ` createToolBars() ` ajoutent les actions aux menus et aux barres d’outils :
void MainWindow::createMenus()
{
fileMenu = menuBar()->addMenu(tr("&File"));
fileMenu->addAction(exitAction);
editMenu = menuBar()->addMenu(tr("&Edit"));
editMenu->addAction(undoAction);
editMenu->addAction(redoAction);
editMenu->addSeparator();
editMenu->addAction(deleteAction);
...
helpMenu = menuBar()->addMenu(tr("&About"));
helpMenu->addAction(aboutAction);
}
void MainWindow::createToolBars()
{
QToolBar *editToolBar = new QToolBar;
editToolBar->addAction(undoAction);
editToolBar->addAction(redoAction);
editToolBar->addSeparator();
editToolBar->addAction(deleteAction);
...
addToolBar(editToolBar);
addToolBar(itemToolBar);
}Voici le slot « itemMoved() » :
void MainWindow::itemMoved(DiagramItem *movedItem,
const QPointF &oldPosition)
{
undoStack->push(new MoveCommand(movedItem, oldPosition));
}Nous poussons simplement une MoveCommand sur la pile, ce qui appelle redo() sur celle-ci.
Voici le slot « deleteItem() » :
void MainWindow::deleteItem()
{
if (diagramScene->selectedItems().isEmpty())
return;
QUndoCommand *deleteCommand = new DeleteCommand(diagramScene);
undoStack->push(deleteCommand);
}Un élément doit être sélectionné pour pouvoir être supprimé. Nous devons vérifier s’il est sélectionné, car le slot ` deleteAction ` peut être activé même si aucun élément n’est sélectionné. Cela peut se produire car nous ne détectons pas de signal ni d’événement lorsqu’un élément est sélectionné.
Voici le slot « addBox() » :
void MainWindow::addBox()
{
QUndoCommand *addCommand = new AddCommand(DiagramItem::Box, diagramScene);
undoStack->push(addCommand);
}La fonction « addBox() » crée une commande « AddCommand » et l'ajoute à la pile d'annulation.
Voici le slot « addTriangle() » :
void MainWindow::addTriangle()
{
QUndoCommand *addCommand = new AddCommand(DiagramItem::Triangle,
diagramScene);
undoStack->push(addCommand);
}La fonction ` addTriangle() ` crée un `AddCommand` et l'ajoute à la pile d'annulation.
Voici l’implémentation de about():
void MainWindow::about()
{
QMessageBox::about(this, tr("About Undo"),
tr("The <b>Undo</b> example demonstrates how to "
"use Qt's undo framework."));
}Le slot « about » est déclenché par la fonction « aboutAction » et affiche une boîte de présentation de l'exemple.
Définition de la classe AddCommand
class AddCommand : public QUndoCommand
{
public:
AddCommand(DiagramItem::DiagramType addType, QGraphicsScene *graphicsScene,
QUndoCommand *parent = nullptr);
~AddCommand();
void undo() override;
void redo() override;
private:
DiagramItem *myDiagramItem;
QGraphicsScene *myGraphicsScene;
QPointF initialPosition;
};La classe ` AddCommand ` ajoute des éléments graphiques de type `DiagramItem` à la `DiagramScene`.
Implémentation de la classe AddCommand
Commençons par le constructeur :
AddCommand::AddCommand(DiagramItem::DiagramType addType,
QGraphicsScene *scene, QUndoCommand *parent)
: QUndoCommand(parent), myGraphicsScene(scene)
{
static int itemCount = 0;
myDiagramItem = new DiagramItem(addType);
initialPosition = QPointF((itemCount * 15) % int(scene->width()),
(itemCount * 15) % int(scene->height()));
scene->update();
++itemCount;
setText(QObject::tr("Add %1")
.arg(createCommandString(myDiagramItem, initialPosition)));
}Nous créons d'abord le DiagramItem à ajouter à la DiagramScene. La fonction setText() nous permet de définir une description ( QString ) de la commande. Nous l'utilisons pour afficher des messages personnalisés dans l'QUndoView et dans le menu de la fenêtre principale.
void AddCommand::undo()
{
myGraphicsScene->removeItem(myDiagramItem);
myGraphicsScene->update();
}undo() supprime l'élément de la scène.
void AddCommand::redo()
{
myGraphicsScene->addItem(myDiagramItem);
myDiagramItem->setPos(initialPosition);
myGraphicsScene->clearSelection();
myGraphicsScene->update();
}Nous définissons la position de l’élément, car nous ne le faisons pas dans le constructeur.
Définition de la classe DeleteCommand
class DeleteCommand : public QUndoCommand
{
public:
explicit DeleteCommand(QGraphicsScene *graphicsScene, QUndoCommand *parent = nullptr);
void undo() override;
void redo() override;
private:
DiagramItem *myDiagramItem;
QGraphicsScene *myGraphicsScene;
};La classe DeleteCommand implémente la fonctionnalité permettant de supprimer un élément de la scène.
Implémentation de la classe DeleteCommand
DeleteCommand::DeleteCommand(QGraphicsScene *scene, QUndoCommand *parent)
: QUndoCommand(parent), myGraphicsScene(scene)
{
QList<QGraphicsItem *> list = myGraphicsScene->selectedItems();
list.first()->setSelected(false);
myDiagramItem = static_cast<DiagramItem *>(list.first());
setText(QObject::tr("Delete %1")
.arg(createCommandString(myDiagramItem, myDiagramItem->pos())));
}Nous savons qu’il doit y avoir un élément sélectionné, car il n’est pas possible de créer une commande DeleteCommand tant que l’élément à supprimer n’est pas sélectionné, et qu’un seul élément peut être sélectionné à la fois. L’élément doit être désélectionné s’il est réinséré dans la scène.
void DeleteCommand::undo()
{
myGraphicsScene->addItem(myDiagramItem);
myGraphicsScene->update();
}L'élément est simplement réinséré dans la scène.
void DeleteCommand::redo()
{
myGraphicsScene->removeItem(myDiagramItem);
}L'élément est supprimé de la scène.
Définition de la classe MoveCommand
class MoveCommand : public QUndoCommand
{
public:
enum { Id = 1234 };
MoveCommand(DiagramItem *diagramItem, const QPointF &oldPos,
QUndoCommand *parent = nullptr);
void undo() override;
void redo() override;
bool mergeWith(const QUndoCommand *command) override;
int id() const override { return Id; }
private:
DiagramItem *myDiagramItem;
QPointF myOldPos;
QPointF newPos;
};La méthode ` mergeWith()` a été réimplémentée afin de regrouper les déplacements consécutifs d'un élément en une seule commande `MoveCommand` ; en d'autres termes, l'élément sera ramené à la position de départ du premier déplacement.
Implémentation de la classe MoveCommand
Le constructeur de MoveCommand se présente comme suit :
MoveCommand::MoveCommand(DiagramItem *diagramItem, const QPointF &oldPos,
QUndoCommand *parent)
: QUndoCommand(parent), myDiagramItem(diagramItem)
, myOldPos(oldPos), newPos(diagramItem->pos())
{
}Nous enregistrons à la fois l'ancienne et la nouvelle position pour les fonctions « Annuler » et « Rétablir » respectivement.
void MoveCommand::undo()
{
myDiagramItem->setPos(myOldPos);
myDiagramItem->scene()->update();
setText(QObject::tr("Move %1")
.arg(createCommandString(myDiagramItem, newPos)));
}Nous nous contentons de définir l'ancienne position de l'élément et de mettre à jour la scène.
void MoveCommand::redo()
{
myDiagramItem->setPos(newPos);
setText(QObject::tr("Move %1")
.arg(createCommandString(myDiagramItem, newPos)));
}Nous plaçons l'élément à sa nouvelle position.
bool MoveCommand::mergeWith(const QUndoCommand *command)
{
const MoveCommand *moveCommand = static_cast<const MoveCommand *>(command);
DiagramItem *item = moveCommand->myDiagramItem;
if (myDiagramItem != item)
return false;
newPos = item->pos();
setText(QObject::tr("Move %1")
.arg(createCommandString(myDiagramItem, newPos)));
return true;
}Chaque fois qu'une commande MoveCommand est créée, cette fonction est appelée pour vérifier si elle doit être fusionnée avec la commande précédente. C'est l'objet de la commande précédente qui est conservé dans la pile. La fonction renvoie « true » si la commande est fusionnée ; sinon, elle renvoie « false ».
Nous vérifions d'abord s'il s'agit du même élément qui a été déplacé deux fois, auquel cas nous fusionnons les commandes. Nous mettons à jour la position de l'élément afin qu'il prenne la dernière position de la séquence de déplacement lors de l'annulation.
Définition de la classe DiagramScene
class DiagramScene : public QGraphicsScene
{
Q_OBJECT
public:
DiagramScene(QObject *parent = nullptr);
signals:
void itemMoved(DiagramItem *movedItem, const QPointF &movedFromPosition);
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
private:
QGraphicsItem *movingItem = nullptr;
QPointF oldPos;
};La classe DiagramScene implémente la fonctionnalité permettant de déplacer un DiagramItem à l’aide de la souris. Elle émet un signal lorsqu’un déplacement est terminé. Ce signal est intercepté par la classe MainWindow, qui génère des MoveCommands. Nous n’examinons pas l’implémentation de DiagramScene, car elle ne traite que des aspects liés au framework graphique.
La fonction ` main() `
La fonction ` main() ` du programme se présente comme suit :
int main(int argv, char *args[])
{
QApplication app(argv, args);
MainWindow mainWindow;
mainWindow.show();
return app.exec();
}Nous dessinons une grille en arrière-plan de la DiagramScene ; nous utilisons donc un fichier de ressources. Le reste de la fonction crée la MainWindow et l'affiche en tant que fenêtre de niveau supérieur.
© 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.