王朝网络
分享
 
 
 

手把手教你写Undo、Redo程序

王朝other·作者佚名  2006-10-26
宽屏版  字体: |||超大  

手把手教你写Undo、Redo程序

Undo、Redo操作是很多具体编辑功能的软件所不能缺少的。最典型两种类型就是文本编辑和图像编辑软件。然而它的实现在某种程度上来说也不是很简单。我也废话少说。要在程序中支持Undo、Redo操作,就需要保存一些必要的信息,这个是众所周知的。如果想支持无限级的Undo、Redo操作,保存的信息就会无限的膨胀,问题来了,如何设计才能使每一步操作保存的数据尽可能少。

下面我就以图像编辑软件为例。说明如何在图像编辑中添加Undo、Redo功能。在我们开始进行编码设计前,对一些问题进行简单说明:

1、如何保存图像编辑操作中的操作信息。图像编辑可简单分为两类:一类是可逆的。也就是我们施加在图像上的操作可以根据操作算法进行逆操作。比如旋转,在旋转某个角度后如果需要Undo我们可以直接按相反的方向再旋转同样的角度;另一类是不可逆的。这里的不可逆不是绝对的。比如我们根据某个模板算法对图像的每个象素进行修改。这时我们就直接把此类操作归为不可逆。因为即使它可能是可逆的,但是实现起来的难道如果很大,这里只是为了方便说明。

2、对操作有了基本分类后。我们可以发现不可逆操作的Undo、Redo功能实现应该比较容易一些。为什么呢?因为操作不可逆,我们必须在操作前把全部的象素保存起来。这就相当于对原来的信息做了一份拷贝。所有的不可逆操作保存的信息可以认为是相同的:都是整个图像象素。此类操作实现简单,但是代码却高。而对于可逆操作,不同的操作算法就对应不同的Undo、Redo。每次操作保存的信息不同,但是我们只需要保存操作的算法。此类操作实现稍微麻烦。但是所需空间较小。对比两种操作,正如鱼和熊掌不能兼得。

3、在我们打开一副图像后,通常在软件的文档类中应该有一个最基本的图像数据类。所有的操作都是基于此类的数据。而且在我们进行Undo、Redo操作时,需要传递一个外部(也就是文档的图像数据)作为Undo、Redo的对象。

好了,我们开始对一些类进行说明。为了把数据数据与图像操作进行分离,我们定义两个基类:CImageData和CImageOperation。分别表示图像数据类和图像操作的基类。

class CImageData

{

public:

…........ //其他的成员及成员函数

BYTE * m_pByte; //象素数据的BYTE指针

BITMAPINFO * m_pInfo; //Windows平台的图像数据结构,也可以自定义

public:

// 函数ExecuteOperation是对当前的图像数据执行某种Operation。

// 注意这个函数的定义我会在后面根据需要修改,不是最后的版本。

bool ExecuteOperation(CImageOperation * pCmd);

};

下面是CImageOperation类的基本定义:

class CImageOperation

{

public:

…........ //其他的成员及成员函数

virtual bool Execute(CImageData * pData) = 0;

};

注意CImageOperation是一个抽象类,因为它并知道具体的图像操作。它的Execute函数也需要由派生的具体操作类实现。我下面就给一个具体操作实现类(以旋转为例):

class CImageRatate : public CImageOperation

{

public:

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

virtual bool Execute(CImageData * pData)

{

// 把pData所指的图像按时钟方向(m_fRotateAngle>0时)旋转m_fRotateAngle度数

// 如果小于0就是逆时钟方向,这里没有具体的实现代码,可参考其他图像库

}

private:

float m_fRotateAngle;

};

注意:这个旋转操作是可逆的。

怎么样你应该理解这个简单的图像操作框架了吧!下面开始我们真正的Undo、Redo部分。基于前面第三点所述,我们可以把Undo的抽象基类设计如下:

class CUndoData

{

public:

CUndoData() : m_ToolTip(0) {}

virtual bool UndoAction(CImageData * pData) = 0;

unsigned int m_ToolTip;

};

