1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
|
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Security.Permissions;
[assembly: PermissionSetAttribute(SecurityAction.RequestMinimum, Name = "FullTrust")]
namespace DotNetExpansions
{
/// <summary>
/// Bietet in einer abgeleiteten Klasse Funktionen für Undo und Redo.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using System;
/// using DotNetExpansions;
///
/// namespace Printversion
/// {
/// internal class Program
/// {
/// private static void Main()
/// {
/// var p = new Person();
/// var pState = new PersonState();
///
/// p.Firstname = "George";
/// p.Lastname = "Jetson";
/// pState.SaveState(p);
/// Console.WriteLine("Startzustand : " + p);
///
/// p.Firstname = "Testvorname";
/// p.Lastname = "Testnachname";
/// pState.SaveState(p);
/// Console.WriteLine("Zweiter Zustand : " + p);
///
/// p = pState.Undo() as Person;
/// Console.WriteLine("Nach undo : " + p);
///
/// p = pState.Redo() as Person;
/// Console.WriteLine("Nach redo : " + p);
///
/// //======================================================================
///
/// Console.WriteLine("\r\n");
///
/// p.Firstname = "Baxter";
/// p.Lastname = "Lomax";
/// pState.SaveState(p);
/// p.Firstname = "James";
/// p.Lastname = "Bond";
/// pState.SaveState(p);
///
/// var c = new Car();
/// var cState = new CarState();
/// c.TypeName = "VW";
/// c.Color = "black";
/// cState.SaveState(c);
/// c.TypeName = "Austin Martin";
/// c.Color = "silver";
/// cState.SaveState(c);
/// c.TypeName = "Maserati";
/// c.Color = "gold";
/// cState.SaveState(c);
///
/// Console.WriteLine("Jeder Objekt-Typ bekommt seinen eigenen StateManager ->\n");
/// p = pState.Undo() as Person;
/// Console.WriteLine("Hier müßte Baxter Lomax stehen: " + p);
///
/// c = cState.Undo() as Car;
/// Console.WriteLine("Hier müßte Austin Martin, silver stehen: " + c);
///
/// pState.ClearLists();
/// Console.WriteLine("CloneStack-Zähler: {0}/ UndoStack-Zähler: {1}",
/// pState.CloneListCount, pState.UndoListCount);
///
/// pState.Dispose();
/// // Der Stack von cState ist vom Dispose nicht betroffen:
/// Console.WriteLine("CloneStack-Zähler: {0}/ UndoStack-Zähler: {1}",
/// cState.CloneListCount, cState.UndoListCount);
///
/// // Verhindert das selbsttätige Schließen des Konsolenfensters.
/// Console.WriteLine("\nPress any key to terminate the program.");
/// Console.ReadKey();
/// }
/// }
///
/// //======================================================================
///
/// public class Car : ICloneable
/// {
/// #region Public Properties
///
/// public string Color { get; set; }
///
/// public string TypeName { get; set; }
///
/// #endregion
///
/// #region Public Methods
///
/// public object Clone()
/// {
/// return MemberwiseClone();
/// }
///
/// public override string ToString()
/// {
/// return String.Format("{0}, {1}", TypeName, Color);
/// }
///
/// #endregion
/// }
///
/// //======================================================================
///
/// public class CarState : StateManager
/// {
/// #region Public Methods
///
/// public override void SaveState(ICloneable car)
/// {
/// base.ObjectHandle = car.Clone() as Car;
/// base.Save();
/// }
///
/// #endregion
/// }
///
/// //======================================================================
///
/// public class Person : ICloneable
/// {
/// #region Public Properties
///
/// public String Firstname { get; set; }
///
/// public String Lastname { get; set; }
///
/// #endregion
///
/// #region Public Methods
///
/// public object Clone()
/// {
/// return MemberwiseClone();
/// }
///
/// public override string ToString()
/// {
/// return String.Format("{0} {1}", Firstname, Lastname);
/// }
///
/// #endregion
/// }
///
/// //======================================================================
///
/// public class PersonState : StateManager
/// {
/// #region Public Methods
///
/// public override void SaveState(ICloneable person)
/// {
/// base.ObjectHandle = person.Clone() as Person;
/// base.Save();
/// }
///
/// #endregion
/// }
/// }
///
/// /* Output:
/// Startzustand : George Jetson
/// Zweiter Zustand : Testvorname Testnachname
/// Nach undo : George Jetson
/// Nach redo : Testvorname Testnachname
///
///
/// Jeder Objekt-Typ bekommt seinen eigenen StateManager ->
///
/// Hier müßte Baxter Lomax stehen: Baxter Lomax
/// Hier müßte Austin Martin, silver stehen: Austin Martin, silver
/// CloneStack-Zähler: 0/ UndoStack-Zähler: 0
/// CloneStack-Zähler: 2/ UndoStack-Zähler: 1
///
/// Press any key to terminate the program.
///
/// */
/// ]]>
/// </code>
/// </example>
public abstract class StateManager : IDisposable
{
#region Events
/// <summary>
/// Tritt ein wenn die Kapazität der Liste erreicht ist.
/// </summary>
public static event Action CapacityExceeded;
#endregion
#region Static Fields
private static object syncLock = new object();
#endregion
#region Fields
private LinkedList<ICloneable> cloneList = new LinkedList<ICloneable>();
private bool disposed = false;
private LinkedList<ICloneable> undoList = new LinkedList<ICloneable>();
/// <summary>
/// Hält die Information ob unmanaged Ressourcen verwendet werden oder nicht.
/// </summary>
private bool useImages;
#endregion
#region Public Properties
/// <summary>
/// Gibt die über die SetCapacity-Methode gesetzte Kapazität der CloneListe zurück.
/// </summary>
public int Capacity
{
get;
private set;
}
/// <summary>
/// Gibt den aktuellen Zählerstand der CloneListe zurück.
/// </summary>
public int? CloneListCount
{
get
{
if(this.cloneList != null)
return this.cloneList.Count;
else
return null;
}
}
/// <summary>
/// Gibt den aktuellen Zählerstand der UndoListe zurück.
/// </summary>
public int? UndoListCount
{
get
{
if(this.undoList != null)
return this.undoList.Count;
else
return null;
}
}
#endregion
#if DEBUG
internal LinkedList<ICloneable> CloneListMirror
{
get
{
return cloneList;
}
}
internal LinkedList<ICloneable> UndoListMirror
{
get
{
return undoList;
}
}
#endif
#region Protected Properties
/// <summary>
/// Setzt das aktuelle Objekt-Handle oder ruft dieses ab.
/// </summary>
protected ICloneable ObjectHandle
{
get;
set;
}
#endregion
#region Public Methods
/// <summary>
/// Löscht alle Inhalte der Listen,
/// ohne die List-Instanzen selber zu zerstören.
/// </summary>
public void ClearLists()
{
this.cloneList.Clear();
this.undoList.Clear();
}
/// <summary>
/// Zerstört alle Instanzen im StateManager
/// und gibt den von ihnen verwendeten Speicher frei.
/// </summary>
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Wiederholt die letzte Operation.
/// Diese Methode ist Threadsicher.
/// </summary>
/// <returns>Das Objekt in dem Zustand,
/// in dem es sich vor dem letzten Undo befand.</returns>
public ICloneable Redo()
{
lock(syncLock)
{
this.ObjectHandle = this.undoList.Last.Value;
this.undoList.RemoveLast();
this.cloneList.AddLast(this.ObjectHandle);
return this.ObjectHandle;
}
}
/// <summary>
/// Fügt eine Instanz in die CloneListe ein.
/// Diese Methode ist Threadsicher.
/// </summary>
/// <exception cref="NullReferenceException">Wirft eine NullReferenceException
/// wenn versucht wird, ein leeres ObjectHandle an die CloneListe zu hängen.</exception>
protected void Save()
{
lock(syncLock)
{
if(this.ObjectHandle != null)
{
AddObjectByCondition();
}
else
throw new NullReferenceException("ObjektHandle ist leer!");
}
}
/// <summary>
/// Transportiert in einer abgeleiteten Klasse
/// eine geklonte Instanz an den StateManager weiter.
/// </summary>
/// <param name="clone">Das geklonte Objekt</param>
public abstract void SaveState(ICloneable clone);
/// <summary>
/// Setzt die Kapazität der CloneListe.
/// Ein Wert von 0 deaktiviert die Kapazitätsbegrenzung.
/// </summary>
/// <param name="capacity">Die Kapazität</param>
public void SetCapacity(int capacity)
{
this.Capacity = capacity;
}
/// <summary>
/// Macht eine Operation rückgängig.
/// Diese Methode ist Threadsicher.
/// </summary>
/// <returns>Das Objekt in dem Zustand,
/// in dem es sich vor dem letzten Save befand.</returns>
public ICloneable Undo()
{
lock(syncLock)
{
this.ObjectHandle = this.cloneList.Last.Value;
this.cloneList.RemoveLast();
this.undoList.AddLast(this.ObjectHandle);
this.ObjectHandle = this.cloneList.Last.Value;
return this.ObjectHandle;
}
}
#endregion
#region Private Methods
/// <summary>
/// Schiebt ein neues Objekt an das Ende der Liste.
/// </summary>
private void AddDirect()
{
this.cloneList.AddLast(this.ObjectHandle);
if(ObjectHandle.GetType() == typeof(Bitmap))
useImages = true;
}
/// <summary>
/// Prüft, ob die Liste die mit SetCapacity angegebene Kapazität überschritten hat,
/// und führt dem entsprechende Operationen aus.
/// </summary>
private void AddObjectByCondition()
{
if(Capacity == 0 || Capacity > 0 && cloneList.Count < Capacity)
{
AddDirect();
}
else
ClipAndAdd();
}
/// <summary>
/// Im Falle daß die Listenkapazität erreicht ist,
/// wird das erste und damit älteste Element aus der Liste entfernt,
/// und dann das neue Element am Ende angefügt.
/// </summary>
private void ClipAndAdd()
{
this.cloneList.RemoveFirst();
AddDirect();
OnCapacityExceeded();
}
private void Dispose(bool disposing)
{
if(!this.disposed)
{
if(disposing)
{
// prüfen ob Images auf der Liste liegen
if(this.useImages)
{
// Images in der UndoListe zerstören
while(this.UndoListCount > 0)
{
object obj = this.undoList.Last.Value;
this.undoList.RemoveLast();
if(obj.GetType() == typeof(Bitmap))
{
// Object in Image casten
Image img = obj as Image;
// Image zerstören
img.Dispose();
}
}
// images in der CloneListe zerstören
while(this.CloneListCount > 0)
{
object obj = this.cloneList.Last.Value;
this.cloneList.RemoveLast();
if(obj.GetType() == typeof(Bitmap))
{
Image img = obj as Image;
img.Dispose();
}
}
}
this.cloneList = null;
this.undoList = null;
this.ObjectHandle = null;
/* Ohne Collect() kommt der GC vielleicht irgendwann mal vorbei,
* aber es soll ja schnell gehen. */
GC.Collect();
}
}
this.disposed = true;
}
private void OnCapacityExceeded()
{
if(CapacityExceeded != null)
CapacityExceeded();
}
#endregion
}
}
|