首页  编辑  

通过保存状态完成撤消-重做功能

Tags: /超级猛料/VCL/Memo&Edit&Richedit/   Date Created:

通过保存状态完成撤消-重做功能

你需要在应用程序中执行撤消-重做操作吗?这里有一个简单的方法,它对占用20-100K内存的小数据量十分有效。

原著:  William Egge

公司:  Eggcentric  参考资料: http://www.eggcentric.com/UndoRedoState.htm  

回答:

 构件下载:

 UndoRedoState.zip

 通过状态方式完成撤消-重做功能。

 

 据我所知,存在两种撤消-重做的方法。第一种是将当前系统的状态在它被修改前保存到一个列表中。在你的编辑器中可能会有 GetState 和 SetState 方法。第二种方法即是保存命令,每个命令都可以被撤消和重做。

 当你的编辑器有许多功能但编辑的数据比较少如10-20K时,采用保存状态的方式比较好,更简捷。当你编辑一个图像时,你可能将撤消和重做的信息保存在一个文件中,这时最好采用向量图的方式,因为这样更节约空间。

 当你编辑大量数据时,采用保存命令的复杂方式就有必要了,尽管这样对编码要求更高,因为如果用保存状态的方式就会消耗大量的时间。

 我自己编写了一个撤消-重做状态的类,主类(TUndoRedoState)中保持状态的快照,在"IState"接口中有两个方法:GetState和SetState。我在编辑器的实现代码中实现了这个接口。

 给主类的构造函数中传送IState接口。调用撤消和重做将调用GetState和SetState函数。如果你不喜欢使用接口,也可以采用方法指针来修改代码以获取GetState和SetState方法。

   用法示例:

     TMyForm = class(TForm, IState)

       procedure FormCreate(Sender: TObject);

     private

       FUndoRedo: TUndoRedoState;

       procedure GetState(S: TStream);

       procedure SetState(S: TStream);

     end;

 

     =====

     procedure TMyForm.FormCreate(Sender: TObject);

     begin

       FUndoRedo:= TUndoRedoState.Create(Self);

     end;

 

     ....

     然后你就可以在你的代码中调用 FUndoRedo.BeginModify 和 EndModify;

 

     你也可以添加一对方法如 CanUndo 和 CanRedo 以便决定是否允许撤消、重做按钮.

 

 

 Happy coding!

 

 Download the source from the url.

 

 Full Source:

 2 units:

 

 ===============================

 unit _State;

 

 interface

 uses

   Classes;

 

 type

   IState = interface

     procedure GetState(S: TStream);

     procedure SetState(S: TStream);

   end;

 

 implementation

 

 end.

 ============================

 unit UndoRedoState;

 

 interface

 uses

   _State, Classes, SysUtils;

 

 // A value of 0 for MaxMemoryUsage means unlimited (default).

 type

   TUndoRedoState = class

   private

     FState: IState;

     FUndoRedoList: TList;

     FModifyCount: Integer;

     FUndoPos: Integer;

     FTailState: TStream;

     FMaxMemoryUsage: LongWord;

     FCurrMemUsage: LongWord;

     function CreateCurrentState: TStream;

     procedure SetMaxMemoryUsage(const Value: LongWord);

     procedure TruncToMem;

   public

     constructor Create(AState: IState);

     property MaxMemoryUsage: LongWord read FMaxMemoryUsage write SetMaxMemoryUsage;

     procedure BeginModify;

     procedure EndModify;

     procedure Undo;

     procedure Redo;

     destructor Destroy; override;

   end;

 

 implementation

 

 { TUndoRedoState }

 

 procedure TUndoRedoState.BeginModify;

 var

   I: Integer;

   S: TStream;

 begin

   Inc(FModifyCount);

   if FModifyCount = 1 then

   begin

     for I:= FUndoRedoList.Count-1 downto FUndoPos+1 do

     begin

       S:= FUndoRedoList[I];

       Dec(FCurrMemUsage, S.Size);

       FUndoRedoList.Delete(I);

       S.Free;

     end;

     S:= CreateCurrentState;

     Inc(FCurrMemUsage, S.Size);

     FUndoRedoList.Add(S);

     FUndoPos:= FUndoRedoList.Count-1;

     if FTailState <> nil then

     begin

       Dec(FCurrMemUsage, FTailState.Size);

       FreeAndNil(FTailState);

     end;

     TruncToMem;

   end;

 end;

 

 constructor TUndoRedoState.Create(AState: IState);

 begin

   Assert(AState <> nil, 'AState should not be nil for '

     +'"TUndoRedoState.Create(AState: IState)"');

 

   inherited Create;

   FState:= AState;

   FUndoRedoList:= TList.Create;

   FUndoPos:= -1;

 end;

 

 function TUndoRedoState.CreateCurrentState: TStream;

 begin

   Result:= TMemoryStream.Create;

   try

     FState.GetState(Result);

   except

     Result.Free;

     raise;

   end;

 end;

 

 destructor TUndoRedoState.Destroy;

 var

   I: Integer;

 begin

   FState:= nil;

   for I:= 0 to FUndoRedoList.Count-1 do

     TObject(FUndoRedoList[I]).Free;

 

   FTailState.Free;

 

   inherited Destroy;

 end;

 

 procedure TUndoRedoState.EndModify;

 begin

   Assert(FModifyCount > 0, 'TUndoRedoState.EndModify: EndModify was called '

     +'more times than BeginModify');

 

   Dec(FModifyCount);

 end;

 

 procedure TUndoRedoState.Redo;

 var

   FRedoPos: Integer;

 begin

   Assert(FModifyCount=0, 'TUndoRedoState.Redo: should not be called while '

     +'modifying');

 

   if (FUndoRedoList.Count > 0) and (FUndoPos < (FUndoRedoList.Count-1)) then

   begin

     FRedoPos:= FUndoPos+2;

     if FRedoPos > (FUndoRedoList.Count-1) then

     begin

       FState.SetState(FTailState);

       Dec(FCurrMemUsage, FTailState.Size);

       FreeAndNil(FTailState);

     end

     else

       FState.SetState(FUndoRedoList[FRedoPos]);

     Inc(FUndoPos);

   end;

 end;

 

 procedure TUndoRedoState.SetMaxMemoryUsage(const Value: LongWord);

 begin

   FMaxMemoryUsage := Value;

 end;

 

 procedure TUndoRedoState.TruncToMem;

 var

   S: TStream;

 begin

   if (FMaxMemoryUsage > 0) and (FCurrMemUsage > FMaxMemoryUsage) then

   begin

     while (FUndoRedoList.Count > 0) and (FCurrMemUsage > FMaxMemoryUsage) do

     begin

       S:= FUndoRedoList[0];

       FUndoRedoList.Delete(0);

       Dec(FCurrMemUsage, S.Size);

       Dec(FUndoPos);

     end;

 

     if (FUndoRedoList.Count = 0) and (FCurrMemUsage > FMaxMemoryUsage) then

       if FTailState <> nil then

       begin

         Dec(FCurrMemUsage, FTailState.Size);

         FreeAndNil(FTailState);

       end;

   end;

 end;

 

 procedure TUndoRedoState.Undo;

 var

   S: TStream;

 begin

   Assert(FModifyCount=0, 'TUndoRedoState.Undo: should not be called while '

     +'modifying');

 

   if FUndoPos >= 0 then

   begin

     if FUndoPos = (FUndoRedoList.Count-1) then

     begin

       FTailState:= CreateCurrentState;

       Inc(FCurrMemUsage, FTailState.Size);

     end;

     S:= FUndoRedoList[FUndoPos];

     Dec(FUndoPos);

     FState.SetState(S);

     TruncToMem;

   end;

 end;

 

 end.