Beispiel für das Undo-Framework
Dieses Beispiel zeigt, wie man die Undo-/Redo-Funktionalität mit dem Qt-Undo-Framework implementiert.

Im Qt-Undo-Framework werden alle vom Benutzer ausgeführten Aktionen in Klassen implementiert, die von ` QUndoCommand` erben. Eine Undo-Befehlsklasse weiß, wie man eine Aktion sowohl rückgängig macht ( redo()) – oder einfach nur beim ersten Mal ausführt – als auch wiederherstellt ( undo()). Für jede vom Benutzer ausgeführte Aktion wird ein Befehl auf einen „ QUndoStack “ gesetzt. Da der Stapel alle im Dokument ausgeführten Befehle (in chronologischer Reihenfolge gestapelt) enthält, kann er den Zustand des Dokuments durch Rückgängigmachen und Wiederherstellen seiner Befehle vor- und zurückrollen. Eine allgemeine Einführung in das Undo-Framework finden Sie im Übersichtsdokument.
Das Undo-Beispiel implementiert eine einfache Diagrammanwendung. Es ist möglich, Elemente hinzuzufügen und zu löschen, die entweder kastenförmig oder rechteckig sind, sowie die Elemente durch Ziehen mit der Maus zu verschieben. Der Undo-Stack wird in einem „ QUndoView “ angezeigt, einer Liste, in der die Befehle als Listenelemente dargestellt werden. „Rückgängig“ und „Wiederherstellen“ sind über das Menü „Bearbeiten“ verfügbar. Der Benutzer kann einen Befehl auch direkt in der Rückgängig-Ansicht auswählen.
Zur Implementierung des Diagramms verwenden wir das Graphics-View-Framework. Wir gehen auf den zugehörigen Code nur kurz ein, da das Framework eigene Beispiele enthält (z. B. das „Diagram Scene Example“).
Das Beispiel besteht aus den folgenden Klassen:
MainWindowist das Hauptfenster und ordnet die Widgets des Beispiels an. Es erstellt die Befehle basierend auf der Benutzereingabe und speichert sie im Befehlsstapel.AddCommandFügt der Szene ein Element hinzu.DeleteCommandlöscht ein Element aus der Szene.MoveCommandWenn ein Element verschoben wird, protokolliert der `MoveCommand` die Start- und Endposition der Verschiebung und verschiebt das Element entsprechend diesen Positionen, wenn `redo()` und `undo()` aufgerufen werden.DiagramSceneErbt von „ QGraphicsScene “ und sendet Signale an „MoveComands“, wenn ein Element verschoben wird.DiagramItemErbt von „ QGraphicsPolygonItem “ und stellt ein Element im Diagramm dar.
Definition der Klasse „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;
};Die Klasse „ MainWindow “ verwaltet den Rückgängig-Stapel, d. h., sie erstellt „ QUndoCommands“ und fügt diese dem Stapel hinzu bzw. entfernt sie wieder, wenn sie das Signal „ triggered() “ von „ undoAction “ und „ redoAction “ erhält.
Implementierung der Klasse „MainWindow“
Wir beginnen mit einem Blick auf den Konstruktor:
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();
}Im Konstruktor richten wir die `DiagramScene` und ` QGraphicsView` ein. Da „ deleteAction “ nur aktiviert sein soll, wenn wir ein Element ausgewählt haben, verbinden wir das Signal „ selectionChanged() “ der Szene mit dem Slot „ updateActions() “.
Hier ist die Funktion „ createUndoView() “:
void MainWindow::createUndoView()
{
QDockWidget *undoDockWidget = new QDockWidget;
undoDockWidget->setWindowTitle(tr("Command List"));
undoDockWidget->setWidget(new QUndoView(undoStack));
addDockWidget(Qt::RightDockWidgetArea, undoDockWidget);
}„ QUndoView “ ist ein Widget, das den Text, der mit der Funktion „ setText()“ festgelegt wird, für jeden „ QUndoCommand “ im Rückgängig-Stapel in einer Liste anzeigt. Wir fügen es in ein Andock-Widget ein.
Hier ist die Funktion „ 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);Die Funktion „ createActions() “ richtet alle Beispielaktionen wie oben gezeigt ein. Die Methoden „ createUndoAction()“ und „ createRedoAction()“ helfen uns dabei, Aktionen zu erstellen, die je nach Status des Stacks deaktiviert oder aktiviert werden. Außerdem wird der Text der Aktion automatisch basierend auf dem „ text()“ der Rückgängig-Befehle aktualisiert. Für die anderen Aktionen haben wir Slots in der Klasse „ MainWindow “ implementiert.
...
updateActions();
}
void MainWindow::updateActions()
{
deleteAction->setEnabled(!diagramScene->selectedItems().isEmpty());
}Sobald alle Aktionen erstellt sind, aktualisieren wir ihren Status, indem wir dieselbe Funktion aufrufen, die mit dem Signal „ selectionChanged “ der Szene verbunden ist.
Die Funktionen „ createMenus() “ und „ createToolBars() “ fügen die Aktionen zu Menüs und Symbolleisten hinzu:
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);
}Hier ist der „ itemMoved() “-Slot:
void MainWindow::itemMoved(DiagramItem *movedItem,
const QPointF &oldPosition)
{
undoStack->push(new MoveCommand(movedItem, oldPosition));
}Wir schieben einfach einen `MoveCommand` auf den Stack, der darauf die Funktion ` redo() ` aufruft.
Hier ist der Slot „ deleteItem() “:
void MainWindow::deleteItem()
{
if (diagramScene->selectedItems().isEmpty())
return;
QUndoCommand *deleteCommand = new DeleteCommand(diagramScene);
undoStack->push(deleteCommand);
}Ein Element muss ausgewählt sein, um gelöscht werden zu können. Wir müssen prüfen, ob es ausgewählt ist, da „ deleteAction “ möglicherweise auch dann aktiviert ist, wenn kein Element ausgewählt ist. Dies kann passieren, da wir kein Signal oder Ereignis abfangen, wenn ein Element ausgewählt wird.
Hier ist der Slot „ addBox() “:
void MainWindow::addBox()
{
QUndoCommand *addCommand = new AddCommand(DiagramItem::Box, diagramScene);
undoStack->push(addCommand);
}Die Funktion „ addBox() “ erstellt einen „AddCommand“ und fügt ihn dem Undo-Stack hinzu.
Hier ist der „ addTriangle() “-Slot:
void MainWindow::addTriangle()
{
QUndoCommand *addCommand = new AddCommand(DiagramItem::Triangle,
diagramScene);
undoStack->push(addCommand);
}Die Funktion „ addTriangle() “ erstellt einen „AddCommand“ und fügt ihn dem Undo-Stack hinzu.
Hier ist die Implementierung von „ about() “:
void MainWindow::about()
{
QMessageBox::about(this, tr("About Undo"),
tr("The <b>Undo</b> example demonstrates how to "
"use Qt's undo framework."));
}Der „about“-Slot wird von „ aboutAction “ ausgelöst und zeigt ein „Über“-Fenster für das Beispiel an.
Klassendefinition von „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;
};Die Klasse „ AddCommand “ fügt der „DiagramScene“ „DiagramItem“-Grafikelemente hinzu.
Implementierung der Klasse „AddCommand“
Wir beginnen mit dem Konstruktor:
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)));
}Zunächst erstellen wir das „DiagramItem“, das der „DiagramScene“ hinzugefügt werden soll. Die Funktion „ setText()“ ermöglicht es uns, eine „ QString “ festzulegen, die den Befehl beschreibt. Diese verwenden wir, um benutzerdefinierte Meldungen im „ QUndoView “ und im Menü des Hauptfensters anzuzeigen.
void AddCommand::undo()
{
myGraphicsScene->removeItem(myDiagramItem);
myGraphicsScene->update();
}undo() entfernt das Element aus der Szene.
void AddCommand::redo()
{
myGraphicsScene->addItem(myDiagramItem);
myDiagramItem->setPos(initialPosition);
myGraphicsScene->clearSelection();
myGraphicsScene->update();
}Wir legen die Position des Elements fest, da wir dies nicht im Konstruktor tun.
Definition der Klasse „DeleteCommand“
class DeleteCommand : public QUndoCommand
{
public:
explicit DeleteCommand(QGraphicsScene *graphicsScene, QUndoCommand *parent = nullptr);
void undo() override;
void redo() override;
private:
DiagramItem *myDiagramItem;
QGraphicsScene *myGraphicsScene;
};Die Klasse „DeleteCommand“ implementiert die Funktionalität zum Entfernen eines Elements aus der Szene.
Implementierung der Klasse „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())));
}Wir wissen, dass ein Element ausgewählt sein muss, da ein „DeleteCommand“ nur erstellt werden kann, wenn das zu löschende Element ausgewählt ist, und da zu jedem Zeitpunkt nur ein Element ausgewählt sein kann. Das Element muss abgewählt werden, wenn es wieder in die Szene eingefügt wird.
void DeleteCommand::undo()
{
myGraphicsScene->addItem(myDiagramItem);
myGraphicsScene->update();
}Das Element wird einfach wieder in die Szene eingefügt.
void DeleteCommand::redo()
{
myGraphicsScene->removeItem(myDiagramItem);
}Das Element wird aus der Szene entfernt.
Definition der Klasse „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;
};Die Methode „ mergeWith()“ wurde neu implementiert, um aufeinanderfolgende Bewegungen eines Objekts zu einem einzigen „MoveCommand“ zusammenzufassen, d. h., das Objekt wird an die Startposition der ersten Bewegung zurückbewegt.
Implementierung der Klasse „MoveCommand“
Der Konstruktor von „MoveCommand“ sieht wie folgt aus:
MoveCommand::MoveCommand(DiagramItem *diagramItem, const QPointF &oldPos,
QUndoCommand *parent)
: QUndoCommand(parent), myDiagramItem(diagramItem)
, myOldPos(oldPos), newPos(diagramItem->pos())
{
}Wir speichern sowohl die alte als auch die neue Position, um die Schritte rückgängig machen bzw. wiederherstellen zu können.
void MoveCommand::undo()
{
myDiagramItem->setPos(myOldPos);
myDiagramItem->scene()->update();
setText(QObject::tr("Move %1")
.arg(createCommandString(myDiagramItem, newPos)));
}Wir setzen einfach die alte Position des Objekts und aktualisieren die Szene.
void MoveCommand::redo()
{
myDiagramItem->setPos(newPos);
setText(QObject::tr("Move %1")
.arg(createCommandString(myDiagramItem, newPos)));
}Wir setzen das Element auf seine neue 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;
}Immer wenn ein „MoveCommand“ erstellt wird, wird diese Funktion aufgerufen, um zu prüfen, ob er mit dem vorherigen Befehl zusammengeführt werden soll. Es ist das Objekt des vorherigen Befehls, das auf dem Stack verbleibt. Die Funktion gibt „true“ zurück, wenn der Befehl zusammengeführt wird; andernfalls „false“.
Zunächst prüfen wir, ob es sich um dasselbe Element handelt, das zweimal verschoben wurde; in diesem Fall führen wir die Befehle zusammen. Wir aktualisieren die Position des Elements so, dass es beim Rückgängigmachen die letzte Position in der Verschiebungssequenz einnimmt.
Klassendefinition von „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;
};Die Klasse „DiagramScene“ implementiert die Funktionalität zum Verschieben eines „DiagramItem“ mit der Maus. Sie sendet ein Signal aus, wenn eine Verschiebung abgeschlossen ist. Dieses wird von der Funktion „ MainWindow “ abgefangen, die „MoveCommands“ erstellt. Wir gehen nicht näher auf die Implementierung von „DiagramScene“ ein, da sie sich ausschließlich mit Fragen des Grafik-Frameworks befasst.
Die Funktion „ main() “
Die Funktion „ main() “ des Programms sieht wie folgt aus:
int main(int argv, char *args[])
{
QApplication app(argv, args);
MainWindow mainWindow;
mainWindow.show();
return app.exec();
}Wir zeichnen ein Raster im Hintergrund der `DiagramScene`, daher verwenden wir eine Ressourcendatei. Der Rest der Funktion erstellt die ` MainWindow ` und zeigt sie als Fenster auf oberster Ebene an.
© 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.