Add Undo/Redo or Back/Forward Functionality to your Application

I have often seen people asking on various forums how to implement Undo/Redo functionality or Back/Forward functionality in their applications. Typically an undo/redo in text-editors or back/forward in file/folder explorers or customized webbrowsers are common examples.

So today we will see how to develop a class to implement such functionality.

Add a new class to your project. Name it UndoRedoClass.

Add the following code to that class.

Public Class UndoRedoClass(Of T)
    Private UndoStack As Stack(Of T)
    Private RedoStack As Stack(Of T)

    Public CurrentItem As T
    Public Event UndoHappened As EventHandler(Of UndoRedoEventArgs)
    Public Event RedoHappened As EventHandler(Of UndoRedoEventArgs)

    Public Sub New()
        UndoStack = New Stack(Of T)
        RedoStack = New Stack(Of T)
    End Sub

    Public Sub Clear()
        UndoStack.Clear()
        RedoStack.Clear()
        CurrentItem = Nothing
    End Sub

    Public Sub AddItem(ByVal item As T)
        If CurrentItem IsNot Nothing Then UndoStack.Push(CurrentItem)
        CurrentItem = item
        RedoStack.Clear()
    End Sub

    Public Sub Undo()
        RedoStack.Push(CurrentItem)
        CurrentItem = UndoStack.Pop()
        RaiseEvent UndoHappened(Me, New UndoRedoEventArgs(CurrentItem))
    End Sub

    Public Sub Redo()
        UndoStack.Push(CurrentItem)
        CurrentItem = RedoStack.Pop
        RaiseEvent RedoHappened(Me, New UndoRedoEventArgs(CurrentItem))
    End Sub

    Public Function CanUndo() As Boolean
        Return UndoStack.Count > 0
    End Function

    Public Function CanRedo() As Boolean
        Return RedoStack.Count > 0
    End Function

    Public Function UndoItems() As List(Of T)
        Return UndoStack.ToList
    End Function

    Public Function RedoItems() As List(Of T)
        Return RedoStack.ToList
    End Function
End Class

Public Class UndoRedoEventArgs
    Inherits EventArgs

    Private _CurrentItem As Object
    Public ReadOnly Property CurrentItem() As Object
        Get
            Return _CurrentItem
        End Get
    End Property

    Public Sub New(ByVal currentItem As Object)
        _CurrentItem = currentItem
    End Sub
End Class

Below is the explanation of various public elements of this class:

  • CurrentItem: This holds the current item. It is neither in the undo stack nor the redo stack. It is in between the two of them.
  • UndoHappened: This event is fired when Undo method is called.
  • RedoHappened: This event is fired when Redo method is called.
  • Clear: Calling this method will clear both the stacks as well as the current item. It is as good as creating a new instance of this class.
  • AddItem: This method is used to add items to our undo list. This is the only way at present to add items to our undo stack.
  • Undo: Calling this method will undo the last item added to our undo list. The item is moved to redo list.
  • Redo: Calling this method will redo the last item added to the redo list. The item is moved to undo list.
  • CanUndo: This method can be used to check whether undo is possible or not. It is useful when we want to take decisions like disabling the undo buttons etc. when undo is not possible.
  • CanRedo: This method can be used to check whether redo is possible or not. It is useful when we want to take decisions like disabling the redo buttons etc. when redo is not possible.
  • UndoItems: Returns a list of undo items we currently have in the undo stack.
  • RedoItems: Returns a list of redo items we currently have in the redo stack.
  • UndoRedoEventArgs: This is the eventargs for our UndoHappened and RedoHappened events. The e.CurrentItem will have the current item which was undone/redone.

This is all we need to add such capabilities to our application.

The class uses generics. So it can be used for virtually any purpose we would ever need.

The class uses two stacks to implement the functionality. One stack holds the undo items and the other one holds the redo items. A stack uses Last-In-First-Out principle and is in accordance with our undo/redo needs. We want to go back to the immediate last item when undoing and the immediate next item when redoing. So far everything looks good.

This is OK when we want to implement unlimited undo/redo. But there would be many situations when you want to limit it due to high memory constraints, when working with big classes. If you try to add a Limit, you will soon find that the stack is not the best choice for this. This is because, you will have to discard the items from the beginning of the stack, and there is no way to do this.

For now this is OK as it will work in most of the common situations. We will modify our class as and when we need to. All we need to remember is this limitation we have (of not having a limit over number of items).

Working Demos

