QUndoCommand 是 Qt 提供的用于实现撤销(undo)和重做(redo)功能的基类,它是 Qt 的 Undo Framework 的核心组件。其表示一个可以执行、撤销和重做的单一命令或操作。通过将应用程序的操作封装为 QUndoCommand 的子类,可以轻松实现撤销/重做功能。
核心概念
-
QUndoCommand:每个命令都是
QUndoCommand
类的一个实例,它代表了一个可以撤销或重做的操作。你需要继承这个类并实现undo()
和redo()
方法来定义具体的操作行为。 -
QUndoStack:这是一个命令的栈,用于存储和管理一系列的
QUndoCommand
。你可以将命令压入栈中(通过调用push()
方法),然后利用栈的特性轻松地执行撤销和重做操作。 -
QUndoGroup:如果你的应用程序有多个撤销堆栈(例如,在不同的视图或文档中有各自的撤销堆栈),你可以使用
QUndoGroup
来管理和切换这些堆栈。
实现步骤
-
创建自定义命令:
- 继承
QUndoCommand
并实现undo()
和redo()
方法。 - 在构造函数中,通常需要保存执行此命令所需的数据状态,以便于后续的撤销和重做操作。
- 继承
-
推入命令到栈:
- 使用
QUndoStack::push(QUndoCommand *command)
方法将你的命令对象添加到撤销栈中。一旦命令被推入栈中,它就会立即被执行(即redo()
被调用)。
- 使用
-
提供用户界面:
- 为用户提供一种方式来触发撤销和重做操作,这通常是通过菜单项或工具栏按钮实现的。可以连接
QUndoStack
的undo()
和redo()
信号到相应的槽函数上。
- 为用户提供一种方式来触发撤销和重做操作,这通常是通过菜单项或工具栏按钮实现的。可以连接
基本用法
1. 创建自定义命令类
#include <QUndoCommand>
class AddItemCommand : public QUndoCommand
{
public:
AddItemCommand(MyModel *model, const QModelIndex &parent, const QString &text, QUndoCommand *parent = nullptr)
: QUndoCommand(parent), m_model(model), m_parent(parent), m_text(text)
{
setText("Add item"); // 设置命令的描述文本
}
void undo() override
{
m_model->removeRow(m_row, m_parent);
}
void redo() override
{
m_row = m_model->addItem(m_parent, m_text);
}
private:
MyModel *m_model;
QModelIndex m_parent;
QString m_text;
int m_row;
};
2. 使用 QUndoStack 管理命令
#include <QUndoStack>
// 创建撤销栈
QUndoStack *undoStack = new QUndoStack(this);
// 执行命令
undoStack->push(new AddItemCommand(model, parentIndex, "New Item"));
// 撤销
undoStack->undo();
// 重做
undoStack->redo();
高级功能
1. 命令合并
class ChangeTextCommand : public QUndoCommand
{
public:
// ...
int id() const override { return 1; } // 必须实现id()才能合并
bool mergeWith(const QUndoCommand *other) override
{
if (other->id() != id())
return false;
// 合并逻辑
m_newText = static_cast<const ChangeTextCommand*>(other)->m_newText;
return true;
}
// ...
};
2. 宏命令(组合命令)
QUndoCommand *macroCmd = new QUndoCommand("Complex operation");
new AddItemCommand(model, parentIndex, "Item 1", macroCmd);
new AddItemCommand(model, parentIndex, "Item 2", macroCmd);
new AddItemCommand(model, parentIndex, "Item 3", macroCmd);
undoStack->push(macroCmd); // 作为一个整体撤销/重做
3. 与 UI 集成
// 创建撤销/重做动作
QAction *undoAction = undoStack->createUndoAction(this, tr("&Undo"));
undoAction->setShortcut(QKeySequence::Undo);
QAction *redoAction = undoStack->createRedoAction(this, tr("&Redo"));
redoAction->setShortcut(QKeySequence::Redo);
// 添加到菜单
menu->addAction(undoAction);
menu->addAction(redoAction);
实际应用示例
自定义命令类
#ifndef COMMANDS
#define COMMANDS
#include <QUndoCommand>
#include "drawscene.h"
class MoveShapeCommand : public QUndoCommand
{
public:
MoveShapeCommand(QGraphicsScene *graphicsScene, const QPointF & delta ,
QUndoCommand * parent = 0);
MoveShapeCommand(QGraphicsItem * item, const QPointF & delta , QUndoCommand * parent = 0);
void undo() Q_DECL_OVERRIDE;
void redo() Q_DECL_OVERRIDE;
private:
QGraphicsScene *myGraphicsScene;
QGraphicsItem *myItem;
QList<QGraphicsItem *> myItems;
QPointF myDelta;
bool bMoved;
};
class ResizeShapeCommand : public QUndoCommand
{
public:
enum { Id = 1234, };
ResizeShapeCommand(QGraphicsItem * item ,
int handle,
const QPointF& scale,
QUndoCommand *parent = 0 );
void undo() Q_DECL_OVERRIDE;
void redo() Q_DECL_OVERRIDE;
bool mergeWith(const QUndoCommand *command) Q_DECL_OVERRIDE;
int id() const Q_DECL_OVERRIDE { return Id; }
private:
QGraphicsItem *myItem;
int handle_;
int opposite_;
QPointF scale_;
bool bResized;
};
class ControlShapeCommand : public QUndoCommand
{
public:
enum { Id = 1235, };
ControlShapeCommand(QGraphicsItem * item ,
int handle,
const QPointF& newPos,
const QPointF& lastPos,
QUndoCommand *parent = 0 );
void undo() Q_DECL_OVERRIDE;
void redo() Q_DECL_OVERRIDE;
bool mergeWith(const QUndoCommand *command) Q_DECL_OVERRIDE;
int id() const Q_DECL_OVERRIDE { return Id; }
private:
QGraphicsItem *myItem;
int handle_;
QPointF lastPos_;
QPointF newPos_;
bool bControled;
};
class RotateShapeCommand : public QUndoCommand
{
public:
RotateShapeCommand(QGraphicsItem *item , const qreal oldAngle ,
QUndoCommand * parent = 0);
void undo() Q_DECL_OVERRIDE;
void redo() Q_DECL_OVERRIDE;
private:
QGraphicsItem *myItem;
qreal myOldAngle;
qreal newAngle;
};
class RemoveShapeCommand : public QUndoCommand
{
public:
explicit RemoveShapeCommand(QGraphicsScene *graphicsScene, QUndoCommand *parent = 0);
~RemoveShapeCommand();
void undo() Q_DECL_OVERRIDE;
void redo() Q_DECL_OVERRIDE;
private:
QList<QGraphicsItem *> items;
QGraphicsScene *myGraphicsScene;
};
class GroupShapeCommand : public QUndoCommand
{
public:
explicit GroupShapeCommand( QGraphicsItemGroup * group, QGraphicsScene *graphicsScene,
QUndoCommand *parent = 0);
void undo() Q_DECL_OVERRIDE;
void redo() Q_DECL_OVERRIDE;
private:
QList<QGraphicsItem *> items;
QGraphicsItemGroup * myGroup;
QGraphicsScene *myGraphicsScene;
bool b_undo;
};
class UnGroupShapeCommand : public QUndoCommand
{
public:
explicit UnGroupShapeCommand( QGraphicsItemGroup * group, QGraphicsScene *graphicsScene,
QUndoCommand *parent = 0);
void undo() Q_DECL_OVERRIDE;
void redo() Q_DECL_OVERRIDE;
private:
QList<QGraphicsItem *> items;
QGraphicsItemGroup * myGroup;
QGraphicsScene *myGraphicsScene;
};
class AddShapeCommand : public QUndoCommand
{
public:
AddShapeCommand(QGraphicsItem *item , QGraphicsScene *graphicsScene,
QUndoCommand *parent = 0);
~AddShapeCommand();
void undo() Q_DECL_OVERRIDE;
void redo() Q_DECL_OVERRIDE;
private:
QGraphicsItem *myDiagramItem;
QGraphicsScene *myGraphicsScene;
QPointF initialPosition;
};
QString createCommandString(QGraphicsItem *item, const QPointF &point);
#endif // COMMANDS
UI集成与呈现
void MainWindow::itemMoved(QGraphicsItem *item, const QPointF &oldPosition)
{
Q_UNUSED(item);
if (!activeMdiChild()) return ;
activeMdiChild()->setModified(true);
if ( item ){
QUndoCommand *moveCommand = new MoveShapeCommand(item, oldPosition);
undoStack->push(moveCommand);
}else{
QUndoCommand *moveCommand = new MoveShapeCommand(activeMdiChild()->scene(), oldPosition);
undoStack->push(moveCommand);
}
}
void MainWindow::itemAdded(QGraphicsItem *item)
{
if (!activeMdiChild()) return ;
activeMdiChild()->setModified(true);
QUndoCommand *addCommand = new AddShapeCommand(item, item->scene());
undoStack->push(addCommand);
}
void MainWindow::itemRotate(QGraphicsItem *item, const qreal oldAngle)
{
if (!activeMdiChild()) return ;
activeMdiChild()->setModified(true);
QUndoCommand *rotateCommand = new RotateShapeCommand(item , oldAngle);
undoStack->push(rotateCommand);
}
void MainWindow::itemResize(QGraphicsItem *item, int handle, const QPointF& scale)
{
if (!activeMdiChild()) return ;
activeMdiChild()->setModified(true);
QUndoCommand *resizeCommand = new ResizeShapeCommand(item ,handle, scale );
undoStack->push(resizeCommand);
}
void MainWindow::itemControl(QGraphicsItem *item, int handle, const QPointF & newPos ,const QPointF &lastPos_)
{
if (!activeMdiChild()) return ;
activeMdiChild()->setModified(true);
QUndoCommand *controlCommand = new ControlShapeCommand(item ,handle, newPos, lastPos_ );
undoStack->push(controlCommand);
}
void MainWindow::deleteItem()
{
qDebug()<<"deleteItem";
if (!activeMdiChild()) return ;
QGraphicsScene * scene = activeMdiChild()->scene();
activeMdiChild()->setModified(true);
if (scene->selectedItems().isEmpty())
return;
QUndoCommand *deleteCommand = new RemoveShapeCommand(scene);
undoStack->push(deleteCommand);
}
QUndoCommand 提供了一种结构化的方式来实现应用程序的撤销/重做功能,是开发专业级应用程序的重要工具。实际开发中的一些参考点:
-
保持命令轻量:命令对象应该只存储执行和撤销操作所需的最小数据
-
明确命令边界:每个命令应该代表一个逻辑上的用户操作
-
合理使用合并:对连续的同类型小操作进行合并(如连续的文字编辑)
-
处理异常情况:确保命令在undo/redo时能处理对象已被删除等情况
-
提供有意义的描述:setText()设置的文本应该能清晰描述操作