Click here to Skip to main content
Click here to Skip to main content

Implementing Object Undo and Redo capabilities in .NET

, 4 May 2004
Rate this:
Please Sign up or sign in to vote.
Implementing Object Undo and Redo capabilities in .NET

Introduction

In Object Oriented world what needs to be done if a users accidentally changes the value of a property? Very often users make a mistake that needs to be cancelled out. The situation is more problematic if the object state has been persisted. In order to make our objects more user friendly, we need to provide a mechanism of un-doing the changes. This article provides one such implementation of the undo/ redo capability. The code represented is in C#.

Basically, if we look at the undo capability of other applications – like word, we notice that the undo functionality is implemented as a Last In – First Out mechanism. This obviously calls for a Stack based implementation.

To understand a stack, think about a long tube with the diameter just about the size of a tennis ball. Also assume that this pipe is closed at one end. So if one puts a tennis balls inside in such a tube, one after another, then what happens if want to take out these balls? It should be clear that these balls can only be taken out in the reverse order of insertion – i.e. the last ball inserted would be the first to be taken out. For more detail, refer to Stack class in the System.Collection namespace.

So essentially what need to do is to somehow push the changes to the object’s state in a stack and then in the event of an undo operation, pop the last entry off this stack and revert back the changes. Now, the next problem to tackle is of recording the state information. An object may have multiple properties and hence any of these may change. We would therefore need a generic implementation that could work with all objects

The PropertyValue class

In order to record the state information, we need to define a class that will store the value of a property prior to it being changed. Now, since we are building a generic concept, we cannot type cast the value. The property type can be any thing – long, int, string, ArrayList, User, Employee, double etc. So when we define our PropertyValue class, we would declare the value as object. The main reason for this is that .NET automatically boxes intrinsic data types to objects of corresponding type and hence the developer does not have to spend time in doing the type conversion.

Therefore our class structure may look like this.

The PropertyName property would store the name of the property whose state is being preserved. The PropertyValue property would store the original state prior to the change being applied. Off course, these two properties can be set while calling the parameterized constructor as well.

The ModificationHistory class

The purpose of this class is to a) store the reference to the parent object that will subscribe to Undo, b) to maintain the stack of changes for the undo operation and c) to undo the changes made in the parent object.

While we are storing the undo stack, we also can store the Redo stack to reapply the changes that were undone. Therefore our class structure may look like following

The constructor is called with the reference to the parent object that wishes for undo stack to be maintained. All undo, redo operations are performed on this object reference passed.

public ModificationHistory(object parent)
{
  redoStack = new Stack();
  undoStack = new Stack();
  objParent = parent;
}  

There are two readonly Boolean properties called CanUndo and CanRedo that return true or false depending on whether the Undo and Redo stacks contain data or not – respectively.

public bool CanUndo
{
  get
  {
    return (undoStack.Count>0?true:false);
  }
}  

The Store method is called from the parent object prior to its property being changed with the property name and value.

public void Store (string propName, object propVal)
{
  PropertyValue pVal = new PropertyValue(propName, propVal);
  undoStack.Push(pVal);
}

For the undo operation, we would use reflection on the objParent to determine the property corresponding to the name stored and then assign it with the old value stored. Since we would be applying the undo, we would then be required to remove the last saved entry from the stack.

public void Undo()
{
   if (undoStack.Count>0)
    {
    PropertyValue pVal = (PropertyValue)undoStack.Pop();
    Type compType = objParent.GetType();
    PropertyInfo pinfo = compType.GetProperty(pVal.PropertyName);

    //first get the existing value and push to redo stack
    object oldVal = pinfo.GetValue(objParent,null);
    PropertyValue pRedo = new PropertyValue(pVal.PropertyName, oldVal);
    redoStack.Push(pRedo);      
    // apply the value that was popped off the undo stack
    pinfo.SetValue(objParent,pVal.Value, null);
    }
}

In the above code fragment, what is being done is that first a value if being popped off the undo stack. Since we are storing only PropertyValue type of objects, the extracted object reference is typed cast as such. Recall that the objParent is the reference to the parent object whose state was saved. We need use reflection to get a handle on the property concerned. This is achieved by using the Type information of the parent to locate the PropertyInfo of the property whose name corresponds to PropertyValue->PropertyName. Once we have this handle, we can invoke the SetValue method to assign a new value to the object. Since this new value is actually the old value, we effectively achieve the undo mechanism. However, just before we call the pinfo.SetValue, we need to push the current state of the property into the redo stack.

The subscribing class

To complete the picture we also need to look at the implementation. Lets say that we create an Entity class with these capabilities and then all implementing classes can inherit from this entity class. Let’s make the class diagram as follows

In the Entity class, we maintain an instance variable of the ModificationHistory class and then delegate the actions to store, undo and redo operations to this object. The AddHistory method defined in the Entity class has been declared with a protected scope making this method visible to all classes that inherit from Entity. In the User class, if we need to maintain the undo information of LastName then the code that needs to be written is as follows –

public string LastName
{
  get
  {
    return mLastName;
  }
  set
  {
    // store the undo information first
    AddHistory("LastName",mLastName);
    mLastName = value;
  }
}

A potential problem

As can be seen from the code fragment above, the original value is being pushed to stack. When the Undo is called, then the same property is executed with the original result. This creates a loop. For example- Let say the original value of the mLastName is “Peter”. Now If this is changed to “Andre” and then to “George”. Therefore in the stack, the values are “Andre” -> “Peter”. Now if we need to undo, what happens? The value of “Andre” gets popped out of stack and then the User.LastName property is called with this value. Now, at this point in time, the current value of mLastName is “George” so this will get pushed to the stack. That results in a situation with the stack becoming “George” -> “Peter”. Calling undo will again make it “Andre” -> “Peter”.

So how do we solve this?

This is solved by maintaining a flag to denote whether or not undo operation is being performed. If the flag is set then we should not store the value in the stack. Consider a code fragment in the Entity class

protected  void AddHistory(string propertyName, object Value)
{
  if (!mBeingUndone)
    mHistory.Store(propertyName, Value);
}

So the next problem is to identify when to set or reset this mBeingUndone flag. The best place for this to happen is when the call is made for Undo in the Entity class.

public void Undo()
{
  if (mHistory.CanUndo)
  {
    mBeingUndone = true;
    mHistory.Undo();
    mBeingUndone = false;
  }
}

Conclusion

The code presented here maintains two stack for Undo and Redo capabilities. As the object’s state is undone, the content of these stacks may vary. Since the code presented here does not deal with object persistence, it is left to the developer. Also the developer needs to determine whether or not the stacks need to be emptied after persisting the object to database. Some folks may prefer the entire undo stack be applied at once rather than calling Undo() multiple times. I feel that these choices are best left to the developer as there cannot be a one solution for all problems.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

Pankaj Chatterjee
Web Developer
India India
No Biography provided

Comments and Discussions

 
GeneralMy vote of 5 Pinmemberjeganssnit15-Aug-10 20:17 
GeneralUndo / Redo Rectangle property PinmemberwebScripter9-Nov-04 4:56 
GeneralGreat Work Pinmemberfaresss23-Sep-04 12:31 
Generali have couple of questions PinmemberSunjay Goyal7-May-04 2:15 
GeneralGood but... PinmemberRui Dias Lopes4-May-04 21:49 
GeneralRe: Good but... PinmemberJeff Varszegi5-May-04 4:13 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140827.1 | Last Updated 5 May 2004
Article Copyright 2004 by Pankaj Chatterjee
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid