手把手教你写Undo、Redo程序(续)

手把手教你写UndoRedo程序(续)

    在第一篇文章“手把手教你写UndoRedo程序”里,我介绍了如何实现一个基于图像操作的Undo, Redo框架结构。但是我们现在所讲的还只是Undo类的结构。还有一个非常重要的部分没有说明:我们的这些Undo类应该在什么地方使用呢?

    还记得我们的第一个CImageData类吗?在ExcuteOperation函数的说明曾提到需要修改函数的参数。现在就是需要修改的时候了。

bool CImageData::ExecuteOperation(CImageOperation * pCmd, CUndoData * pData)

{

    pCmd->Execute(this, pData);

}

bool CImageOperation::Execute(CImageData * pData, CUndoData *pData)

{

    if (pData)

    {

        // 如果外部传入的Undo数据接口pData不空

        // 我们就在进行真正的操作前先用pData构造好Undo数据

    }

    // 根据不同的派生类修改pData的图像数据

}

    下面我们还是以图像旋转这个具体操作来看如何实现CImageRotate

class CImageRatate : public CImageOperation

{

public:

    CImageRatate(float  fAngle) : m_fRotateAngel(fAngle) {}

 

    virtual bool    Execute(CImageData * pData, CUndoData * pData)

    {

        if (pData)  // 根据不同的操作类型,pData实际上指向的是不同的派生类

            pData->m_fRotateAngle = -m_fRotateAngle;

        pData->Rotate(m_fRotateAngle);

        // 这里假定Rotate函数是一个旋转图像的成员函数

    }

private:

    float       m_fRotateAngle;

};

 

在这种结构里有许多值得改进的地方。下面我就三个方面对前面介绍的结构进行改进:

·支持单步操作的多级Undo/Redo

·支持基于图像SelectUndo/Redo

·实现Undo数据的磁盘保存

    下面分别对每个方面进行单独说明。

 

1 单步操作的多级Undo/Redo

    先看看什么是单步操作的多级Undo/Redo: 在一些比较特殊的软件中,软件提供给用户的是执行某一步操作,但是在内部程序实现的时候会包含多个子操作。这多个子操作就形成了多级的Undo/Redo。当然,这种情况比较特殊,比如在绣花编辑软件中,用户的针脚操作在内部可能需要多个子操作,这样才能方便用户在后来根据子操作进行修改(比如针脚编辑).

    特别说明:我下面所做的改进只能是体现了单步操作的多级Undo/Redo实现思想。之所以这么做主要是为了能利用前面的框架。为了把多个子操作的Undo/Redo封装起来。我们还需要一个Undo接口类。

它的基本实现方法如下:

class CUndoInfo

{

public:

    // 默认值直接设置为1,因为m_pSaved至少有一个

    CUndoInfo() : m_UndoNum(1) {}

    virtual ~CUndoInfo() {}

   

    virtual bool UndoAction(CImageData* pData)

    {

        do{

            m_pSaved[m_UndoNum-1].UndoAction(pData);

            m_UndoNum--;

        }while(m_UndoNum>0 );

        return true;

    }

    // 这里的CUndoData指针实际上可以是一个指针列表, 表示多个子操作

    CUndoData *     m_pSaved;

    // 表示m_pSaved保存的指针个数

    unsigned int    m_UndoNum;

};

    现在我们已经有了新的Undo接口类,所以给外部使用的文档接口类也需要改变。现在我们只需要把CUndoList中所有的CUndoData全部替换为CUndoInfo即可。怎么样,还算比较简单吧!

 

2 基于图像SelectUndo/Redo

    下面就实现带SelectUndo/Redo功能。在图像编辑时,如果用户在进行操作时,先选择了部分图像。那么我们在保存数据时就应该只保存修改的部分数据。这样才能节省空间,提高效率。

 

    首先我们需要在添加一个矩形值成员变量,用来保存操作的范围。这个矩形值的保存位置有两个:如果你不想对前面定义的类做过多的修改,这个矩形值可直接放入CUndoInfo类;另一个位置是保存在CUndoData

    如果保存在CUndoInfo,我们就只需在CUndoInfo::UndoAction进行简单修改。

class CUndoInfo

{

    // 保持原来的定义不变

    RECT    m_Rect;

    virtual bool    UndoAction(CImageData * pData)

    {

        // 根据m_Rect的值修改pData,也就是取pData部分数据形成新的pData

        // 然后用新的数据调用内部Undo.

        do{

            m_pSaved[m_UndoNum-1].UndoAction(pData);

            m_UndoNum--;

        }while(m_UndoNum>0 );

        return true;

    }

};

    如果保存在CUndoData抽象类中,我们就需要对每个派生类进行修改。很明显,这样的代价要比上一种方法大很多。所以我也不多做介绍。

 

3 实现Undo数据的磁盘保存

    节省内存空间我们才需要实现Undo数据的磁盘保存。保存在磁盘,更容易实现无穷级数的UndoRedo操作。从前面的描述知道,可逆操作保存的是操作算法,这类操作一般占用的磁盘空间不多。我这里也就暂时不考虑实现对此类操作的磁盘保存。所以我们把保存在CFullImageUndoCImageData数据直接保存在磁盘。我们对CFullImageUndo类做如下修改:

class CFullImageUndo

{

public:

    virtual bool    UndoAction(CImageData * pData)

    {

        // 先从m_pszFilePath读取文件的内容到临时内存

        // 把读取的数据与pData的数据进行交换

        // 把交换后的数据重新写入文件,必要时释放临时内存

    }

public:

    // 指向CImageData保存的图像数据位置

    char * m_pszFilePath;

};

    也许这些实现和改进都不值一提。如果能给部分初学者提供部分帮助足矣。 
原文地址:https://www.cnblogs.com/hehe520/p/6330102.html