通过保存状态完成撤消-重做功能
你需要在应用程序中执行撤消-重做操作吗?这里有一个简单的方法,它对占用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.