成员m_ToolTip所表示的值是一个字符串资源的ID,如果我们希望在工具栏的Undo、Redo按钮上添加操作提示功能,就可以使用它。默认值是0,表示没有提示信息。

函数UndoAction是真正的Undo、Redo实现函数,也是一个抽象类。它的参数是由外部传入的Undo对象(通常是文档类中的CImageData对象)。

根据前面第二点的说明,图像的可逆操作我们认为保存的数据是一样,都是CImageData对象。而不可逆操作是不同类型的。所以下面再定义两个类,分别表示可逆操作的Undo类和一个不可逆的操作类。(不可逆操作很多,仍以旋转为例)

class CFullImageUndo : public CUndoData

{

public:

virtual bool UndoAction(CImageData * pData)

{

// 这里进行真正的Undo,我们只需把m_UndoData和pData的数据相互交互即可

// 为什么交换就实现了Undo呢?因为m_UndoData是保存的操作前的数据,而参

// 数pData指向的正是文档中的数据,交换为文档的数据就被旧的数据替换啦!

}

public:

CImageData m_UndoData;

};

CFullImageUndo主要是针对不可逆操作的,因为只有这类操作我们才需要保存整个的图像数据。下面是可逆的旋转操作:

class CRatateUndo : public CUndoData

{

public:

CRotateUndo(float fAngle) : m_fRotateAngle(fAngle) {}

virtual bool UndoAction(CImageData * pData)

{

// 这里根据m_fRotateAngle对pData所指数据进行旋转

m_fRotateAngle *= -1;

// 这里为什么需要把角度乘以-1呢?因为在进行一步Undo操作后,这个Undo数据

// 马上就会变为Redo数据了,而进行Redo操作的算法是逆向的,这里来说就是

// 应该把旋转是方向改变一下。

}

private:

float m_fRotateAngle; //此成员意义与CImageRatate中的一样。

};

现在基本的Undo类有了。还没有实现给外部文档类使用的Undo/Redo列表啦!我们需要保存所有的Undo/Redo列表。从使用其他软件你应该可以感受出:最后的操作总是被最先Undo。Redo也是这样的。使用什么样的数据结构保存列表就好实现了。我们也找一种后进先出的列表:栈。我们就来实现这个接口类:(这里的栈我直接使用了STL的栈工具,其实STL的栈也是封装STL的Duque实现的)

#pragma warning(disable : 4786)

#include <stack>

class CUndoList

{

public:

CUndoList(){}

~CUndoList()

{

ClearUndo();

ClearRedo();

}

public:

// 下面两个函数判断Undo/Redo栈是否已经空

bool IsUndoEmpty() const { return m_UndoList.empty(); }

bool IsRedoEmpty() const { return m_RedoList.empty(); }

// 返回Undo数据的m_ToolTip数据,实现略

unsigned int GetUndoTips() const;

unsigned int GetRedoTips() const;

void AddUndo(CUndoData * pUndo);

{

if (pUndo)

{

m_UndoList.push(pUndo);

ClearRedo();

}

}

void Undo(CImageData * pData);

{

CUndoData *pUndo = m_UndoList.top();

pUndo->UndoAction(pData);

// 在调用pUndo的UndoAction后,内部就已经把pUndo变为了Redo数据

m_RedoList.push(pUndo);

}

void Redo(CImageData * pData);

{

CUndoData *pUndo = m_RedoList.top();

pUndo->UndoAction(pData);

// 在调用pUndo的UndoAction后,内部就已经把pUndo变为了Undo数据

m_UndoList.push(pUndo);

}

void ClearUndo(); // 清除Undo栈,实现略

void ClearRedo(); // 清除Redo栈,实现略

private:

std::stack<CUndoData *> m_UndoList;

std::stack<CUndoData *> m_RedoList;

};

好了现在接口类实现。我们就可以在文档类中使用这个CUndoList类,并根据CUndoList类的函数返回指,实现工具栏安装状态的改变以及工具栏按钮的提示信息。

进一步内容可参考:

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

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
>>返回首页<<
推荐阅读
 
 
频道精选
 
静静地坐在废墟上,四周的荒凉一望无际,忽然觉得,凄凉也很美
© 2005- 王朝网络 版权所有