一、 CUndo类接口说明
1.1 virtual void Serialize(CArchive& ar)=0;
作用:纯虚函数,序列化从CUndo继承的子类,主要作用是对该子类各个成员变量进行序列化,程序中完成一步操作时,保存各个成员变量的值。
1.2 virtual void DeleteContents() = 0;
作用:纯虚函数,由从CUndo继承的子类进行实现,主要作用是在撤销操作时,在加载上一步资源之前,处理子类中的相关资源,如将成员变量恢复到原始状态等等。
上面两个接口,由CUndo的子类进行实现,在程序中不会被直接调用。
1.3 void CheckPoint();
作用:设置检查点,程序中每进行一步操作,设置一个检查点,记录该操作,用于后面的撤销操作使用。
1.4 void Undo();
作用:程序进行撤销操作时,调用该接口。
伪代码如下:
1.5 void Redo();
作用:程序进行反撤销操作时,调用该接口
伪代码如下:
1.6 void SetUndolevels(int undoLevels){m_undoLevels = undoLevels; }
作用:设置撤销操作的深度
1.7 ~CUndo()
作用:析构函数,释放资源
伪代码如下:
二、利用序列化技术实现撤销功能的实例
以下实现一个在对话框的客户区可以绘制直线的程序,每次通过鼠标左键按下记录绘制起点,然后鼠标移动,左键弹起时记录绘制终点,在起点和终点之间绘制一条直线。每次只能显示一条直线,程序有撤销和反撤销的功能。
2.1 创建从CUndo派生的绘制直线的类CDrawline
该类主要包含CPoint类型的两个成员变量,分别代表绘制直线的起点和终点,接口SetStartPoint和SetEndPoint分别设置起点和终点。
2.1.1 实现Serialize
2.1.2 实现DeleteContents
在这里只是为了说明,DeleteContents需要在CUndo的派生类中自己实现,其实在本类中该函数进行的操作没有特别大的意义。
2.2 实现对话框程序
2.2.1 新建对话框工程UndoTest,在相应类中增加一个CDrawLine类型的成员变量 m_DrawLine;
为对话框增加撤销和前进的菜单,以及对应的快捷键Ctrl+Z,Ctrl+Y,具体过程,这里不再详述。
界面如下图:
2.2.4 增加并实现鼠标左键按下的消息处理函数,记录绘制起始点
2.2.6 增加并实现撤销菜单的事件处理函数,实现撤销功能
2.2.7 增加并实现前进菜单的事件处理函数,实现反撤销功能
附undo.h:
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