网站首页 > 博客文章 正文
在Qt的撤销框架中,所有的用户动作的运行是从QUndoCommand类继承而来,通俗地说,撤销的类知道程序当中上一步与下一步的动作,用户希望的所有的表现形式都是通过一个QUndoStack来支配。Stack承载了所有的事件的执行,它根据事件依次地排列进来。在堆中包含了文档状态的前进与后退的命令,它直观地表现为撤销与重做。
在本例子中我们将通过Qt撤销框架来执行撤销/重做的功能。如下图所示为重新制作了一个图片的绘画过程,类似于排列积木。如果感觉排列的方式不好,可以在历史记录窗口中选择。
退回的节点,是为了可以删除其中的一条项目,这样,无论是箱体还是矩形,都可根据鼠标的移动放置在窗口上。撤销堆的信息在QUndoView显示出来。显示的信息包含了每一步移动的信息,通过选择QUndoView上的项目,做到用户所期望的显示。
一、类组成概述
例子是由下面的类组成。
- (1) MainWindow:主窗口类,用来管理主窗口将例子的部件排列,由用户或QUndoStack来建立命令,移动图形。
- (2) AddCommand:添加一个项目到场景当中。
- (3) DeleteCommand:从场景当中删除一个项。
- (4) MoveCommand:用来记录移动、开始与结束的位置,同时为redo()与undo()函数的调用提供依据。
- (5) DiagramScene:从QGraphicsScene继承,当一项移动时,发射信号给MoveCommands。
- (6) DiagramItem:从QGraphicsPolygonltem继承而来,功能是让绘图显示出来。
二、主窗口类
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
public slots:
void itemMoved(DiagramItem *movedDiagram,
const QPointF &moveStartPosition);
private slots:
void deleteItem();
void addBox();
void addTriangle();
void about();
void itemMenuAboutToShow();
void itemMenuAboutToHide();
private:
void createActions();
void createMenus();
void createUndoView();
QAction *deleteAction;
QAction *addBoxAction;
QAction *addTriangleAction;
QAction *undoAction;
QAction *redoAction;
QAction *exitAction;
QAction *aboutAction;
QMenu *fileMenu;
QMenu *editMenu;
QMenu *iteraMenu;
QMenu *helpMenu;
DiagramScene *diagramScene;
QUndoStack *undoStack;
QUndoView *undoView;
};
MainWindow类维持一个undo的堆,它建立一个QUndoCommands并在堆中弹出或者压入,弹出压入的命令需要获得撤销事件与重做事件发出triggered()信号才能进行。
下面是从一个构造函数开始的。
MainWindow::MainWindow(QWidget *parent):QMainWindow(parent)
{
undoStack = new QUndoStack(this);
createActions();
createMenus();
connect(undoStack, SIGNAL(canRedoChanged(bool)),
redoAction, SLOT(setEnabled(bool)));
connect(undoStack, SIGNAL(canUndoChanged(bool)),
undoAction, SLOT(setEnabled(bool)));
createUndoView();
diagramScene = new DiagramScene();
diagramScene->setSceneRect(QRect(0, 0, 500, 500));
connect(diagramScene, SIGNAL(itemMoved(DiagramItem *, const QPointF &)),
this, SLOT(itemMoved(DiagramItem *, const QPointF &)));
QGraphicsView *view = new QGraphicsView(diagramScene);
setCentralWidget(view);
setWindowTitle("UnRedo");
resize(700,500);
}
通过连接canRedoChanged()与canUndoChanged()信号到撤销与重做的动作槽,这样就实现了撤销与重做的命令,剩下的是在构造函数当中建立一个图表,下面是用来建设撤销的图表方法。
void MainWindow::createUndoView()
{
undoView = new QUndoView(undoStack);
undoView->setWindowTitle(tr("Command List"));
undoView->show();
undoView->setAttribute(Qt::WA_QuitOnClose, false);
}
QUndoView是用来显示文本的部件,用setText()函数来完成每个QUndoCommand,并在撤销堆当中提供列表。
下面介绍的是建立动作的函数。
void MainWindow::createActions()
{
deleteAction = new QAction(tr("删除图形"), this);
deleteAction->setShortcut(tr("Del"));
connect(deleteAction, SIGNAL(triggered()), this, SLOT(deleteItem()));
undoAction = new QAction(tr("恢复"), this);
undoAction->setShortcut(tr("Ctrl+Z"));
undoAction->setEnabled(false);
connect(undoAction, SIGNAL(triggered()), undoStack, SLOT(undo()));
redoAction = new QAction(tr("重做"), this);
QList<QKeySequence> redoShortcuts;
redoShortcuts << tr("Ctrl+Y") << tr("Shift+Ctrl+Z");
redoAction->setShortcuts(redoShortcuts);
redoAction->setEnabled(false);
connect(redoAction, SIGNAL(triggered()), undoStack, SLOT(redo()));
addBoxAction = new QAction(tr("添加矩形"),this);
connect(addBoxAction, SIGNAL(triggered()),this,SLOT(addBox()));
addTriangleAction = new QAction(tr("添加三角形"),this);
connect(addTriangleAction,SIGNAL(triggered()),this,SLOT(addTriangle()));
exitAction = new QAction(tr("退出"),this);
connect(exitAction,SIGNAL(triggered()),this,SLOT(close()));
aboutAction = new QAction(tr("关于"),this);
connect(aboutAction,SIGNAL(triggered()),this,SLOT(about()));
}
管理例子中所有的动作,这里使用了两种动作来做到对堆的直接访问,它们分别是undo()和redo()槽。当没有可以重做或撤销的堆时,它们就自动停止。
void MainWindow::createMenus()
{
fileMenu= menuBar()->addMenu(tr("文件"));
fileMenu->addAction(exitAction);
editMenu = menuBar()->addMenu(tr("编辑"));
editMenu->addAction(undoAction);
editMenu->addAction(redoAction);
editMenu->addSeparator();
editMenu->addAction(deleteAction);
connect(editMenu, SIGNAL(aboutToShow()), this, SLOT(itemMenuAboutToShow()));
connect(editMenu, SIGNAL(aboutToHide()), this, SLOT(itemMenuAboutToHide()));
itemMenu = menuBar()->addMenu(tr("图形"));
itemMenu->addAction(addBoxAction);
itemMenu->addAction(addTriangleAction);
helpMenu=menuBar()->addMenu(tr("帮助"));
helpMenu->addAction(aboutAction);
}
在这里通过两个槽来做到图形动作的撤销与重做,它们是通过选择小窗口中的列表项目实现的,这两个槽函数分别是aboutToShow()与aboutToHide()。
下面是itemMoved槽的创建方法。
void MainWindow::itemMoved(DiagramItem *movedItem, const QPointF &oldPosition)
{
undoStack->push(new MoveCommand(movedItem, oldPosition));
}
当一个选中的项目需要被删除掉,就需要判断这个删除动作是不是可以进行操作。它在完成删除动作的同时,不捕捉任何的信号或者事件。
下面包含了itemMenuAboutToShow()与itemMenuAboutToHide()槽。
void MainWindow::itemMenuAboutToHide()
{
deleteAction->setEnabled(true);
}
void MainWindow::itemMenuAboutToShow()
{
undoAction->setText(tr("Undo ") + undoStack->undoText());
redoAction->setText(tr("Redo ") + undoStack->redoText());
deleteAction->setEnabled(!diagramScene->selectedItems().isEmpty());
}
以上的两个函数是用来获得动态的项目菜单,两个槽分别被aboutToShow()与aboutToHide()信号连接。这里值得注意的是,需要判断删除的动作是不是已经没有任何的项目可以撤销或者重做,这样做的目的是给动作事件作为参考。
下面是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()函数是用来建立一个命令并把它推入到undo堆当中。
三、AddCommand类定义
class AddCommand : public QUndoCommand
{
public:
AddCommand(DiagramItem::DiagramType addType, QGraphicsScene *graphicsScene,
QUndoCommand *parent = 0);
~AddCommand();
void undo();
void redo();
private:
DiagramItem *myDiagramItem;
QGraphicsScene *myGraphicsScene;
QPointF initialPosition;
};
在DiagramScene当中提供了添加图表的功能。
AddCommand类的执行首先从构造函数开始。
AddCommand::AddCommand(DiagramItem::DiagramType addType,
QGraphicsScene *scene, QUndoCommand *parent)
:QUndoCommand(parent)
{
static int itemCount = 0;
myGraphicsScene = scene;
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)));
}
在这里先要建立一个图表到一个图形项目当中。setText()函数作为QString类型来使用,它用于描述一个命令。
void AddCommand::undo()
{
myGraphicsScene->removeItem(myDiagramItem);
myGraphicsScene->update();
}
void AddCommand::redo()
{
myGraphicsScene->addItem(myDiagramItem);
myDiagramItem->setPos(initialPosition);
myGraphicsScene->clearSelection();
myGraphicsScene->update();
}
undo()删除掉了项目中的图形。
四、DeleteCommand类定义
class DeleteCommand : public QUndoCommand
{
public:
DeleteCommand(QGraphicsScene *graphicsScene,
QUndoCommand *parent = 0);
~DeleteCommand();
void undo();
void redo();
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())));
}
通过判断来决定是否已经提供了向前或向后的显示,同时为用户定义一个列表,这个列表是用来选择向前或向后的项目,通过文本的设置,来获得当前项目的场景并显示出来。
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 = 0);
void undo();
void redo();
bool mergeWith(const QUndoCommand *command);
int id() const { return Id; }
private:
DiagramItem *myDiagramItem;
QPointF myOldPoa;
QPointF newPos;
};
在mergeWith()中,提供了连续项目的移动命令,它通过项目中提示信息,返回到第一个移动位置。
MoveCommand的构造函数如下。
MoveCommand::MoveCommand(DiagramItem *diagramItem,
const QPointF &oldPos, QUndoCommand *parent) : QUndoCommand(parent)
{
myDiagramItem = diagramItem;
newPos = diagramItem->pos();
myOldPos = oldPos;
}
在这里要设置一个新的与旧的位置,它为以后的撤销与重做提供了依据,重做与撤销的动作是分别保存的。
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被建立,就需要函数来检测前一个命令与当前命令的联合,前一个命令被压入到堆当中,函数将通过返回真的方法表明当前的命令已经被合并到堆当中,出现错误将返回false。
六、DiagramScene类定义
class DiagramScene : public QGraphicsScene
{
Q_OBJECT
public:
DiagramScene(QObject *parent = 0);
signals:
void itemMoved(DiagramItem *movedItem,
const QPointF &movedFromPosition);
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event);
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
private:
QGraphicsItem *movingItem;
QPointF oldPos;
};
DiagramScene用来执行了一个通过图表的移动来更改场景的方法,当一个鼠标移动结束,将会发送一个信号,它通过MainWindow来捕捉这个信号。同时还建立了一个MoveCommands。
七、主函数
程序的主函数方法如下。
int main(int argv, char *args[])
{
Q_INIT_RESOURCE(undoframework);
QApplication app(argv, args);
MainWindow mainWindow;
mainWindow.show();
return app.exec();
}
在这里使用了资源文件,同时将MainWindow作为一个顶级的窗口来使用。
——————————————————
对于本文实例完整代码有需要的朋友,可关注并在评论区留言!
猜你喜欢
- 2024-09-08 Qt 贪吃蛇制作(含源码)(qt编写贪吃蛇)
- 2024-09-08 qt 提示“启动程序失败,路径或者权限错误?”解决方法
- 2024-09-08 电脑文件不小心删除了怎么办?两个方法教你秒恢复
- 2024-09-08 Qt——内存回收(qti内存)
- 2024-09-08 C++跨平台库QT学习 操作Excel(跨平台c++开发工具)
- 2024-09-08 Qt 工程 pro文件(qt工程文件.pro在哪儿找)
- 2024-09-08 Qt中的快捷键汇总(qtcreator快捷键)
- 2024-09-08 Qt中文乱码解决思路(vscode终端中文乱码怎么解决)
- 2024-09-08 Qt5+VS2015编程实例:下拉列表框QComboBox控件使用
- 2024-09-08 Qt编程进阶(87):基于HTTP协议的网络文件下载
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- powershellfor (55)
- messagesource (56)
- aspose.pdf破解版 (56)
- promise.race (63)
- 2019cad序列号和密钥激活码 (62)
- window.performance (66)
- qt删除文件夹 (72)
- mysqlcaching_sha2_password (64)
- ubuntu升级gcc (58)
- nacos启动失败 (64)
- ssh-add (70)
- jwt漏洞 (58)
- macos14下载 (58)
- yarnnode (62)
- abstractqueuedsynchronizer (64)
- source~/.bashrc没有那个文件或目录 (65)
- springboot整合activiti工作流 (70)
- jmeter插件下载 (61)
- 抓包分析 (60)
- idea创建mavenweb项目 (65)
- vue回到顶部 (57)
- qcombobox样式表 (68)
- vue数组concat (56)
- tomcatundertow (58)
- pastemac (61)
本文暂时没有评论,来添加一个吧(●'◡'●)