Articles   Members Online:
-Article/Tip Search
-News Group Search over 21 Million news group articles.
-Delphi/Pascal
-CBuilder/C++
-C#Builder/C#
-JBuilder/Java
-Kylix
Member Area
-Home
-Account Center
-Top 10 NEW!!
-Submit Article/Tip
-Forums Upgraded!!
-My Articles
-Edit Information
-Login/Logout
-Become a Member
-Why sign up!
-Newsletter
-Chat Online!
-Indexes NEW!!
Employment
-Build your resume
-Find a job
-Post a job
-Resume Search
Contacts
-Contacts
-Feedbacks
-Link to us
-Privacy/Disclaimer
Embarcadero
Visit Embarcadero
Embarcadero Community
JEDI
Links
How to Undo - Redo using State (update 2) a Turn on/off line numbers in source code. Switch to Orginial background IDE or DSP color Comment or reply to this aritlce/tip for discussion. Bookmark this article to my favorite article(s). Print this article
09-Nov-03
Category
Algorithm
Language
Delphi 2.x
Views
143
User Rating
No Votes
# Votes
0
Replies
0
Publisher:
DSP, Administrator
Reference URL:
DKB
			Author: William Egge

Do you need to implement undo and redo in your application?  Here is a simple 
method, with source, that does the job for small data (up to 20 or 100K in memory) 

Do you need to implement undo and redo in your application?  Here is a simple 
method, with source, that does the job for small data (up to 20 or 100K in memory) 

Answer:

There are 2 methods of Undo-Redo that I know of. The first is saving the current 
state of the system into a list before it is modified. There would be a GetState 
and SetState method of your editor.  The second method is to store commands, where 
each command can undo and redo itself. 

Saving state is a good choice when your editor data is small such as 10 to 20K and 
your editor has many capabilities. Saving state is a simple solution. If you are 
doing image editing then you could get by with using a file to store your undo and 
redo information. A vector graphics editor would be a good choice here because 
vectors do not need much storage space. 

The more complex solution of storing commands requires much more coding but is 
nessesary when your editor edits large amounts of data and storing its state would 
be too time consuming. A word processor is an example. 

I have coded an Undo-Redo State class.. here is how it works. There is the main 
class that holds the state snapshots (TUndoRedoState), then there is the interface 
"IState" that has 2 methods, GetState and SetState. I implemented this by making my 
editor form implement the IState interface. 

The main class is created and passed the IState interface. Calling Undo and Redo 
makes calls to GetState and SetState. If you do not like the way I use an interface 
then you can easily change the class to accept method pointers to some GetState and 
SetState method, but I prefer the Interface. 

