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).
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.