MFC中利用序列化技术实现撤销功能

6 篇文章 1 订阅
一、 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


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值