dotnet-snippets.de
Willkommen bei dotnet-snippets.de! Snippet hinzufügen Login Registrieren
Snippets in der Datenbank: 1319 | Anzahl registrierter User: 1268 | Besucher online: 291
Hauptmenü
Home
Snippet Wettbewerb
Top Ten
Zufälliger Snippet
Vista Gadget
T-Shirts für Geeks
FAQs
.NET Community
dotnet-forum.de
dotnet-kicks.de
Social
Facebook
Twitter
RSS Feeds
Rss Alle Snippets
Rss C#
Rss VB.NET
Rss C++
Rss Delphi.NET
Rss ASP.NET
Werbung
Alice DSL Flatrate

Partner
Partner von Codezone.de


Member of Microsoft Community Leader/Insider Program (CLIP)

Multiples Undo/Redo


Autor: Rainer Hilmer
Sprache: C#
Bewertung: 9,44
(2 Bewertungen)

Anzahl der Aufrufe: 1425
  

Beschreibung:

Bietet Undo/Redo Funktionen für beliebige Zustände bzw. Inhalte. Ein Demo:

using System;
using Cyrons;

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
*/


Der StateManager ist Bestandteil meines DotNetExpansions Framework:
http://cyrons.beanstalkapp.com/general/browse/DotNetExpansions/tags/(neueste Release Nummer)/

Das Paket beinhaltet neben dem Framework auch eine ausführliche Hilfedatei im chm-Format.

Weitere Informationen zum DotNetExpansions Framework gibt es hier:
http://dotnet-forum.de/blogs/rainerhilmer/archive/2009/09/28/dotnet-expansions-framework.aspx


Abgelegt unter: undo, redo, state.



C#
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
   }
}
Sie haben Fragen zu diesem Snippet oder brauchen Hilfe bei der .NET Entwicklung?
Freundliche und kompetente Entwickler helfen Ihnen gern weiter im Forum für .NET Entwicklung.



Kommentare:
(Zum Schreiben von Kommentaren bitte anmelden.)

Konstantin Gross schrieb am:  31.01.2010 19:17:48

Hey, herzlichen Glückwunsch zum ersten Platz =)
Rainer Hilmer schrieb am:  31.01.2010 23:25:12

Danke Konstantin. :-)
Gerade nachdem ich dein ISO-Snippet sah, hatte ich mir eigentlich keine Chancen mehr ausgemalt. Als ich gestern die Mail erhielt, sind mir fast die Augen aus dem Kopf gefallen.
Es freut mich daß die Jury einen Sonderpreis für dich eingerichtet hat.


Diese Snippets könnten für Sie interessant sein:
Keine Snippets verfügbar.

schlecht sehr gut
1 2 3 4 5 6 7 8 9 10
Nur angemeldete User können Snippets bewerten.