Some applications that demonstrates the use of this class can be found here:

  • Text-Only WebBrowser – This application demonstrates the use of our UndoRedoClass by creating a Text-Only WebBrowser and implementing smooth flow of back/forward navigation along with some advanced features.
  • Undo/Redo Capable TextBox – This demo shows how to add undo/redo capabilities to the windows forms TextBox control.

 

13 Responses to “Add Undo/Redo or Back/Forward Functionality to your Application”

  1. Patrick Osborne Says:

    I find this article to be very helpful.
    When do you think you will post the followup articles?

  2. Phil Oh Says:

    I am really looking forward to the followup articles. I hope they will come soon.

  3. UndoRedoClass Example « Pradeep1210's Blog Says:

    […] Add Undo/Redo or Back/Forward functionality to your application […]

  4. UndoRedoClass Example « Pradeep1210's Blog Says:

    […] This is a continuation of my previous article: Add Undo/Redo or Back/Forward functionality to your application […]

  5. Undo/Redo Capable TextBox (winforms) « Pradeep1210's Blog Says:

    […] Add a new class named UndoRedoClass and add the code from our first part of the article into […]

  6. Nana Derick Says:

    First of all i would say your willingness to serve least thinkers like me shall always made you known to all. That was such a vital point to consider. Thank you so much, and i’m very happy to have landed in your blog. Please, all the same, i’m just a beginner, and just have some basic knowledge in lua. However, it has always been such a big challenge to apply translate between the languages. Thus, how best can you help me with this class in lua please? I’m sorry for my english. Thank you

  7. Jeff Says:

    You should indicate to visitors dropping by which VB this code applies to—-after finding the page many will not know if this is what they are looking for. Is it for VB6? VB.Net? ASP.Net VB.net backcoding?

  8. WiiMaxx Says:

    Here a c# version

    public delegate void UndoHappened(object sender, UndoRedoEventArgs e);

    public delegate void RedoHappened(object sender, UndoRedoEventArgs e);

    public class UndoRedoWrapper : INotifyPropertyChanged
    {
    private Stack UndoStack;
    private Stack RedoStack;

    public T CurrentItem;

    public event UndoHappened OnUndo;

    public event RedoHappened OnRedo;

    public UndoRedoWrapper()
    {
    UndoStack = new Stack();
    RedoStack = new Stack();
    }

    public void Clear()
    {
    UndoStack.Clear();
    RedoStack.Clear();
    CurrentItem = default(T);

    RaisePropertyChangedEvent(“CanUndo”);
    RaisePropertyChangedEvent(“CanRedo”);
    }

    public void AddItem(T item)
    {
    if (CurrentItem != null)
    UndoStack.Push(CurrentItem);

    CurrentItem = item;

    RaisePropertyChangedEvent(“CanUndo”);
    RaisePropertyChangedEvent(“CanRedo”);
    }

    public void Undo()
    {
    RedoStack.Push(CurrentItem);

    CurrentItem = UndoStack.Pop();
    if (OnUndo != null)
    OnUndo(this, new UndoRedoEventArgs(CurrentItem));

    RaisePropertyChangedEvent(“CanUndo”);
    RaisePropertyChangedEvent(“CanRedo”);
    }

    public void Redo()
    {
    UndoStack.Push(CurrentItem);

    CurrentItem = RedoStack.Pop();
    if (OnRedo != null)
    OnRedo(this, new UndoRedoEventArgs(CurrentItem));

    RaisePropertyChangedEvent(“CanUndo”);
    RaisePropertyChangedEvent(“CanRedo”);
    }

    public bool CanUndo
    {
    get { return UndoStack.Count > 0; }
    }

    public bool CanRedo
    {
    get { return RedoStack.Count > 0; }
    }

    public List UndoItems()
    {
    return UndoStack.ToList();
    }

    public List RedoItems()
    {
    return RedoStack.ToList();
    }

    #region INotifyPropertyChanged Member

    public event PropertyChangedEventHandler PropertyChanged;

    public void RaisePropertyChangedEvent(string propertyName)
    {
    PropertyChangedEventHandler handler = PropertyChanged;

    if (handler != null)
    handler(this, new PropertyChangedEventArgs(propertyName));
    }

    #endregion
    }

    public class UndoRedoEventArgs : EventArgs
    {
    private object _currentItem;

    public object CurrentItem
    {
    get { return _currentItem; }
    }

    public UndoRedoEventArgs(object currentItem)
    {
    _currentItem = currentItem;
    }
    }

  9. Sugumaran Says:

    what is the best and fastest method to implement undo/redo for image oriented application.


Leave a comment