Undo 프레임워크 예제
이 예제는 Qt Undo 프레임워크를 사용하여 실행 취소/다시 실행 기능을 구현하는 방법을 보여줍니다.

Qt 실행 취소 프레임워크에서 사용자가 수행하는 모든 작업은 ` QUndoCommand`를 상속하는 클래스에서 구현됩니다. 실행 취소 명령 클래스는 작업을 ` redo()`(즉, 처음에 수행한 대로 되돌리기)하고 ` undo()`(다시 실행)하는 방법을 모두 알고 있습니다. 사용자가 수행하는 각 작업에 대해, 명령이 ` QUndoStack`에 추가됩니다. 이 스택에는 문서에서 실행된 모든 명령이 (시간 순서대로 쌓인 상태로) 포함되어 있으므로, 명령을 취소하거나 다시 실행함으로써 문서의 상태를 앞뒤로 되돌릴 수 있습니다. undo 프레임워크에 대한 개괄적인 소개는 개요 문서를 참조하십시오.
이 실행 취소 예제는 간단한 다이어그램 애플리케이션을 구현합니다. 상자나 직사각형 모양의 항목을 추가 및 삭제할 수 있으며, 마우스로 드래그하여 항목을 이동할 수도 있습니다. 실행 취소 스택은 QUndoView 에 표시되며, 이는 명령이 목록 항목으로 표시되는 목록입니다. 실행 취소 및 다시 실행 기능은 편집 메뉴를 통해 사용할 수 있습니다. 사용자는 실행 취소 뷰에서 명령을 직접 선택할 수도 있습니다.
다이어그램을 구현하기 위해 그래픽 뷰 프레임워크를 사용합니다. 이 프레임워크에는 자체 예제(예: Diagram Scene Example)가 있으므로 관련 코드는 간략하게만 다룹니다.
이 예제는 다음 클래스로 구성됩니다:
MainWindow는 메인 창이며 예제의 위젯을 배치합니다. 이 클래스는 사용자 입력을 기반으로 명령을 생성하고 명령 스택에 저장합니다.AddCommand는 씬에 항목을 추가합니다.DeleteCommand씬에서 항목을 삭제합니다.MoveCommand항목이 이동될 때 MoveCommand는 이동의 시작 및 종료 위치를 기록해 두었다가,redo()및undo()가 호출되면 이 위치 정보를 바탕으로 항목을 이동시킵니다.DiagramSceneQGraphicsScene 를 상속받으며, 항목이 이동될 때 에 대한 신호를 발생시킵니다.MoveComandsDiagramItemQGraphicsPolygonItem 를 상속하며 다이어그램에서 항목을 나타냅니다.
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;
};MainWindow 클래스는 실행 취소 스택을 관리합니다. 즉, QUndoCommand객체를 생성하고, undoAction 및 redoAction 에서 triggered() 신호를 수신하면 스택에 해당 객체를 푸시하고 팝합니다.
MainWindow 클래스 구현
먼저 생성자부터 살펴보겠습니다:
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();
}생성자에서 DiagramScene과 QGraphicsView 을 초기화합니다. 항목을 선택했을 때만 deleteAction 이 활성화되도록 하려면, 씬의 selectionChanged() 신호를 updateActions() 슬롯에 연결해야 합니다.
다음은 createUndoView() 함수입니다:
void MainWindow::createUndoView()
{
QDockWidget *undoDockWidget = new QDockWidget;
undoDockWidget->setWindowTitle(tr("Command List"));
undoDockWidget->setWidget(new QUndoView(undoStack));
addDockWidget(Qt::RightDockWidgetArea, undoDockWidget);
}QUndoView 는 undo 스택에 있는 각 QUndoCommand 에 대해 setText() 함수로 설정된 텍스트를 목록 형태로 표시하는 위젯입니다. 이 위젯을 도킹 위젯에 배치합니다.
다음은 ` 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);createActions() 함수는 위에서 보여준 방식대로 모든 예제 액션을 설정합니다. createUndoAction() 및 createRedoAction() 메서드는 스택의 상태에 따라 비활성화되거나 활성화되는 액션을 생성하는 데 도움을 줍니다. 또한, 액션의 텍스트는 실행 취소 명령의 text()에 따라 자동으로 업데이트됩니다. 다른 액션들의 경우, MainWindow 클래스에 슬롯을 구현했습니다.
...
updateActions();
}
void MainWindow::updateActions()
{
deleteAction->setEnabled(!diagramScene->selectedItems().isEmpty());
}모든 액션이 생성되면, 씬의 selectionChanged 신호에 연결된 동일한 함수를 호출하여 액션의 상태를 업데이트합니다.
createMenus() 및 createToolBars() 함수는 메뉴와 툴바에 액션을 추가합니다:
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);
}다음은 itemMoved() 슬롯입니다:
void MainWindow::itemMoved(DiagramItem *movedItem,
const QPointF &oldPosition)
{
undoStack->push(new MoveCommand(movedItem, oldPosition));
}단순히 스택에 MoveCommand를 푸시하면, 스택에서 redo() 가 호출됩니다.
다음은 ` deleteItem() ` 슬롯입니다:
void MainWindow::deleteItem()
{
if (diagramScene->selectedItems().isEmpty())
return;
QUndoCommand *deleteCommand = new DeleteCommand(diagramScene);
undoStack->push(deleteCommand);
}항목을 삭제하려면 해당 항목이 선택되어 있어야 합니다. 항목이 선택되지 않은 상태에서도 ` deleteAction `이 활성화될 수 있으므로, 항목이 선택되었는지 확인해야 합니다. 이는 항목이 선택될 때 신호나 이벤트를 감지하지 않기 때문에 발생할 수 있습니다.
다음은 ` addBox() ` 슬롯입니다:
void MainWindow::addBox()
{
QUndoCommand *addCommand = new AddCommand(DiagramItem::Box, diagramScene);
undoStack->push(addCommand);
}addBox() 함수는 AddCommand를 생성하여 이를 undo 스택에 삽입합니다.
다음은 ` addTriangle() ` 슬롯입니다:
void MainWindow::addTriangle()
{
QUndoCommand *addCommand = new AddCommand(DiagramItem::Triangle,
diagramScene);
undoStack->push(addCommand);
}addTriangle() 함수는 AddCommand를 생성하여 실행 취소 스택에 밀어 넣습니다.
다음은 about() 의 구현입니다:
void MainWindow::about()
{
QMessageBox::about(this, tr("About Undo"),
tr("The <b>Undo</b> example demonstrates how to "
"use Qt's undo framework."));
}about 슬롯은 aboutAction 에 의해 트리거되며, 예제에 대한 정보 상자를 표시합니다.
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;
};AddCommand 클래스는 DiagramScene에 DiagramItem 그래픽 항목을 추가합니다.
AddCommand 클래스 구현
먼저 생성자부터 살펴보겠습니다:
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)));
}먼저 DiagramScene에 추가할 DiagramItem을 생성합니다. setText() 함수를 통해 명령을 설명하는 QString 를 설정할 수 있습니다. 이를 사용하여 QUndoView 및 메인 창의 메뉴에 사용자 정의 메시지를 표시합니다.
void AddCommand::undo()
{
myGraphicsScene->removeItem(myDiagramItem);
myGraphicsScene->update();
}undo() 이 코드는 씬에서 항목을 제거합니다.
void AddCommand::redo()
{
myGraphicsScene->addItem(myDiagramItem);
myDiagramItem->setPos(initialPosition);
myGraphicsScene->clearSelection();
myGraphicsScene->update();
}생성자에서는 이 작업을 수행하지 않으므로 항목의 위치를 직접 설정합니다.
DeleteCommand 클래스 정의
class DeleteCommand : public QUndoCommand
{
public:
explicit DeleteCommand(QGraphicsScene *graphicsScene, QUndoCommand *parent = nullptr);
void undo() override;
void redo() override;
private:
DiagramItem *myDiagramItem;
QGraphicsScene *myGraphicsScene;
};DeleteCommand 클래스는 씬에서 항목을 제거하는 기능을 구현합니다.
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())));
}삭제할 항목이 선택되어 있지 않으면 DeleteCommand를 생성할 수 없고, 한 번에 하나의 항목만 선택할 수 있으므로 선택된 항목이 반드시 하나 있어야 함을 알고 있습니다. 항목을 다시 씬에 삽입할 때는 선택 상태를 해제해야 합니다.
void DeleteCommand::undo()
{
myGraphicsScene->addItem(myDiagramItem);
myGraphicsScene->update();
}항목은 단순히 씬에 다시 삽입됩니다.
void DeleteCommand::redo()
{
myGraphicsScene->removeItem(myDiagramItem);
}항목이 씬에서 제거됩니다.
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;
};mergeWith() 메서드는 항목의 연속적인 이동을 하나의 MoveCommand로 처리하도록 재구현되었습니다. 즉, 항목은 첫 번째 이동의 시작 위치로 되돌아가게 됩니다.
MoveCommand 클래스 구현
MoveCommand의 생성자는 다음과 같습니다:
MoveCommand::MoveCommand(DiagramItem *diagramItem, const QPointF &oldPos,
QUndoCommand *parent)
: QUndoCommand(parent), myDiagramItem(diagramItem)
, myOldPos(oldPos), newPos(diagramItem->pos())
{
}되돌리기(undo)와 다시 실행(redo)을 위해 이전 위치와 새로운 위치를 모두 저장합니다.
void MoveCommand::undo()
{
myDiagramItem->setPos(myOldPos);
myDiagramItem->scene()->update();
setText(QObject::tr("Move %1")
.arg(createCommandString(myDiagramItem, newPos)));
}단순히 항목의 이전 위치를 설정하고 씬을 업데이트합니다.
void MoveCommand::redo()
{
myDiagramItem->setPos(newPos);
setText(QObject::tr("Move %1")
.arg(createCommandString(myDiagramItem, newPos)));
}항목을 새로운 위치로 이동시킵니다.
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;
}MoveCommand가 생성될 때마다 이 함수가 호출되어 이전 명령과 병합되어야 하는지 확인합니다. 스택에 보관되는 것은 바로 이전 명령 객체입니다. 명령이 병합되면 이 함수는 true를 반환하고, 그렇지 않으면 false를 반환합니다.
먼저 동일한 항목이 두 번 이동된 것인지 확인하고, 해당 경우 명령을 병합합니다. 또한, 실행 취소 시 이동 순서에서 마지막 위치에 오도록 항목의 위치를 업데이트합니다.
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;
};DiagramScene은 마우스를 사용하여 DiagramItem을 이동하는 기능을 구현합니다. 이동이 완료되면 신호를 발생시킵니다. 이 신호는 MainWindow 에 의해 수신되며, 은 MoveCommand를 생성합니다. DiagramScene은 그래픽 프레임워크 관련 문제만을 다루므로 그 구현에 대해서는 자세히 살펴보지 않겠습니다.
main() 함수
프로그램의 ` main() ` 함수는 다음과 같습니다:
int main(int argv, char *args[])
{
QApplication app(argv, args);
MainWindow mainWindow;
mainWindow.show();
return app.exec();
}DiagramScene의 배경에 격자선을 그리기 위해 리소스 파일을 사용합니다. 함수의 나머지 부분은 MainWindow 를 생성하여 최상위 창으로 표시합니다.
© 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.