由于在工作中,需要用到Qt的Redo/Undo功能,经过对Qt提供的Example中的undoframework的研究,可以通过Qt中的QUndoStack和QUndoCommand类可以很方便地实现Redo/Undo功能。
提炼出了例子中的AddBox动作,以说明如何使用QUndoStack和QUndoCommand类,其代码如下:
//"main.cpp"
#include <QtWidgets>
#include "mainwindow.h"
int main(int argv, char *args[])
{
QApplication app(argv, args);
MainWindow mw;
mw.show();
return app.exec();
}
//"diagramitem.h"
#ifndef DIAGRAMITEM_H
#define DIAGRAMITEM_H
#include <QGraphicsPolygonItem>
QT_BEGIN_NAMESPACE
class QGraphicsItem;
class QGraphicsScene;
class QGraphicsSceneMouseEvent;
class QPointF;
QT_END_NAMESPACE
class DiagramItem : public QGraphicsPolygonItem
{
public:
enum { Type = UserType + 1 };
enum DiagramType { Box, Triangle };
explicit DiagramItem(DiagramType diagramType, QGraphicsItem *item = 0);
DiagramType diagramType() const {
return polygon() == boxPolygon ? Box : Triangle;
}
int type() const { return Type; }
private:
QPolygonF boxPolygon;
QPolygonF trianglePolygon;
};
#endif
//"diagramitem.cpp"
#include <QtWidgets>
#include "diagramitem.h"
DiagramItem::DiagramItem(DiagramType diagramType, QGraphicsItem *item)
: QGraphicsPolygonItem(item)
{
if (diagramType == Box) {
boxPolygon << QPointF(0, 0) << QPointF(0, 30) << QPointF(30, 30)
<< QPointF(30, 0) << QPointF(0, 0);
setPolygon(boxPolygon);
} else {
trianglePolygon << QPointF(15, 0) << QPointF(30, 30) << QPointF(0, 30)
<< QPointF(15, 0);
setPolygon(trianglePolygon);
}
QColor color(static_cast<int>(qrand()) % 256,
static_cast<int>(qrand()) % 256, static_cast<int>(qrand()) % 256);
QBrush brush(color);
setBrush(brush);
setFlag(QGraphicsItem::ItemIsSelectable);
setFlag(QGraphicsItem::ItemIsMovable);
}
//diagramscene.h
#ifndef DIAGRAMSCENE_H
#define DIAGRAMSCENE_H
#include <QObject>
#include <QGraphicsScene>
class DiagramItem;
QT_BEGIN_NAMESPACE
class QGraphicsSceneDragDropEvent;
class QGraphicsViewItem;
QT_END_NAMESPACE
//! [0]
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;
};
//! [0]
#endif
//diagramscene.cpp
#include <QtWidgets >
#include "diagramitem.h"
#include "diagramscene.h"
DiagramScene::DiagramScene(QObject *parent) : QGraphicsScene(parent)
{
movingItem = 0;
}
void DiagramScene::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
QPointF mousePos(event->buttonDownScenePos(Qt::LeftButton).x(),
event->buttonDownScenePos(Qt::LeftButton).y());
const QList<QGraphicsItem *> itemList = items(mousePos);
movingItem = itemList.isEmpty() ? 0 : itemList.first();
if (movingItem != 0 && event->button() == Qt::LeftButton) {
oldPos = movingItem->pos();
}
clearSelection();
QGraphicsScene::mousePressEvent(event);
}
void DiagramScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
if (movingItem != 0 && event->button() == Qt::LeftButton) {
if (oldPos != movingItem->pos())
emit itemMoved(qgraphicsitem_cast<DiagramItem *>(movingItem),
oldPos);
movingItem = 0;
}
QGraphicsScene::mouseReleaseEvent(event);
}
//commands.h
#pragma once
#include <QUndoCommand>
#include "diagramitem.h"
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;
};
QString createCommandString(DiagramItem *item, const QPointF &point);
//commands.cpp
#include <QtWidgets >
#include "commands.h"
#include "diagramitem.h"
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)));
}
AddCommand::~AddCommand()
{
if(!myDiagramItem->scene())
{
delete myDiagramItem;
}
}
void AddCommand::undo()
{
myGraphicsScene->removeItem(myDiagramItem);
myGraphicsScene->update();
}
void AddCommand::redo()
{
myGraphicsScene->addItem(myDiagramItem);
myDiagramItem->setPos(initialPosition);
myGraphicsScene->clearSelection();
myGraphicsScene->update();
}
QString createCommandString(DiagramItem *item, const QPointF &pos)
{
return QObject::tr("%1 at (%2, %3)")
.arg(item->diagramType() == DiagramItem::Box ? "Box" : "Triangle")
.arg(pos.x()).arg(pos.y());
}
//mainwindow.h
#pragma once
#include <QMainWindow>
class QAction;
class QMenu;
class QUndoStack;
class QUndoView;
class DiagramScene;
class DiagramItem;
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow();
~MainWindow();
private slots:
void addBox();
private:
void createActions();
void createMenus();
void createUndoView();
QAction *addBoxAction;
QAction *undoAction;
QAction *redoAction;
QMenu *editMenu;
QMenu *itemMenu;
DiagramScene *diagramScene;
QUndoStack *undoStack;
QUndoView *undoView;
};
//mainwindow.cpp
#include <QtWidgets>
#include "mainwindow.h"
#include "diagramscene.h"
#include "diagramitem.h"
#include "commands.h"
MainWindow::MainWindow()
{
undoStack = new QUndoStack(this);
createActions();
createMenus();
createUndoView();
diagramScene = new DiagramScene();
diagramScene->setSceneRect(QRect(0, 0, 500, 500));
QGraphicsView *view = new QGraphicsView(diagramScene);
setCentralWidget(view);
setWindowTitle("UnRedo");
resize(700,500);
}
MainWindow::~MainWindow()
{}
void MainWindow::createActions()
{
addBoxAction = new QAction("Add Box",this);
connect(addBoxAction,SIGNAL(triggered()),this,SLOT(addBox()));
undoAction = undoStack->createUndoAction(this,"Undo");
redoAction = undoStack->createRedoAction(this,"Redo");
}
void MainWindow::createMenus()
{
editMenu = menuBar()->addMenu("Edit");
editMenu->addAction(undoAction);
editMenu->addAction(redoAction);
itemMenu = menuBar()->addMenu("Item");
itemMenu->addAction(addBoxAction);
}
void MainWindow::createUndoView()
{
undoView = new QUndoView(undoStack);
undoView->setWindowTitle("Command List");
undoView->show();
undoView->setAttribute(Qt::WA_QuitOnClose, false);
}
void MainWindow::addBox()
{
QUndoCommand *addCommand = new AddCommand(DiagramItem::Box, diagramScene);
undoStack->push(addCommand);
}
程序运行结果如下:

执行Undo/Redo命令后的结果

PS:
这篇是我大学毕业后,参加工作后的第一篇技术性博客,之后会不断更新在工作中遇到的问题及其解决方法,此博客即作为同行的交流,也是对自己技术的一种回顾与积累。
Raymond/Ray
本文详细介绍了如何使用Qt的QUndoStack和QUndoCommand类实现Redo/Undo功能,通过实例化AddBox动作来展示操作过程。文章提供了一个完整的实现流程,包括创建场景、命令、菜单和视图等关键组件。

610

被折叠的 条评论
为什么被折叠?



