C++与MFC中实现撤销功能的Redo、Undo框架

一、 CUndo类接口说明

1.1 virtual void Serialize(CArchive& ar)=0;

作用:纯虚函数,序列化从CUndo继承的子类,主要作用是对该子类各个成员变量进行序列化,程序中完成一步操作时,保存各个成员变量的值。

1.2 virtual void DeleteContents() = 0;

作用:纯虚函数,由从CUndo继承的子类进行实现,主要作用是在撤销操作时,在加载上一步资源之前,处理子类中的相关资源,如将成员变量恢复到原始状态等等。
上面两个接口,由CUndo的子类进行实现,在程序中不会被直接调用。

1.3 void CheckPoint();

作用:设置检查点,程序中每进行一步操作,设置一个检查点,记录该操作,用于后面的撤销操作使用。
伪代码如下:

CheckPoint() 
{
	if (m_chkpt <= 0) {
		CMemFile* file = new CMemFile(m_growsize);//动态创建了一片内存用于序列化对象
		Store(file);//存储操作,主要作用就是序列化存储该对象
{
         		     file->SeekToBegin();
		     CArchive ar(file, CArchive::store);
		     Serialize(ar); 
		     ar.Close();
}
		AddUndo(file);
{
      ……
                                     m_undolist.AddHead(file);//将该存储该对象属性的内存地址放进撤销操作列表中
}
ClearRedoList();//将反撤销操作的列表清空(正常进行操作时,反撤销操作是不能用的)
		{
 		     ……
     m_redolist.RemoveAll();	
}
	}
}

1.4 void Undo();

作用:程序进行撤销操作时,调用该接口。

伪代码如下:

Undo() 
{
	……
	CMemFile *pFile = (CMemFile *) m_undolist.GetHead();//获取撤销操作列表当前头结点
	m_undolist.RemoveHead();//移除该头结点
	AddRedo(pFile);//将头结点增加到反撤销操作列表
	{
	     m_redolist.AddHead(file);
}
	pFile = (CMemFile *)m_undolist.GetHead();//获取当前撤销操作的头结点
	Load(pFile);//加载存储的对象的成员变量的值
	{
	      DeleteContents();//在恢复上一步操作之前,调用DeleteContents接口
	      file->SeekToBegin();
	      CArchive ar(file, CArchive::load);
	      Serialize(ar); //序列化加载上一步操作中对象各个成员变量的值
	      ar.Close();
	}
}

1.5 void Redo();

作用:程序进行反撤销操作时,调用该接口
伪代码如下:

Redo() 
{
	
	CMemFile *pFile = (CMemFile *) m_redolist.GetHead() ;
	m_redolist.RemoveHead();//从反撤销操作中移除该头结点
	AddUndo(pFile);//将节点加到撤销操作列表中
{
                     m_undolist.AddHead(file);
}
	Load(pFile);
	{
	      DeleteContents();//在恢复下一步操作之前,调用DeleteContents接口
	      file->SeekToBegin();
	      CArchive ar(file, CArchive::load);
	      Serialize(ar); //序列化加载下一步操作中对象各个成员变量的值
	      ar.Close();
}
}

1.6 void SetUndolevels(int undoLevels){m_undoLevels = undoLevels; }

作用:设置撤销操作的深度

1.7 ~CUndo()

作用:析构函数,释放资源

伪代码如下:

~CUndo() 
{
      Clear();
     {
          // Clear undo list
         ClearUndoList();
 
         // Clear redo list
         ClearRedoList();
     }
}

二、利用序列化技术实现撤销功能的实例

以下实现一个在对话框的客户区可以绘制直线的程序,每次通过鼠标左键按下记录绘制起点,然后鼠标移动,左键弹起时记录绘制终点,在起点和终点之间绘制一条直线。每次只能显示一条直线,程序有撤销和反撤销的功能。

2.1 创建从CUndo派生的绘制直线的类CDrawline

该类主要包含CPoint类型的两个成员变量,分别代表绘制直线的起点和终点,接口SetStartPoint和SetEndPoint分别设置起点和终点。

