多步Undo/Redo的实现

时间:2020-12-25 16:51:27
          应用软件中,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;   
  } Top