1   {
2     Author William Egge, egge@eggcentric.com
3            http://www.eggcentric.com
4   
5     Download this working example at http://www.eggcentric.com/UndoRedoState.htm
6   
7     This is a demo of using TUndoRedoState.
8     Created June 13, 2001
9   
10    Enjoy!
11  }
12  unit Frm_UndoRedoExample;
13  
14  interface
15  
16  uses
17    Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
18    StdCtrls, Buttons, ExtCtrls, UndoRedoState, _State;
19  
20  type
21    // Make this form implement the IState interface to be used
22    // by the UndoRedoState object.
23    TForm_UndoRedoExample = class(TForm, IState)
24      FDrawSurface: TImage;
25      FRedoBtn: TSpeedButton;
26      FUndoBtn: TSpeedButton;
27      FDirections: TLabel;
28      procedure Ev_FormCreate(Sender: TObject);
29      procedure Ev_FUndoBtnClick(Sender: TObject);
30      procedure Ev_FRedoBtnClick(Sender: TObject);
31      procedure Ev_FDrawSurfaceMouseDown(Sender: TObject; Button: TMouseButton;
32        Shift: TShiftState; X, Y: Integer);
33      procedure Ev_FDrawSurfaceMouseMove(Sender: TObject; Shift: TShiftState; X,
34        Y: Integer);
35      procedure Ev_FDrawSurfaceMouseUp(Sender: TObject; Button: TMouseButton;
36        Shift: TShiftState; X, Y: Integer);
37      procedure Ev_FormDestroy(Sender: TObject);
38    private
39      { Private declarations }
40      FUndoRedo: TUndoRedoState;
41      FMouseDown: Boolean;
42    public
43      { Public declarations }
44      // Methods that implement the IState interface
45      procedure GetState(S: TStream);
46      procedure SetState(S: TStream);
47    end;
48  
49  var
50    Form_UndoRedoExample: TForm_UndoRedoExample;
51  
52  implementation
53  
54  {$R *.DFM}
55  
56  procedure TForm_UndoRedoExample.GetState(S: TStream);
57  begin
58    FDrawSurface.Picture.Bitmap.SaveToStream(S);
59  end;
60  
61  procedure TForm_UndoRedoExample.SetState(S: TStream);
62  begin
63    FDrawSurface.Picture.Bitmap.LoadFromStream(S);
64  end;
65  
66  procedure TForm_UndoRedoExample.Ev_FormCreate(Sender: TObject);
67  begin
68    // Create a bitmap to draw on
69    with FDrawSurface.Picture.Bitmap do
70    begin
71      Width := FDrawSurface.Width;
72      Height := FDrawSurface.Height;
73    end;
74  
75    // Create the UndoRedo object, this form implements the state interface
76    FUndoRedo := TUndoRedoState.Create(Self);
77  end;
78  
79  procedure TForm_UndoRedoExample.Ev_FUndoBtnClick(Sender: TObject);
80  begin
81    FUndoRedo.Undo;
82  end;
83  
84  procedure TForm_UndoRedoExample.Ev_FRedoBtnClick(Sender: TObject);
85  begin
86    FUndoRedo.Redo;
87  end;
88  
89  procedure TForm_UndoRedoExample.Ev_FDrawSurfaceMouseDown(Sender: TObject;
90    Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
91  begin
92    // It is possible to get 2 mouse down events with no mouse up event, but rarely
93    // Get out when this happens and let mouse up reset it to false.
94    if FMouseDown then
95      Exit;
96  
97    FMouseDown := True;
98    FUndoRedo.BeginModify;
99  
100   // Set our start point where you first click
101   FDrawSurface.Canvas.MoveTo(X, Y);
102 end;
103 
104 procedure TForm_UndoRedoExample.Ev_FDrawSurfaceMouseMove(Sender: TObject;
105   Shift: TShiftState; X, Y: Integer);
106 begin
107   // Draw
108   if FMouseDown then
109     FDrawSurface.Canvas.LineTo(X, Y);
110 end;
111 
112 procedure TForm_UndoRedoExample.Ev_FDrawSurfaceMouseUp(Sender: TObject;
113   Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
114 begin
115   // Finished Editing
116   if FMouseDown then
117   begin
118     FUndoRedo.EndModify;
119     FMouseDown := False;
120   end;
121 end;
122 
123 procedure TForm_UndoRedoExample.Ev_FormDestroy(Sender: TObject);
124 begin
125   FUndoRedo.Free;
126 end;
127 
128 end.


Full Source of UndoRedoState.pas and _State.pas: 
2 units: 

129 unit _State;
130 
131 interface
132 uses
133   Classes;
134 
135 type
136   IState = interface
137     procedure GetState(S: TStream);
138     procedure SetState(S: TStream);
139   end;
140 
141 implementation
142 
143 end.
144 
145 [ver 2, update: fixed problem where setting state the stream needed to be set back 
146 to position 0 before calling setState] 
147 
148 unit UndoRedoState;
149 {
150   Author William Egge
151          egge@eggcentric.com
152          http://www.eggcentric.com
153 }
154 
155 interface
156 uses
157   _State, Classes, SysUtils;
158 
159 // A value of 0 for MaxMemoryUsage means unlimited (default).
160 type
161   TUndoRedoState = class
162   private
163     FState: IState;
164     FUndoRedoList: TList;
165     FModifyCount: Integer;
166     FUndoPos: Integer;
167     FTailState: TStream;
168     FMaxMemoryUsage: LongWord;
169     FCurrMemUsage: LongWord;
170     function CreateCurrentState: TStream;
171     procedure SetMaxMemoryUsage(const Value: LongWord);
172     procedure TruncToMem;
173   public
174     constructor Create(AState: IState);
175     property MaxMemoryUsage: LongWord read FMaxMemoryUsage write SetMaxMemoryUsage;
176     procedure BeginModify;
177     procedure EndModify;
178     procedure Undo;
179     procedure Redo;
180     destructor Destroy; override;
181   end;
182 
183 implementation
184 
185 { TUndoRedoState }
186 
187 procedure TUndoRedoState.BeginModify;
188 var
189   I: Integer;
190   S: TStream;
191 begin
192   Inc(FModifyCount);
193   if FModifyCount = 1 then
194   begin
195     for I := FUndoRedoList.Count - 1 downto FUndoPos + 1 do
196     begin
197       S := FUndoRedoList[I];
198       Dec(FCurrMemUsage, S.Size);
199       FUndoRedoList.Delete(I);
200       S.Free;
201     end;
202     S := CreateCurrentState;
203     Inc(FCurrMemUsage, S.Size);
204     FUndoRedoList.Add(S);
205     FUndoPos := FUndoRedoList.Count - 1;
206     if FTailState <> nil then
207     begin
208       Dec(FCurrMemUsage, FTailState.Size);
209       FreeAndNil(FTailState);
210     end;
211     TruncToMem;
212   end;
213 end;
214 
215 constructor TUndoRedoState.Create(AState: IState);
216 begin
217   Assert(AState <> nil, 'AState should not be nil for '
218     + '"TUndoRedoState.Create(AState: IState)"');
219 
220   inherited Create;
221   FState := AState;
222   FUndoRedoList := TList.Create;
223   FUndoPos := -1;
224 end;
225 
226 function TUndoRedoState.CreateCurrentState: TStream;
227 begin
228   Result := TMemoryStream.Create;
229   try
230     FState.GetState(Result);
231   except
232     Result.Free;
233     raise;
234   end;
235 end;
236 
237 destructor TUndoRedoState.Destroy;
238 var
239   I: Integer;
240 begin
241   FState := nil;
242   for I := 0 to FUndoRedoList.Count - 1 do
243     TObject(FUndoRedoList[I]).Free;
244 
245   FTailState.Free;
246 
247   inherited Destroy;
248 end;
249 
250 procedure TUndoRedoState.EndModify;
251 begin
252   Assert(FModifyCount > 0, 'TUndoRedoState.EndModify: EndModify was called '
253     + 'more times than BeginModify');
254 
255   Dec(FModifyCount);
256 end;
257 
258 procedure TUndoRedoState.Redo;
259 var
260   FRedoPos: Integer;
261   S: TStream;
262 begin
263   Assert(FModifyCount = 0, 'TUndoRedoState.Redo: should not be called while '
264     + 'modifying');
265 
266   if (FUndoRedoList.Count > 0) and (FUndoPos < (FUndoRedoList.Count - 1)) then
267   begin
268     FRedoPos := FUndoPos + 2;
269     if FRedoPos > (FUndoRedoList.Count - 1) then
270     begin
271       FTailState.Position := 0;
272       FState.SetState(FTailState);
273       Dec(FCurrMemUsage, FTailState.Size);
274       FreeAndNil(FTailState);
275     end
276     else
277     begin
278       S := FUndoRedoList[FRedoPos];
279       S.Position := 0;
280       FState.SetState(S);
281     end;
282     Inc(FUndoPos);
283   end;
284 end;
285 
286 procedure TUndoRedoState.SetMaxMemoryUsage(const Value: LongWord);
287 begin
288   FMaxMemoryUsage := Value;
289 end;
290 
291 procedure TUndoRedoState.TruncToMem;
292 var
293   S: TStream;
294 begin
295   if (FMaxMemoryUsage > 0) and (FCurrMemUsage > FMaxMemoryUsage) then
296   begin
297     while (FUndoRedoList.Count > 0) and (FCurrMemUsage > FMaxMemoryUsage) do
298     begin
299       S := FUndoRedoList[0];
300       FUndoRedoList.Delete(0);
301       Dec(FCurrMemUsage, S.Size);
302       Dec(FUndoPos);
303       S.Free;
304     end;
305 
306     if (FUndoRedoList.Count = 0) and (FCurrMemUsage > FMaxMemoryUsage) then
307       if FTailState <> nil then
308       begin
309         Dec(FCurrMemUsage, FTailState.Size);
310         FreeAndNil(FTailState);
311       end;
312   end;
313 end;
314 
315 procedure TUndoRedoState.Undo;
316 var
317   S: TStream;
318 begin
319   Assert(FModifyCount = 0, 'TUndoRedoState.Undo: should not be called while '
320     + 'modifying');
321 
322   if FUndoPos >= 0 then
323   begin
324     if FUndoPos = (FUndoRedoList.Count - 1) then
325     begin
326       FTailState := CreateCurrentState;
327       Inc(FCurrMemUsage, FTailState.Size);
328     end;
329     S := FUndoRedoList[FUndoPos];
330     S.Position := 0;
331     Dec(FUndoPos);
332     FState.SetState(S);
333     TruncToMem;
334   end;
335 end;
336 
337 end.



Component Download: http://www.eggcentric.com/UndoRedoState.zip

			
Vote: How useful do you find this Article/Tip?
Bad Excellent
1 2 3 4 5 6 7 8 9 10

 

Advertisement
Share this page
Advertisement
Download from Google

Copyright © Mendozi Enterprises LLC