class CDrawLine :public CUndo
{
public:
	CDrawLine();
	virtual ~CDrawLine();
private:
	CPoint m_cpStart;
	CPoint m_cpEnd;
public:
	void SetStartPoint(const CPoint& point)
	{
		m_cpStart=point;
	}
	void SetEndPoint(const CPoint& point)
	{
		m_cpEnd=point;
	}
	void OnLButtonUp();
	void Draw(CDC* dc,CWnd* pWnd);
	virtual void Serialize(CArchive& ar);
	virtual void DeleteContents();
};
2.1.1 实现Serialize
void CDrawLine::Serialize(CArchive& ar)
{
	if(ar.IsStoring())
	{
		ar<<m_cpStart;
		ar<<m_cpEnd;
	}
	else
	{
		ar>>m_cpStart;
		ar>>m_cpEnd;
	}
}
2.1.2 实现DeleteContents

在这里只是为了说明,DeleteContents需要在CUndo的派生类中自己实现,其实在本类中该函数进行的操作没有特别大的意义。

void CDrawLine::DeleteContents()
{
	m_cpStart=m_cpEnd=CPoint();
}
2.1.3 实现Draw函数,用来绘制直线。
void CDrawLine::Draw(CDC* pDC,CWnd* pWnd)
{
   if(m_cpEnd==m_cpStart) return;
   CRect client;
   pWnd->GetClientRect(&client);
   CPen pen(PS_SOLID,2,RGB(255,0,0));
   pDC->SelectObject(&pen);
   pDC->MoveTo(m_cpStart);
   pDC->LineTo(m_cpEnd);
   pDC->DeleteDC();
}
2.1.4 实现OnLButtonUp函数,用来记录检查点
void CDrawLine::OnLButtonUp()
{
	CheckPoint();
}

2.2 实现对话框程序

2.2.1 新建对话框工程UndoTest,在相应类中增加一个CDrawLine类型的成员变量 m_DrawLine;

为对话框增加撤销和前进的菜单,以及对应的快捷键Ctrl+Z,Ctrl+Y,具体过程,这里不再详述。

class CUndoTestDlg : public CDialogEx
{
       …
       CDrawLine m_DrawLine;
}

界面如下图:
图片预览

2.2.2 增加并实现Draw函数用来绘制直线。
void CUndoTestDlg::Draw(void)
{
	HDC hdc=::GetDC(m_hWnd);
	CDC dc;
	dc.Attach(hdc);
	m_DrawLine.Draw(&dc,this);
}
 
2.2.3 在OnPaint中调用上述Draw函数
void CUndoTestDlg::OnPaint()
{
	
	……
	Draw();
}
2.2.4 增加并实现鼠标左键按下的消息处理函数,记录绘制起始点
void CUndoTestDlg::OnLButtonDown(UINT nFlags, CPoint point)
{
	m_DrawLine.SetStartPoint(point);
	CDialogEx::OnLButtonDown(nFlags, point);
}
2.2.5 增加并实现鼠标左键抬起的消息处理函数,记录绘制终点、重绘界面以及记录撤销操作的检查点。
void CUndoTestDlg::OnLButtonUp(UINT nFlags, CPoint point)
{
	m_DrawLine.SetEndPoint(point);
	Invalidate(TRUE);
	m_DrawLine.OnLButtonUp();
	CDialogEx::OnLButtonUp(nFlags, point);
}
2.2.6 增加并实现撤销菜单的事件处理函数,实现撤销功能
void CUndoTestDlg::OnUndo()
{
	m_DrawLine.Undo();
	this->Invalidate(TRUE);
}
2.2.7 增加并实现前进菜单的事件处理函数,实现反撤销功能
void CUndoTestDlg::OnMenuRedo()
{
	m_DrawLine.Redo();
	this->Invalidate(TRUE);
}
附undo.h:
// undo.h implementation
// Author - Keith Rule (keithr@europa.com)
//
// A description of this code can be found in May 1997 - Windows Tech Journal.
// Back issues can be ordered from either (800) 241-4320 or (918) 831-9557.
//
// An extended version of this class is part of the MFC Class of the week
// (http://www.weeklymfc.com/).
//
// Modified by Jiang Feng , 1998.10
//
#ifndef _UNDO_H_
#define _UNDO_H_
//------------------------------------------------------------
//  Undo/Redo for MFC By Keith Rule
class CUndo {
private:
	CObList	m_undolist;		// Stores undo states
	CObList	m_redolist;		// Stores redo states
	long	m_growsize;		// Adjust for faster saves
	long	m_undoLevels;	// Requested Undolevels 
	long	m_chkpt;
 
	//added by yangguichun, 2008/06/25
	BOOL	m_bUndo;
	BOOL	m_bRedo;
	//end added by yangguichun, 2008/06/25
 
