由于在工作中,需要用到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