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
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
}
}
4 Kommentare zum Snippet