	void AddUndo(CMemFile*);
	void AddRedo(CMemFile *pFile); 
	void Load(CMemFile*);
	void Store(CMemFile*);
	void ClearRedoList();
	void ClearUndoList();
public:
 
	// Here are the hooks into the CDocument class
	virtual void Serialize(CArchive& ar) = 0;
	virtual void DeleteContents() = 0;
 
	// User accessable functions
	CUndo(long undolevels = 8, long = 32768);	// Constructor
	~CUndo();			// Destructor
	BOOL CanUndo();		// Returns TRUE if can Undo
	BOOL CanRedo();		// Returns TRUE if can Redo
	void Undo();		// Restore next Undo state
	void Redo();		// Restore next Redo state				
	void CheckPoint();	// Save current state 
	void EnableCheckPoint();
	void DisableCheckPoint();
 
	void SetUndolevels(int undoLevels){m_undoLevels = undoLevels; }
 
	BOOL IsUndo(){ return m_bUndo; };
	BOOL IsRedo() { return m_bRedo; };
 
	/// <summary>
	///	清除undo和redo的列表
	/// </summary>
	void Clear(){
		// Clear undo list
		ClearUndoList();
 
		// Clear redo list
		ClearRedoList();
	};
};
 
// Constructor
inline CUndo::
	CUndo(long undolevels, long growsize) : 
m_growsize(growsize), m_undoLevels(undolevels),
	m_chkpt(0)
{
	m_undoLevels = 10	;
	//added by yangguichun, 2008/06/25
	m_bUndo = FALSE;
	m_bRedo = FALSE;
	//end added by yangguichun, 2008/06/25
} 
 
// Remove contents of the redo list
inline void CUndo::
	ClearRedoList()
{
	// Clear redo list
	POSITION pos = m_redolist.GetHeadPosition(); 
	CMemFile* nextFile = NULL;
	while(pos) {
		nextFile = (CMemFile *) m_redolist.GetNext(pos);
		delete nextFile;
	}
	m_redolist.RemoveAll();	
}
 
inline void CUndo::ClearUndoList()
{
	POSITION pos = m_undolist.GetHeadPosition(); 
	CMemFile  *nextFile = NULL;
	while(pos) {
		nextFile = (CMemFile *) m_undolist.GetNext(pos);
		delete nextFile;
	}
	m_undolist.RemoveAll();	
}
// Destructor
inline CUndo::
	~CUndo() 
{
	Clear();
}
 
// Checks undo availability, may be used to enable menus
inline BOOL CUndo::
	CanUndo() 
{
	return (m_undolist.GetCount() > 1);
}
 
// Checks redo availability, may be used to enable menus
inline BOOL CUndo::
	CanRedo() 
{
	return (m_redolist.GetCount() > 0);
}
 
// Adds state to the beginning of undo list		
inline void CUndo::
	AddUndo(CMemFile* file) 
{
	// Remove old state if there are more than max allowed
	if (m_undolist.GetCount() > m_undoLevels) {
		CMemFile* pFile = (CMemFile *) m_undolist.RemoveTail();
		delete pFile;
	}
	// Add new state to head of undo list
	m_undolist.AddHead(file);
}
 
// Saves current object into CMemFile instance
inline void CUndo::
	Store(CMemFile* file) 
{
	file->SeekToBegin();
	CArchive ar(file, CArchive::store);
	Serialize(ar); 
	ar.Close();
}
 
// Loads CMemfile instance to current object
inline void CUndo::
	Load(CMemFile* file) 
{
	DeleteContents(); 
	file->SeekToBegin();
	CArchive ar(file, CArchive::load);
	Serialize(ar); 
	ar.Close();
}
 
// Save current object state to Undo list
inline void CUndo::
	CheckPoint() 
{
	if (m_chkpt <= 0) {
		CMemFile* file = new CMemFile(m_growsize);
		Store(file);
		AddUndo(file);
		ClearRedoList();
	}
}
 
inline void CUndo::
	EnableCheckPoint()
{
	if (m_chkpt > 0) {
		m_chkpt--;
	}
}
 
inline void CUndo::
	DisableCheckPoint()
{
	m_chkpt++;
}
 
// Place CMemFile instnace on Redo list
inline void CUndo::
	AddRedo(CMemFile *file) 
{
	// Move state to head of redo list
	m_redolist.AddHead(file);
}
 
