Qt学习应用之路——Undo/Redo

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

程序运行结果如下:

Qt学习应用之路——Undo/Redo_第1张图片

执行Undo/Redo命令后的结果

Qt学习应用之路——Undo/Redo_第2张图片

PS:

这篇是我大学毕业后,参加工作后的第一篇技术性博客,之后会不断更新在工作中遇到的问题及其解决方法,此博客即作为同行的交流,也是对自己技术的一种回顾与积累。

Raymond/Ray










你可能感兴趣的:(Qt学习应用之路——Undo/Redo)