Qt和设计模式-画板的备忘录

  • 备忘录模式概述
    备忘录模式提供了一种状态恢复的实现机制,使得用户可以很方便的回到一个特定的历史状态,当前很多软件都提供了撤销(Undo)和恢复(反撤销)操作,其中就使用了备忘录模式。
  • 备忘录模式定义
    备忘录模式(Memento Pattern):在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。它是一种对象行为型模式,其别名为Token。
    本次需要实现一个画板绘画,支持撤销、反撤销动作。
    效果如下
    这里写图片描述
    图1 画板的撤销动作

    以下是实现部分:

  • 分析需求
    需要使用备忘录保存画板每一次绘画的所有坐标,以便可以保存。备忘录模式中的三个角色:Originator(原发器)、Memento(备忘录)、Caretaker(负责人)。在这里需要保存坐标状态的类就是原发器,也就是Widget画板。备忘录需要保存坐标列表作为一次绘画状态。负责人也就是管理者负责保存多个备忘录,以便恢复状态。

  • 具体实现
    需要重写QWidget的鼠标事件以便可以绘图

virtual void mousePressEvent(QMouseEvent *event);
virtual void mouseMoveEvent(QMouseEvent *event);
virtual void mouseReleaseEvent(QMouseEvent *event);
virtual void paintEvent(QPaintEvent *event);

绘图大致流程

Created with Raphaël 2.1.0 鼠标点击操作 鼠标左击? 记录鼠标移动坐标 松开左键绘制 绘制 yes no

所以为了需要将一次绘制状态保存,Caretaker类需要获取到每次的Memento,可以使用Qt的信号与槽机制,这里有至少两种方法可以采用:

  1. 通过信号与槽将Memento传出去
  2. 通过信号与槽,让Caretaker调用Widget(原发器)的save()接口(不能将Memento暴露给外界使用,只能Caretaker和Widget之间使用)。

恢复状态的实现同样的方法让Caretaker调用Widget的load()接口。

以下是具体实现步骤
Memento类 .h

//memento.cpp类实现比较简单 .cpp文件就不浪费篇幅了
//存储paint的画笔移动坐标(一次绘画的所有点集合)
class Memento
{
public:
    Memento(QList<QPoint> pointList);
    //一般情况下为了保证Memento只为某一个类备忘 C++中可以用友元实现
    //提供set 和 get接口破坏类封装(虽然友元也破坏了...)
    friend class Widget;

private:
    QList<QPoint> m_pointList;
};

MementoCaretaker类 .h or .cpp

//.h
class MementoCaretaker : public QObject
{
    Q_OBJECT
public:
    void push(Memento* m);  //保存状态
    Memento* undo();        //撤销保存(实际上已经保存在内存,可根据需求调整)
    Memento* restore();     //反撤销

private:
    QStack<Memento*> m_stack;         //保存所有状态
    static int       m_currentIndex;  //保存撤销和反撤销的位置
};

//.cpp
int MementoCaretaker::m_currentIndex = 0;

void MementoCaretaker::push(Memento *m)
{
    m_stack.push(m);
    m_currentIndex++;
}

Memento *MementoCaretaker::restore()
{
    if(m_currentIndex + 1 >= m_stack.size())
    {
        return nullptr;
    }
    m_currentIndex++;
    Memento* m = m_stack[m_currentIndex];
    return m;
}

Memento *MementoCaretaker::undo()
{
    if(m_currentIndex <= 0)
    {
        return nullptr;
    }
    m_currentIndex--;
    qDebug() << m_stack.size() << "  "<<m_currentIndex;
    Memento* m = m_stack[m_currentIndex];
    return m;
}

Widget类 .h or .cpp

//.h
class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = 0);
    ~Widget();

protected:
    virtual void mousePressEvent(QMouseEvent *event);
    virtual void mouseMoveEvent(QMouseEvent *event);
    virtual void mouseReleaseEvent(QMouseEvent *event);
    virtual void paintEvent(QPaintEvent *event);

private:
    bool          m_bPrint; //鼠标左击更为 true
    QList<QPoint> m_list;   //当前绘画状态


signals:
    void signal_memento();    //需要保存状态时 发出此信号

public slots:
    Memento* slot_save();       //外部接口
    void slot_load(Memento* m); //外部接口
};

//.cpp
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , m_bPrint(false)
{
    setWindowFlags(Qt::FramelessWindowHint);
}
Widget::~Widget()
{

}

Memento *Widget::slot_save()
{
    update();
    Memento *m = new Memento(m_list);
    //松开鼠标左键  证明需要保存 发出信号 外部调用该函数  然后完成绘制
    return m;
}

void Widget::slot_load(Memento* m)
{
    if(m == nullptr)
    {
        return;
    }
    m_list.clear();
    m_list = m->m_pointList;
    qDebug() <<"load";
    update();
}

void Widget::mousePressEvent(QMouseEvent *event)
{
    if(event->buttons() == Qt::LeftButton)
    {
        m_bPrint = true;
        //m_list.clear();
        //若想让线不是连接在一起  则需要导入整个Memento的列表
    }
}

