Simple Undo/redo library for C#/.NET(简单的C#.Net撤销、重做库)

原文地址连接:

http://www.codeproject.com/Articles/456591/Simple-Undo-redo-library-for-Csharp-NET

可执行文件及源代码下载连接:

 http://download.csdn.net/detail/eyuanatqqdotcom/4579836

Introduction 

This article provides an example framework for undo/redo functionality without using Command or Momento pattern. The framework providesundo/redo stack and support for collated undo/redo.

Note: This is not intended to replace full blown Command or Memento pattern implementation but just demonstrates a simplistic way of performingundo/redo operations. This is especially useful for addingundo/redo support for existing applications. 

Background 

Recently a friend of mine asked for some help on implementing an undo/redo functionality in an existing application. The obvious choice for such an approach would be either Command or Memento pattern but that would have required making drastic changes to the application.

The solution provided in this article demonstrates a simplistic way to introduceundo/redo functionality at a chosen level of granularity (property/operation/multiple-operations). 

Simple undo operation example 

The key to using this library is to introduce undo behaviors at the relevant points of action. Using a verysimple example, if one of your objects has a property as follows:

public int Age
{
    get { return _age; }
 
    set
    {
       _age = value;
        NotifyPropertyChanged("Age");
    }
}

If you would like to implement undo behavior at this property's level (as opposed to UI level) then you should add the line shown below 

public string Name
{
    get { return _name; }
    set
    {
        UndoRedoManager.Instance().Push((x) => this.Name = x, _name, "Change name");
        _name = value;
        NotifyPropertyChanged("Name");
    }
}

The line UndoRedoManager.Instance().Push(a=> Age=a, _age, "Change age") can be broken down as follows:

  1. UndoRedoManager.Instance() -Get the instance ofUndoRedoManager singleton object. 
  2. Push- Push an undo record on the undo stack with the following data 
    • a=> Age=a  - The method to be called to perform undo. In this case, we are just declaring a in-place lambda expression which calls the Age set property accessor. 
    • _age - The data to be passed to the method. In this case, this member variable contains the current value of the age before it is changed.  
    • "Change age" - is the description of the undo operation (optional). 

So when an undo operation is called after one sets the Age property, then the lambda expression specified above gets called effectively resetting the age to the original value. Basically you are creating the Undo record at places where an undo is required.  There is no requirement to have a lambda expression - you can create your undo methods as non-anonymous methods. 

Note that the UndoRedoManager takes care of the condition in which this lambda expression is called in the context of an ongoing undo operation, in which case, the new lambda expression will be added to the redo stack. You will never explicitly add a redo operation to the stack. 

The signature of the UndoRedoManager.Push operation is      

public void Push<t>(UndoRedoOperation<t> undoOperation, T undoData, string description = "")   

As you can see that the data type is a template parameter and can be of any type.

Fundamentally, you will be pushing undo record (state) to the stack at any place where you want the user to be able to perform undo's. This is similar to maintaining a list of Commands being executed in the Command pattern and then calling Command.Undo when an undo needs to be performed. 

Slightly more complex example

To test the UndoRedoManager class, I downloaded and modified the DrawTools code from Code project and added undo-redo functionality to the supplied code. Given below is an example of how I added undo functionality to the Move operation ofDrawLine.cs class. 

public override void Move(int deltaX, int deltaY)
{
    UndoRedoManager.Instance().Push((dummy) => Move(-deltaX, -deltaY), this);     
    startPoint.X += deltaX;
    startPoint.Y += deltaY;
    endPoint.X += deltaX;
    endPoint.Y += deltaY;
    Invalidate();
}  

Here is another example 

public override void Normalize()
{
     UndoRedoManager.Instance().Push(r => DrawRectangle.GetNormalizedRectangle(r), rectangle);
     rectangle = DrawRectangle.GetNormalizedRectangle(rectangle);
}

Consolidating multiple undo operations 

Imagine the case where you are setting the Name and Age of the person in two different calls as shown below

Person p = new Person();
p.Name   = "new name";
p.Age    =  p.Age+1;   

Assuming that both Name and Age setters create undo records, the above code will result in two undo records in the undo stack. If you want to consolidate these into one undo record then you can surround the above with a transaction.

using (new UndoTransaction("optional description))
{
     p.Name = "new name";
     p.Age  = p.Age+1;
}

This will cause the two undo records to be  counted as one undo record. Another example would be in cases where one undoable operation may call another set of operations which are undoable themselves e.g.,  

private Person AddPerson(Person person)
{
    //Do not add if the person is already in the list
    Person personInList = _personList.Find(p => p.ID == person.ID);
    if (personInList != null)
    {
        return personInList;
    }
    UndoRedoManager.Instance().Push(p => RemovePerson(p), person,"Add Person");
    personListBindingSource.Position =  personListBindingSource.Add(person);
    return person;
}                       
 
private void btnAddTran_Click(object sender, EventArgs e)
{
    using (new UndoTransaction("Add Person"))
    {
        Person p = new Person() {};
        AddPerson(p);
        p.Name = "<Change Name>";
        p.Age = 0;
    }
}

In this case, if the UndoTransaction was not used in btnAddTran_Clickfunction, the undo stack would have contained 3 undo records (one for AddPerson, one for Name change, one for Age change) instead of just  1 record.  

UndoRedoManager operations

Apart from the Push operation describe above, the UndoRedoManager provides the following operations and events:

  • Undo() -This is called to perform an Undo operation. One should check if there are undo operations in the stack before calling this method. 
  • Redo() -This is used to perform a Redo operation.
    One should check if there are redo operations in the stack before calling this method.  
  • HasUndoOperations/HasRedoOperations - These can be called to determine if there are anyundo/redo records in the undo stack.
  • MaxItems -This sets/gets the maximum number of items to be stored in the stack.
  • UndoStackStatusChanged/RedoStackStatusChanged -These events are fired when items are added/removed from the undo stack. e.g., These events can be used to set the state ofundo/redo menu items. 

Redos  

Redos are automatically handled by the UndoRedoManager class. The user just has to push undo operations to theUndoRedoManager stack.

Contents of attached solution 

The attached solution consists of the following projects: 

  1. UndoMethods - This class library consists of theUndoManager class and other supporting classes. 
  2. UndoPatternSample - This sample demonstrates the various usages of UndoManager class.
  3. DrawToolkit projects- This is a modified version of  DrawTools from CodeProject withundo/redo functionality.  

License

This article, along with any associated source code and files, is licensed underThe Code Project Open License (CPOL)

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值