// Perform an Undo command
inline void CUndo::
	Undo() 
{
	if (CanUndo()) {
		// Remember that the head of the undo list
		// is the current state. So we just move that
		// to the Redo list and load then previous state.
		//added by yangguichun, 2008/06/25
		m_bUndo = TRUE;
		//end added by yangguichun, 2008/06/25
		CMemFile *pFile = (CMemFile *) m_undolist.GetHead();
		m_undolist.RemoveHead();
		AddRedo(pFile);
		pFile = (CMemFile *)m_undolist.GetHead();
		Load(pFile);
		//added by yangguichun, 2008/06/25
		m_bUndo = FALSE;
		//end added by yangguichun, 2008/06/25
	}
}
 
//Perform a Redo Command
inline void CUndo::
	Redo() 
{
	if (CanRedo()) {
		//added by yangguichun, 2008/06/25
		m_bRedo = TRUE;
		//end added by yangguichun, 2008/06/25
		CMemFile *pFile = (CMemFile *) m_redolist.GetHead() ;
		m_redolist.RemoveHead();
		AddUndo(pFile);
		Load(pFile);
		//added by yangguichun, 2008/06/25
		m_bRedo = FALSE;
		//end added by yangguichun, 2008/06/25
	}
}
#endif

附:另一个可供参考的范例

class Command{
public:
	virtual bool execute() = 0;
	virtual bool Unexecute() = 0;
};
 
class operator1 : public Command{
public:
	bool execute(){
		std::cout << "Operator1" << std::endl;
		return true;
	}
	bool Unexecute(){
		std::cout << "Unexecute Operator2" << std::endl;
		return true;
	}
};
 
class operator2 : public Command{
public:
	bool execute(){
		std::cout << "Operator2" << std::endl;
		return true;
	}
	bool Unexecute(){
		std::cout << "Unexecute Operator2" << std::endl;
		return true;
	}
};
 
class CommandManager
{
public:
	static CommandManager* instance(){
		static CommandManager ins;
		return &ins;
	}
 
	void callCommand(Command* pCommand){
		if (pCommand != NULL)
		{
			if (pCommand->execute())
			{
				PushUndoCommand(pCommand);
				clearRedoCommand();
			}
			else
			{
				delete pCommand;
			}
		}
	}
 
	void PushUndoCommand(Command* pCommand){
		if (pCommand != NULL)
		{
			m_stackUndo.push(pCommand);
		}
	}
	void PushRedoCommand(Command* pCommand){
		if (pCommand != NULL)
		{
			m_stackRedo.push(pCommand);
		}
	}
	void clearRedoCommand(){
		while (!m_stackRedo.empty())
		{
			delete m_stackRedo.top();
			m_stackRedo.pop();
		}
	}
	void clearUndoCommand(){
		while (!m_stackUndo.empty())
		{
			delete m_stackUndo.top();
			m_stackUndo.pop();
		}
	}
 
	void undo(){
		Command * pCommand = PopUndoCommand();
		if (pCommand)
		{
			if (pCommand->Unexecute())
			{
				PushRedoCommand(pCommand);
			}
			else
			{
				delete pCommand;
			}
		}
	}
 
	void redo(){
		Command * pCommand = PopRedoCommand();
		if (pCommand)
		{
			if (pCommand->execute())
			{
				PushUndoCommand(pCommand);
			}
			else
			{
				delete pCommand;
			}
		}
	}
 
	Command * PopUndoCommand()
	{
		Command * pCommand = NULL;
		if (!m_stackUndo.empty())
		{
			pCommand = m_stackUndo.top();
			m_stackUndo.pop();
		}
		return pCommand;
	}
 
	Command* PopRedoCommand()
	{
		Command * pCommand = NULL;
		if (!m_stackRedo.empty())
		{
			delete m_stackRedo.top();
			m_stackRedo.pop();
		}
		return pCommand;
	}
 
private:
	std::stack<Command *> m_stackUndo;
	std::stack<Command *> m_stackRedo;
 
private:
	CommandManager(){};
	CommandManager(const CommandManager&);
	CommandManager& operator=(const CommandManager&);
};

作者:HymanLiuTS ;Edisonlhz
来源:CSDN
原文:https://blog.csdn.net/hyman_c/article/details/50760627
https://blog.csdn.net/a18826408828/article/details/52289302
版权声明:本文为博主原创文章,转载请附上博文链接!

  • 4
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值