void Widget::mouseMoveEvent(QMouseEvent *event)
{
    if(m_bPrint)
    {
        m_list.append(event->pos());
    }
}

void Widget::mouseReleaseEvent(QMouseEvent *event)
{
    if(m_bPrint)
    {
        m_bPrint = false;
        emit signal_memento();
        qDebug() << "updata";
    }
}

void Widget::paintEvent(QPaintEvent *event)
{
    if(!m_list.isEmpty())
    {
        QPainter p(this);
        p.setRenderHint(QPainter::Antialiasing);    //抗锯齿化
        p.setPen(QColor(250,24,0));

        qDebug() <<"line";
        for(int i = 0; i < m_list.size() - 1; i++)
        {
            p.drawLine(m_list.at(i), m_list.at(i+1));
        }
    }
    QWidget::paintEvent(event);
}

最后定义了一个Helper类测试使用
Helper类.h or .cpp

//.h
class Helper : public QWidget
{
public:
    Helper();

    void initLayout();  //初始化界面
    void init();        //初始化数据

private:
    Widget*           m_pPaintWidget;
    QPushButton*      m_pUndoButton;       //撤销
    QPushButton*      m_pRestoreButton;    //撤销恢复

    MementoCaretaker* m_pCaretaker;
};

//.cpp
Helper::Helper()
{
    initLayout();
    init();
}

void Helper::initLayout()
{
    m_pPaintWidget = new Widget;
    m_pPaintWidget->show();
    m_pUndoButton = new QPushButton("撤销", this);     //撤销
    m_pRestoreButton = new QPushButton("反撤销", this);    //撤销恢复

    QGridLayout *l = new QGridLayout;

    l->addWidget(m_pUndoButton);
    l->addWidget(m_pRestoreButton);
    setLayout(l);

    setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);
}

void Helper::init()
{
    m_pCaretaker = new MementoCaretaker;

    //保存状态
    connect(m_pPaintWidget, &Widget::signal_memento, [=]()
    {
        m_pCaretaker->push(m_pPaintWidget->slot_save());
    });

    //撤销
    connect(m_pUndoButton, &QPushButton::clicked, [=]()
    {
        m_pPaintWidget->slot_load(m_pCaretaker->undo());
    });

    //反撤销
    connect(m_pRestoreButton, &QPushButton::clicked, [=]()
    {
        m_pPaintWidget->slot_load(m_pCaretaker->restore());
    });
}

最终实现效果如图1所示。

  • 谈谈备忘录模式
    备忘录模式的封装:备忘录是一个比较特殊的对象,对备忘录的创建、控制等这些使用方法应该只有原发器拥有,而管理类只负责管理,其他类不能访问到备忘录,所以需要对备忘录封装。理想的情况是只允许生成该备忘录的那个原发器访问备忘录的内部状态。所以C++语言中可以使用friend,让原发器类和备忘录类成为友元类。在Java语言中可以将原发器类和备忘录类放在一个包中,让它们之间满足默认的包内可见性,也可以将备忘录类作为原发器类的内部类,只有原发器才可以访问备忘录中的数据,其他对象都无法使用备忘录中的数据。
  • 备忘录模式的优缺点
    提供了一种状态恢复的能力,外界无法访问到原发器和备忘录的具体数据(不用提供get/set方法给外界)实现了数据的封装。
    但是需要保存的状态多了,内存开销很大
  • 扩展
    支持撤销动作的大多数都可使用备忘录方法,或者使用命令模式实现。

新手一枚,有什么不对的,轻喷。。。

C++ Qt设计模式(第2版)是美国萨福克大学已使用十余年的经典教程,利用跨平台开源软件开发框架Qt阐释了C++和设计模式中的主要思想。全书共分四个部分:第一部分介绍C++、UML、Qt、模型-视图、SQL、XML、设计模式等基础知识,目的是为零基础的C++初学者铺垫一条学习面向对象编程的快捷之路;第二部分讲解内存访问、继承等重要的C++特性,是前一部分的延伸和拓展;第三部分使用Phonon编写了一个多媒体播放器,展示了主要技术理念的应用方法;附录部分给出了C++保留关键字、Debian和Qt程序开发环境的配置等内容。每节的练习题和各章后面的复习题,既可作为课堂上的讨论题,也可进一步启发读者对于关键知识点的思考。 C++ Qt设计模式(第2版)目录 第一部分 设计模式Qt 第1章 C++简介 2 第2章 类与对象 46 第3章 Qt简介 78 第4章 列表 85 第5章 函数 94 第6章 继承与多态 116 第7章 库与设计模式 163 第8章 QObject, QApplication,信号和槽 179 第9章 窗件和设计师 195 第10章 主窗口和动作 225 第11章 范型和容器 246 第12章 元对象,属性和反射编程 262 第13章 模型和视图 277 第14章 验证和正则表达式 302 第15章 XML解析 318 第16章 更多的设计模式 335 第17章 并发 353 第18章 数据库编程 376 第二部分 C++语言规范 第19章 类型与表达式 386 第20章 作用域与存储类 416 第21章 内存访问 431 第22章 继承详解 443 第三部分 编 程 作 业 第23章 MP3自动点唱机作业 456
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值