总结:在mainwindows中添加一个QUndoStack和QUndoView。在构造函数中给edit[undoAction&redoAction]和view[Undo stack]添加不同的动作。
注意:edit中的undo和redo操作只需要添加响应的API就可以了,qt编辑器会自动实现。但是view中的动作需要自己将这个动作关联到一个槽函数实现:如果没有undoview视图就创建一个
将QUndoStack作为参数传递给scene的构造函数,同时用一个指针指向QUndoStack。
1、mainwindows
1.1、mainwindow.h中
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
/*https://blog.csdn.net/elf001/article/details/8980083*/
class Scene;
class QUndoStack;
class QUndoView;
#include <QMainWindow>
#include "scene.h"
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0);
~MainWindow();
private:
Scene* m_scene;
QUndoStack* m_undoStack; // 撤销堆栈
QUndoView* m_undoView; // 撤销堆栈视图
public slots: //添加槽方法的定义
void showMessage( QString ); // 在状态栏上显示消息
void showUndoStack(); // 打开一个新的撤销堆栈窗体
};
#endif // MAINWINDOW_H
1.2、mianwindow.cpp中
#include "mainwindow.h"
#include <QMenuBar>
#include <QStatusBar>
#include <QGraphicsView>
#include <QUndoStack>
#include <QUndoView>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
//添加菜单到菜单栏里面:添加下拉菜单
menuBar()->addMenu( "&File" );
QMenu* editMenu = menuBar()->addMenu( "&Edit" ); //将编辑获得视图的菜单项指针存储在本地变量里面
QMenu* viewMenu = menuBar()->addMenu( "&View" ); //因为以后要用到
menuBar()->addMenu( "&Simulate" );
menuBar()->addMenu( "&Help" );
//新建undo堆栈和联系菜单操作
m_undoStack = new QUndoStack(this);
m_undoView = 0;
viewMenu->addAction("Undo stack", this, SLOT(showUndoStack()));
//https://blog.csdn.net/ljhandlwt/article/details/52354139
QAction *undoAction = m_undoStack->createUndoAction(this);
QAction *redoAction = m_undoStack->createRedoAction(this);
undoAction->setShortcut( QKeySequence::Undo ); //Ctrl+Z, Alt+Backspace:setShortcuts()函数,用于说明快捷键
redoAction->setShortcut( QKeySequence::Redo ); //Ctrl+Y, Shift+Ctrl+Z:Qt的QKeySequence为我们定义了很多内置的快捷键
editMenu->addAction(undoAction);
editMenu->addAction(redoAction); //在edit中添加两个动作
statusBar()->showMessage("QSimulate has started");
// 创建场景和显示场景的中央视图部件
m_scene = new Scene(m_undoStack); //View是视图,负责显示;Scene是文档,负责存储数据。所以从这个角度出发,我们可以这样认为,一个Scene可以关联到多个View,就好比一份数据可以有多个视图去查看它一样。
QGraphicsView* view = new QGraphicsView( m_scene ); //QGraphicsScene是一个视图,它不能够单独存在,必须关联到至少一个QGraphicsView
view->setAlignment( Qt::AlignLeft | Qt::AlignTop );
view->setFrameStyle( 0 );
setCentralWidget( view );
//将信号与槽关联:m_scene发射message信号时,MainWindow接收信号,执行showMessage槽函数
connect( m_scene, SIGNAL(message(QString)), this, SLOT(showMessage(QString)) );
}
MainWindow::~MainWindow()
{
}
void MainWindow::showMessage( QString msg )
{
statusBar()->showMessage( msg ); // 在主窗口状态栏上显示消息
}
void MainWindow::showUndoStack()
{
if(m_undoView == 0) //判断撤销堆栈视图是否被创建
{
m_undoView = new QUndoView(m_undoStack); //创建并且设置窗体标题
m_undoView->setWindowTitle("QSimulate - Undo stack");
m_undoView->setAttribute( Qt::WA_QuitOnClose, false ); //撤销堆栈视图被用户关闭应用程序也不会退出
}
m_undoView->show();
}
2、Scene
2.1、Scene.h
#ifndef SCENE_H
#define SCENE_H
class QGraphicsSceneMouseEvent;
class QUndoStack; //添加类QUndoStack的前置定义。
#include <QGraphicsScene>
#include "sation.h"
class Scene : public QGraphicsScene
{
Q_OBJECT
public:
Scene( QUndoStack* ); // 构造函数接受一个QUndoStack指针参数。
signals:
void message( QString ); // 文本消息信号
protected:
void mousePressEvent( QGraphicsSceneMouseEvent* ); // 接收鼠标按下事件
void contextMenuEvent( QGraphicsSceneContextMenuEvent* ); // 接收上下文菜单事件
private:
QUndoStack* m_undoStack; // 撤销堆栈存储指针
};
#endif // SCENE_H
2.2、Scene.cpp
#include "scene.h"
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsSceneContextMenuEvent>
#include <QMenu>
#include <QAction>
#include <QUndoStack>
Scene::Scene( QUndoStack* undoStack ) : QGraphicsScene()
{
addLine( 0, 0, 0, 1, QPen(Qt::transparent, 1) );
m_undoStack = undoStack;
}
void Scene::mousePressEvent( QGraphicsSceneMouseEvent* event )
{
// 设置本地变量和检查被选中的电台是否存在
qreal x = event->scenePos().x();
qreal y = event->scenePos().y();
QTransform transform;
Sation *station = dynamic_cast<Sation *>(this->itemAt(QPointF(x, y), transform)); //pointf返回一个基类,现在让他指向派生类
// 如果电台没有被选中并且鼠标左键被按下, 创建一个新电台
if ( station == 0 && event->button() == Qt::LeftButton )
{
addItem( new Sation( x, y ) );
emit message( QString("Station add at %1,%2").arg(x).arg(y) );
}
// 调用基类的mousePressEvent处理其它鼠标按下事件
QGraphicsScene::mousePressEvent( event );
}
/*
* 添加新方法contextMenuEvent的代码。当用户在场景里按下鼠标右键,这个方法将被调用。我们只想用户指向一个电台时才会显示一个上下文菜单。
* 然后我们创建一个只有删除电台选项的菜单。
* 如果用户选择这个选项,电台将会从场景中删除,并且从内存中删除,和发送一条消息。
*/
void Scene::contextMenuEvent( QGraphicsSceneContextMenuEvent* event )
{
// 我们只想当用户选择一个电台时才显示菜单
qreal x = event->scenePos().x();
qreal y = event->scenePos().y();
QTransform transform;
Sation *station = dynamic_cast<Sation *>(this->itemAt(QPointF(x, y), transform)); //pointf返回一个基类,现在让他指向派生类
if(0 == station) return;
// 显示上下文和相应的动作
QMenu menu;
QAction* deleteAction = menu.addAction("Delete Station");
if ( menu.exec( event->screenPos() ) == deleteAction )
{
removeItem( station );
delete station;
emit message( QString("Station deleted at %1,%2").arg(x).arg(y) );
}
}
3、其余不变:这里的undo关联的是secne,和item没有关系
3.1、sation.h
#ifndef SATION_H
#define SATION_H
#include <QGraphicsItem>
/*新实现一个sation类,这个类可以在scene中用,也可以在mainwindow中用*/
class Sation : public QGraphicsItem
{
public:
Sation(qreal, qreal);
void paint(QPainter*, // // paint虚函数:绘制图标
const QStyleOptionGraphicsItem*,
QWidget*
);
QRectF boundingRect() const{ // boundingRect虚函数:定义每个图标对应的外部边框
return QRectF(-6.5, -13, 13, 18);//返回一个矩形:比我们要画的图标稍大
}
};
#endif //
SATION_H
3.2、sation.cpp
#include "sation.h"
#include <QPainter>
Sation::Sation(qreal x, qreal y):QGraphicsItem()
{
setPos(x, y);
setFlags(QGraphicsItem::ItemIsMovable |
QGraphicsItem::ItemIsSelectable |
QGraphicsItem::ItemIgnoresTransformations
);
}
//构造函数里我们根据x和y传递的参数设置坐标。在paint函数绘制我们的电台图标。
void Sation::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
//绘制电台图标,必须小于边框矩形
painter->setRenderHint( QPainter::Antialiasing );
painter->setPen( QPen( Qt::black, 2 ) );
painter->drawRect( -4, -3, 8, 7 ); //画一个矩形:以鼠标点击的位置作为起始中心
painter->drawLine( 0, -4, 0, -11 );
painter->drawLine( -5, -11, 0, -6 );
painter->drawLine( +5, -11, 0, -6 );
}
4、main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}