Rainer's profileCyrons BlogPhotosBlogListsMore ![]() | Help |
|
|
June 22 StateManager mit eingeschränkter Kapazität (Undo/Redo)Um eine Möglichkeit zu bieten, den Speicherverbrauch zu reduzieren, habe ich den StateManager noch einmal erweitert. Dazu kann der Stack auf eine beliebige Anzahl an Elementen eingeschränkt werden (muß aber nicht). Wird die Kapazität überschritten, fällt das zuerst eingefügte (und damit älteste) Element hinten heraus.
namespace _04_StateManagerWithList { using System; using System.Collections.Generic; using System.Drawing; /// <summary> /// Bietet in einer abgeleiteten Klasse Funktionen für Undo und Redo. /// </summary> 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 List<ICloneable> cloneList = new List<ICloneable>(); private bool disposed = false; private List<ICloneable> undoList = new List<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 uint 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 List<ICloneable> CloneListMirror { get { return cloneList; } } internal List<ICloneable> UndoListMirror { get { return undoList; } } #endif Demousing System; class StringState : StateManager { #region Public Methods public override void SaveState(ICloneable clone) { ObjectHandle = clone.Clone() as ICloneable; Save(); } #endregion } class Program { #region Fields StringState state; #endregion #region Constructors internal Program() { state = new StringState(); } #endregion #region Private Methods private static void CapacityExceededEventHandler() { Console.WriteLine("Das älteste Element ist gerade hinten herausgefallen."); } private void DumpCloneStackContent() { foreach (var item in state.CloneListMirror) { Console.WriteLine(item.ToString()); } } static void Main() { // amount ist größer als die Listenkapazität. uint amount = 12; Program that = new Program(); StateManager.CapacityExceeded += new Action(CapacityExceededEventHandler); // Um den Unterschied zu sehen, folgende Zeile auskommentieren -> that.state.SetCapacity(10); // <- uint capacity = that.state.Capacity; if (capacity == 0) capacity = amount; for (int i = 0; i < amount; i++) { that.state.SaveState((i + 1).ToString()); } Console.WriteLine("Inhalt der Cloneliste:"); that.DumpCloneStackContent(); Console.WriteLine("Taste drücken um fortzufahren."); Console.ReadKey(); Console.WriteLine("Führe nun Undos aus..."); for (int i = 0; i < capacity - 1; i++) { Console.WriteLine(that.state.Undo().ToString()); } OutputDas älteste Element ist gerade hinten herausgefallen. Press any key to terminate the program.
June 16 "Kann Datei xyz nicht löschen. Sie wird von einer anderen Person oder Programm benutzt"Zumindest so ähnlich lautet die Fehlermeldung, der man leider viel zu häufig begegnet. Die Lösung ist Unlocker, zu finden auf dieser Webseite. Ich benutze das Ding schon lange und kann es empfehlen. WARNUNG: Das Ding macht seinen Job. Dadurch können die Programme, welche das Laufwerk bzw. die Datei blockieren, instabil werden! Von einem allzu leichtfertigen Umgang mit Unlocker muß ich also abraten. Allerdings lässt sich so eine Instabilität i.d.R. durch ein Aus- und Wiedereinloggen oder einen Neustart des betroffenen Programms/Dienstes leicht wieder beheben. Das schöne bei Unlocker ist auch, daß er in einer Liste anzeigt, welche(s) Programm(e) für die Blockade verantwortlich ist/sind. StateManager mit DisposingDas ist die finale Version meines StateManagers - es sei denn ihr habt noch Wünsche. :) namespace StateManagerMitDisposing { using System; using System.Collections.Generic; using System.Drawing; /// <summary> /// Bietet in einer abgeleiteten Klasse Funktionen für Undo und Redo. /// </summary> public abstract class StateManager : IDisposable { #region Static Fields private static object syncLock = new object(); #endregion #region Fields private Stack<ICloneable> cloneStack = new Stack<ICloneable>(); private Stack<ICloneable> undoStack = new Stack<ICloneable>(); private bool disposed = false; /// <summary> /// Hält die Information ob unmanaged Ressourcen verwendet werden oder nicht. /// </summary> private bool useImages; #endregion #region Public Properties /// <summary> /// Gibt den aktuellen Zählerstand des CloneStacks zurück. /// </summary> public int? CloneStackCount { get { if (this.cloneStack != null) return this.cloneStack.Count; else return null; } } /// <summary> /// Gibt den aktuellen Zählerstand des UndoStacks zurück. /// </summary> public int? UndoStackCount { get { if (this.undoStack != null) return this.undoStack.Count; else return null; } } #endregion #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 Stacks, /// ohne die Stack-Instanzen selber zu zerstören. /// </summary> public void ClearStacks() { this.cloneStack.Clear(); this.undoStack.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.undoStack.Pop(); this.cloneStack.Push(this.ObjectHandle); return this.ObjectHandle; } } /// <summary> /// Fügt eine Instanz in den CloneStack ein. /// Diese Methode ist threadsicher. /// </summary> /// <exception cref="NullReferenceException">Wirft eine NullReferenceException /// wenn versucht wird, ein leeres ObjectHandle auf dem Stack zu speichern.</exception> protected void Save() { lock (syncLock) { if (this.ObjectHandle != null) { this.cloneStack.Push(this.ObjectHandle); if (ObjectHandle.GetType() == typeof(Bitmap)) useImages = true; } 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> /// 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.cloneStack.Pop(); this.undoStack.Push(this.ObjectHandle); this.ObjectHandle = this.cloneStack.Peek(); return this.ObjectHandle; } } #endregion #region Private Methods private void Dispose(bool disposing) { if (!this.disposed) { if (disposing) { // prüfen ob Images auf dem Stack liegen if (this.useImages) { // Images im UndoStack zerstören while (this.UndoStackCount > 0) { object obj = this.undoStack.Pop(); if (obj.GetType() == typeof(Bitmap)) { // Object in Image casten Image img = obj as Image; // Image zerstören img.Dispose(); } } // images im CloneStack zerstören while (this.CloneStackCount > 0) { object obj = this.cloneStack.Pop(); if (obj.GetType() == typeof(Bitmap)) { Image img = obj as Image; img.Dispose(); } } } this.cloneStack = null; this.undoStack = null; this.ObjectHandle = null; /* Ohne Collect() kommt der GC vielleicht irgendwann mal vorbei, * aber es soll ja schnell gehen. */ GC.Collect(); } } this.disposed = true; } #endregion } } Laut CLRProfiler funktioniert das Disposing einwandfrei.
Ich habe eine Demoapplikation in mein Skydrive gestellt, mit der ihr euch selber einen kurzen Eindruck verschaffen könnt. June 09 Undo/Redo, NachtragKleine Erweiterung, große Wirkung. Hier nun der erweiterte StateManagernamespace StateManagerCountExtension { using System; using System.Collections.Generic; public abstract class StateManager { #region private fields private static object syncLock = new object(); private Stack<ICloneable> cloneStack = new Stack<ICloneable>(); private Stack<ICloneable> undoStack = new Stack<ICloneable>(); #endregion #region protected properties protected ICloneable ObjectHandle { get; set; } #endregion #region public properties public int CloneStackCount { get { return cloneStack.Count; } } public int UndoStackCount { get { return undoStack.Count; } } #endregion #region Abstract Methods public abstract void SaveState(ICloneable clone); #endregion #region Public Methods protected void Save() { lock(syncLock) { this.cloneStack.Push(this.ObjectHandle); } } public ICloneable Redo() { lock(syncLock) { this.ObjectHandle = this.undoStack.Pop(); this.cloneStack.Push(this.ObjectHandle); return this.ObjectHandle; } } public ICloneable Undo() { lock(syncLock) { this.ObjectHandle = this.cloneStack.Pop(); this.undoStack.Push(this.ObjectHandle); this.ObjectHandle = this.cloneStack.Peek(); return this.ObjectHandle; } } #endregion } } Und wieder ein kleines Demo (die Klassen Person und PersonState sind die gleichen wie im vorigen Artikel). namespace StateManagerCountExtension { June 08 Undo/Redo (multipel!) von ObjektzuständenDas Command Pattern, so wie es im Buch Head first Design Patterns gezeigt wird, und so wie man es bei dofactory findet, speichert Befehle. Dazu muß für jeden Befehlstyp eine eigene Klasse definiert werden (z.B. LichtAnBefehl, LichtAusBefehl). Ich weiß nicht wie es euch geht, aber nachdem ich die besagten Samples studiert habe, bin ich zu dem Schluß gekommen, daß das Command Pattern in dieser Form wenig praxisnah ist. Zum Einen wird der Code durch die Definition von allen in Frage kommenden Befehlsklassen unheimlich aufgeblasen.
StateManager Naked DemoZuerst möchte ich kurz den kuriosen Namen dieses Projekts erklären, bevor falsche Verdächtigungen aufkommen: StateManager KlasseSie ist abstrakt weil ein direkte Instantiierung nicht ratsam ist. Eine zwischengeschaltete ObjectState-Klasse muß die abstrakte Methode SaveState(ICloneable clone) überschreiben. Mit dem Parameter wird ein Objekt übergeben welches das ICloneable-Interface implementiert. Die Methoden Undo() und Redo() stammen jedoch direkt von der Basisklasse. Das macht den Code schlanker. ObjectState Klasse (hier PersonState und CarState)Diese Zwischenklasse liefert ein Objekt, welches das ICloneable Interface implementiert, an den StateManager. Jede ObjectState Klasse deklariert ihre eigene Instanz der StateManager-Klasse. Dadurch gibt es keine Konflikte. ObjektklassenAlle Objekt-Typen können verwaltet werden. Es ist ganz egal ob es sich, wie hier um ein paar simple Stringproperties handelt, oder z.B. um Bitmaps. Output des DemosStartzustand : George Jetson
Und jetzt der Code: namespace StateManagerDemo { using System; using System.Collections.Generic; public abstract class StateManager { private static object syncLock = new object(); private Stack<ICloneable> cloneStack = new Stack<ICloneable>(); private Stack<ICloneable> undoStack = new Stack<ICloneable>(); protected ICloneable ObjectHandle { get; set; } #region Abstract Methods public abstract void SaveState(ICloneable clone); #endregion Abstract Methods #region Public Methods protected void Save() { lock(syncLock) { this.cloneStack.Push(this.ObjectHandle); } } public ICloneable Redo() { lock(syncLock) { this.ObjectHandle = this.undoStack.Pop(); this.cloneStack.Push(this.ObjectHandle); return this.ObjectHandle; } } public ICloneable Undo() { lock(syncLock) { this.ObjectHandle = this.cloneStack.Pop(); this.undoStack.Push(this.ObjectHandle); this.ObjectHandle = this.cloneStack.Peek(); return this.ObjectHandle; } } #endregion Public Methods } //====================================================================== internal class Person : ICloneable { /* Auch private Member werden gesichert. * Das ist für zukünftige Erweiterungen von Interesse. */ private string inofficial = "not official!"; public string Firstname { get; set; } public string Lastname { get; set; } #region ICloneable Members public object Clone() { return this.MemberwiseClone(); } #endregion public override string ToString() { return String.Format("{0} {1}", this.Firstname, this.Lastname); } } //====================================================================== internal class PersonState : StateManager { #region StateManager Members public override void SaveState(ICloneable person) { this.ObjectHandle = person.Clone() as ICloneable; this.Save(); } #endregion } //====================================================================== internal class Car : ICloneable { public string TypeName { get; set; } public string Color { get; set; } #region ICloneable Members public object Clone() { return this.MemberwiseClone(); } #endregion public override string ToString() { return String.Format("{0}, {1}", this.TypeName, this.Color); } } //====================================================================== internal class CarState : StateManager { #region StateManager Members public override void SaveState(ICloneable car) { this.ObjectHandle = car.Clone() as ICloneable; this.Save(); } #endregion } //====================================================================== internal class Program { public static void Main() { Person p = new Person(); PersonState personState = new PersonState(); p.Firstname = "George"; p.Lastname = "Jetson"; personState.SaveState(p); Console.WriteLine("Startzustand : " + p.ToString()); p.Firstname = "Testvorname"; p.Lastname = "Testnachname"; personState.SaveState(p); Console.WriteLine("Zweiter Zustand : " + p.ToString()); p = personState.Undo() as Person; Console.WriteLine("Nach undo : " + p.ToString()); p = personState.Redo() as Person; Console.WriteLine("Nach redo : " + p.ToString()); //====================================================================== Console.WriteLine("\r\n"); p.Firstname = "Baxter"; p.Lastname = "Lomax"; personState.SaveState(p); p.Firstname = "James"; p.Lastname = "Bond"; personState.SaveState(p); Car c = new Car(); CarState carState = new CarState(); c.TypeName = "VW"; c.Color = "black"; carState.SaveState(c); c.TypeName = "Austin Martin"; c.Color = "silver"; carState.SaveState(c); c.TypeName = "Maserati"; c.Color = "gold"; carState.SaveState(c); Console.WriteLine("Jeder Objekt-Typ bekommt seinen eigenen StateManager ->"); p = personState.Undo() as Person; Console.WriteLine("Hier müßte Baxter Lomax stehen: " + p.ToString()); c = carState.Undo() as Car; Console.WriteLine("Hier müßte Austin Martin, silver stehen: " + c.ToString()); // Verhindert das selbsttätige Schließen des Konsolenfensters. Console.WriteLine("\nPress any key to terminate the program."); Console.ReadKey(); } } }
|
|
|