怎样做Undo

解决方案 »

  1.   

    Adding Undo to an Existing Application
    http://codeguru.earthweb.com/misc/undo_redo_cdocument.shtml
      

  2.   

    多步Undo/Redo的实现
        应用软件中,Undo/Redo功能为用户提供了方便,而多步Undo/Redo则更是如此。
    本文利用C++面对象的程序设计技术,研究了多步Undo/Redo的实现方法。    首先,我们建立一个基类CEditRecord,对于每一种操作,都从该基类上派
    生出与操作相对应的类,记载操作过程,供以后进行具体的Undo/Redo操作;基
    类CEditRecord中的纯虚函数,为Undo、Redo操作提供接口。    然后,我们建立一个用于控制Undo/Redo的类:CRecordCtrl。 CRecordCtrl
    类从基类CObArray上派生,用于记载已经进行过的操作,响应Undo/Redo命令等;
    其中的nMaxStep变量表示允许Undo/Redo的次数,nCurrRecord变量表示当前的Undo
    的位置;函数Undo()和Redo()用于响应来自系统菜单、快捷键或者工具条的Undo
    和Redo命令;函数SetMaxStep()用于设置允许Undo/Redo的次数。////////////////////////////////////////////////////////
    // CEditRecord.h
    // Class CEditRecord、CEditCtrl Definition
    class CEditRecord : public CObject
    {
    public:
    CEditRecord();
    public: 
    virtual BOOL Undo( )=0;
    virtual BOOL Redo( )=0;
    };class CRecordCtrl:public CObArray
    {
    public:
    CRecordCtrl( );
    ~CRecordCtrl( );
    private: 
    int nCurrRecord;
    int nMaxStep;
    public:
    BOOL EnableUndo( );
    BOOL EnableRedo( );
    BOOL Undo( );
    BOOL Redo( );
    BOOL SetMaxStep(int n); 
    }; extern CEditCtrl Records;
    ////////////////////////////////////////////////////
    // CEditRecord.Cpp
    // Class CEdit、CEditCtrl Imeplemetion#include "CEditRecord.h"
    CRecordCtrl Records;CEditRecord::CEditRecord( )
    {
    int mm=Records.GetSize( );
    if(nCurrRecord==mm-1)

    if(mm==nMaxStep)
    {
    //删除最早的记录
    CEditRecord* pRec=(CEditRecord*)GetAt(0);
    delete pRec;
    Records.RemoveAt(0);
    nCurrRecord--;
    }
    }
    else
    {
    //删除所有Undo过的记录
    for(int i=mm-1;i>nCurrRecord;i--)
    {
    CEditRecord* pRec=(CEditRecord*)GetAt(i);
    delete pRec;
    Records.RemoveAt(i); 

    } nCurrRecord=Records.Add(this);
    }CRecordCtrl::CRecordCtrl( )
    {
    nCurrRecord=-1;
    nMaxStep=5;
    }CRecordCtrl::~CRecordCtrl( )
    {
    for(int i=GetSize()-1;i>=0;i--)
    {
    CEditRecord* pUndo=(CEditRecord*)GetAt(i);
    delete pUndo;
    }
    }BOOL CRecordCtrl::EnableUndo( )
    {
    return (nCurrRecord>=0);
    }BOOL CRecordCtrl::EnableRedo( )
    {
    return (nCurrRecord<GetSize( )-1);
    }BOOL CRecordCtrl::Undo( )
    {
    if(!EnableUndo( )) return FALSE;CEditRecord* pRec=(CEditRecord*)GetAt(nCurrRecord--);
    if(pRec==NULL) return FALSE;return pRec->Undo();
    }BOOL CRecordCtrl::Redo()
    {
    if(!EnableRedo( )) return FALSE;CEditRecord* pRec=(CEditRecord*)GetAt(++nCurrRecord);
    if(pRec==NULL) return FALSE;return pRec->Redo();
    }
    BOOL CRecordCtrl::SetMaxStep(int n)
    {
    if(n<1) return FALSE;
    int mm=GetSize( );if(UndoStep<=n || mm<=n) 
    {
    UndoStep=n;
    return TRUE;

    else 

    //压缩用于Undo的记录
    int nPack=mm-n;
    int u=min(nCurrRecord,nPack);
    for(int i=u-1;i>=0;i--)
    {
    CEditRecord* pRec=(CEditRecord*)GetAt(i);
    delete pRec; 
    nCurrRecord--;
    }//压缩用于Redo的记录
    int v=nPack-u;
    for(int i=0;i<v;i++)
    {
    CEditRecord* pRec=(CEditRecord*)GetAt(mm-i-1);
    if(pRedo==NULL) delete pRec; 
    }

    return TRUE;
    }
    在CEditRecord的生成函数中,首先判定是否达到允许的最大Undo次数; 如果
    未达到,直接把this指针加入到阵列中;如果超过,需要从阵列中,清除一些关于早期的操作的记录,然后把this
    指针加入到阵列中。函数CRecordCtrl::SetMaxStep( )中,对于缩小Undo/Redo次数这种情况,特别是在阵列中已经
    记载了较多的操作,则需清除一些。在CRecordCtrl类的析构函数中,清除阵列中的每一个CEditRecord对象。下面给出一个例子说明如何建立CEditRecord对象,为方便计,假设进行的操作是从一个全局性
    的字符串pText中删除一段内容,我们用Pos表示所删内容在pText中的相对位置, 用Len表示所删内
    容的长度,并假设全局串pTetx的存储空间足够大。1.从基类CEditRecord上派生出CEditCutString;
    2.设置私有变量Pos、Len用于表示所删内容在pText中的相对位置、长度;设置私有变量pBuff
    用于分配存储空间,保存所删内容;设置私有变量Avialiable用于表示可否进行Undo/Redo。/////////////////////////////////////////////////////
    // Example 
    // 
    #include "CEditRecord.h"class CEditCutString:public CEditRecord
    {
    public:
    CEditCutString(int,int);
    ~CEditCutString(); 
    private:
    int Pos;
    int Len;
    char* pBuff; 
    BOOL Avialiable;public:
    virtual BOOL Undo();
    virtual BOOL Redo(); 
    };
    CEditCutString::CEditCutString(int p,int n)
    {
    Pos=p;
    Len=n; 
    pBuff=new char[n]; if(pBuff==NULL)
    Avialiable=FALSE;
    esle

    Avialiable=TRUE; 
    memcpy(pBuff,pText+Pos,Len);
    }
    }CEditCutString::~CEditCutString
    {
    if(Avialiable)
    delete []pBuff; 
    }BOOL CEditCutString::Undo()
    {
    if(!Avialiable)
    return FALSE; int l=strlen(pText);
    for(int i=l;i>=Pos;i--)
    pText[i+Len]=pText[i]; for(int j=0;j<Len;j++)
    pText[Pos+j]=pBuff[j];return TRUE;
    }BOOL CEditCutString::Redo()
    {
    if(!Avialiable)
    return FALSE; int l=strlen(pText);for(int i=Pos;i<=l-Len;i++)
    pText[i]=pText[i+Len]; return TRUE;
    }