The Artima Developer Community
Sponsored Link

.NET Buzz Forum
Undo und Redo implementieren

0 replies on 1 page.

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 0 replies on 1 page
-

Posts: 1524
Nickname: nitronic
Registered: Jul, 2006

Norbert Eder works as a software architect.
Undo und Redo implementieren Posted: Mar 12, 2008 9:37 AM
Reply to this message Reply

This post originated from an RSS feed registered with .NET Buzz by -.
Original Post: Undo und Redo implementieren
Feed Title: Norbert Eder - Living .NET
Feed URL: http://feeds.feedburner.com/NorbertEder-Livingnet
Feed Description: Copyright (c)2005, 2006 by Norbert Eder
Latest .NET Buzz Posts
Latest .NET Buzz Posts by -
Latest Posts From Norbert Eder - Living .NET

Advertisement
Es kommt ja doch häufig vor, dass eine Anwendung ein Undo bzw. ein Redo unterstützen muss. Wird diese Funktionalität gefordert wird immer wieder nach einer Umsetzungsmöglichkeit gefragt. Hier eine kleine Lösung, die für diese Zwecke verwendet werden kann.

Hinweis: In diesem Beispiel werden keine Fehlerabfragen gemacht. Es empfiehlt sich, einen Ausführungsstatus zu integrieren, der das Verhalten entsprechend beeinflusst.

Design


Um diese Funktionalität umzusetzen, wird das Command-Pattern verwendet. Dieses eignet sich sehr gut dazu, Aktionen auszuführen und diese auch wieder zurück zu nehmen. Erweitert wird das Command-Pattern um einen CommandManager, der für die Ausführung und Verwaltung der einzelnen Commands zuständig ist.



Implementierung


Zuerst wird eine Basisklasse für den Command implementiert, welche die Methoden Execute(), Undo und Redo zur Verfügung stellt.

public abstract class Command
{
    public abstract void Execute();

    public abstract void Undo();

    public abstract void Redo();
}


Als weiteren Schritt wird der CommandManager implementiert. Dieser ist in diesem Fall ein Singleton (damit er nur einmal pro Instanz vorhanden sein kann) und bietet nach aussen hin die selbe Funktionalität wie ein Command an. Intern werden die einzelnen Commands jedoch in einem Stack behalten, um den Verlauf der einzelnen Commands nachvollziehen zu können. Darüber kann nun ein Undo bzw. ein Redo abgebildet werden.

public class CommandManager
{
    #region Static Attributes

    private static object _lockObject = new object();
    private static CommandManager _instance = null;

    #endregion Static Attributes

    #region Attributes

    private Stack<Command> _commandStack = new Stack<Command>();
    private Stack<Command> _undoneStack = new Stack<Command>();

    #endregion Attributes

    #region ctor

    private CommandManager() { }

    #endregion ctor

    #region Static Methods

    public static CommandManager GetInstance
    {
        get
        {
            if (_instance == null)
            {
                lock (_lockObject)
                {
                    _instance = new CommandManager();
                }
            }
            return _instance;
        }
    }

    #endregion Static Methods

    #region Public Methods

    public void Execute(Command command)
    {
        command.Execute();
        _commandStack.Push(command);
    }

    public void Redo()
    {
        Command redoCommand = _undoneStack.Pop();
        redoCommand.Redo();
        _commandStack.Push(redoCommand);
    }

    public void Undo()
    {
        Command undoCommand = _commandStack.Pop();
        undoCommand.Undo();
        _undoneStack.Push(undoCommand);
    }

    #endregion Public Methods
}


Damit ist die eigentliche Implementierung bereits abgeschlossen und wir können zu einem ersten Test schreiten. Hierzu benötigen wir ein paar kleinere Klassen, die nicht wirklich etwas aufregendes machen.

Hier eine Klasse Person, die zwei Eigenschaften zur Verfügung stellt, anhand derer getestet wird. Zudem implementiert sie das Interface ICloneable (wird im nachfolgenden Command benutzt) und überschreibt die ToString-Methode.

public class Person : ICloneable
{
    #region Properties

    public String Firstname { get; set; }
    public String Lastname { get; set; }

    #endregion Properties

    #region ICloneable Members

    public object Clone()
    {
        Person p = new Person();
        p.Firstname = this.Firstname;
        p.Lastname = this.Lastname;
        return p;
    }

    #endregion ICloneable Members

    #region Overrides

    public override string ToString()
    {
        return String.Format("{0}, {1}", Lastname, Firstname);
    }

    #endregion Overrides
}


Als nächsten Schritt müssen wir noch einen konkreten Command implementieren, der eine Aktion durchführt. Dieser wird sich lediglich ein wenig mit den Eigenschaften der Person spielen:

public class PersonCommand : Command
{
    private Person _person;
    private Person _personStateBefore;

    public PersonCommand(Person person)
    {
        _person = person;
    }

    public override void Execute()
    {
        _personStateBefore = _person.Clone() as Person;

        _person.Firstname = "Norbert";
        _person.Lastname = "Eder";
    }

    public override void Undo()
    {
        _person.Firstname = _personStateBefore.Firstname;
        _person.Lastname = _personStateBefore.Lastname;
    }

    public override void Redo()
    {
        Execute();
    }
}


Wichtig ist an dieser Stelle nur, dass sich der Command den ursprünglichen Status des übergebenen Objektes merkt und somit eine Undo bzw. Redo-Funktionalität anbieten kann.

Schlussendlich ein Stück Code, welches bei mir in einer Konsolen-Anwendung direkt in der static void Main ausgeführt wird:

CommandManager manager = CommandManager.GetInstance;

Person p = new Person();
p.Firstname = "<not set>";
p.Lastname = "<not set>";
Console.WriteLine(
    String.Format("Before first execution: " + p.ToString()));

manager.Execute(new PersonCommand(p));
Console.WriteLine(
    String.Format("After first execution : " + p.ToString()));

manager.Undo();
Console.WriteLine(
    String.Format("After undo            : " + p.ToString()));

manager.Redo();
Console.WriteLine(
    String.Format("After redo            : " + p.ToString()));

Console.Read();


Und nun möchte ich das Ergebnis nicht vorenthalten:



Fazit


Wie zu sehen ist, ist ein Undo bzw. Redo-Mechanismus sehr einfach zu implementieren. Abhängig der Umgebung in welcher diese Methode zu tragen kommt, müssen eventuell weitere Mechanismen eingefügt werden.

Read: Undo und Redo implementieren

Topic: You can now build the source code in either VS2k5 or VS2k8 Previous Topic   Next Topic Topic: AKS v2 - Accessibility Kit for SharePoint

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use