title: Undo/Redo implementation for an edit-control, based on stacks
body: <p>I'm trying to implement a simple undo/redo mechanism (based on stacks) for some events of a textbox.</p>
  
  <p>Before asking this, I've seen a lot of undo/redo implementations like <strong><a href="https://stackoverflow.com/questions/597792/how-to-implement-good-and-efficient-undo-redo-functionality-for-a-textbox">these</a></strong>, but more or less they are incomplete and showing things that I already knew (on the other hand, the profesional way using rare interfaces scapes from my compehenssion, so I want to follow this stack-based way), because those examples more than a undo/redo example for edit-controls, are a push/pop examples for stacks, but a undo/redo is a little more than writting a method to pop the last item of a "<em>undo stack</em>" and another method to pop the last item of a "<em>redo stack</em>", because in some point while the user is interacting with the control, the stacks should be cleared/resetted.</p>
  
  <p>I mean that in a real undo/redo mechanism for edit-controls, the "<em>redo stack</em>" should be cleared when the user undoes and the user makes a text modification in the control while the "<em>undo stack</em>" still contain items, so in that point there is nothing to redo because a change occured while undoing. 
  I didn't seen any complete example of a undo-redo mechanism in this way bearing in mind how must act the undo/redo stacks when changes occur in the control.</p>
  
  <p>I need help to properlly implement the logic of my undo/redo stacks, I started trying it by my own for days with a couple of trial and erros, but always escapes me some detail, because when I get one (undo or redo)stack to work properly, the other one stops working as expected undoing what it should not undo or redoing what it should not redo,
  so I discarded out (again) <strong><a href="http://pastebin.com/UMt5jS7d" rel="nofollow noreferrer">all the conditional logic that I written</a></strong> because my logic always is wrong, I should start from zero again with a proper conditional algorithm, I mean the proper conditionals to push or pop the stack items at the right moment.</p>
  
  <p>Then, more than words or suggestions, I need a working code that can solve the problem with my algorithm, I need to complete the algorithm logic of the <code>AddUndoRedoItem</code> method in the code below, this is a specific question about this.</p>
  
  <p>If I am missing a simpler solution following the same principles (a undo and redo stacks), I will accept that solution too.</p>
  
  <p>In C# or Vb.Net, no matter.</p>
  
  <p>PD:
  If because my poor English I didin't explained some thing prroperly and you are not totally sure of what kind of undo/redo I'am asking for, just I'm asking for a undo/redo as it sounds, just test the <strong>Ctrl+Z</strong>(undo) and <strong>Ctrl+Y</strong>(redo) keys in the Notepad while performing changes in the text undone or redone, see how it acts, that is a real undo/redo implementation, what I'm trying to reproduce with the stacks.</p>
  
  <hr>
  
  <p>This is the current code:</p>
  
  
  
  <pre class="lang-vb prettyprint-override"><code>Public Enum UndoRedoCommand As Integer
      Undo
      Redo
  End Enum
  
  Public Enum UndoRedoTextBoxEvent As Integer
      TextChanged
  End Enum
  
  Public NotInheritable Class UndoRedoTextBox
  
      Private ReadOnly undoStack As Stack(Of KeyValuePair(Of UndoRedoTextBoxEvent, Object))
      Private ReadOnly redoStack As Stack(Of KeyValuePair(Of UndoRedoTextBoxEvent, Object))
  
      Private lastCommand As UndoRedoCommand
      Private lastText As String
  
      Public ReadOnly Property Control As TextBox
          Get
              Return Me.controlB
          End Get
      End Property
      Private WithEvents controlB As TextBox
  
      Public ReadOnly Property CanUndo As Boolean
          Get
              Return (Me.undoStack.Count &lt;&gt; 0)
          End Get
      End Property
  
      Public ReadOnly Property CanRedo As Boolean
          Get
              Return (Me.redoStack.Count &lt;&gt; 0)
          End Get
      End Property
  
      Public ReadOnly Property IsUndoing As Boolean
          Get
              Return Me.isUndoingB
          End Get
      End Property
      Private isUndoingB As Boolean
  
      Public ReadOnly Property IsRedoing As Boolean
          Get
              Return Me.isRedoingB
          End Get
      End Property
      Private isRedoingB As Boolean
  
      Public Sub New(ByVal tb As TextBox)
  
          Me.undoStack = New Stack(Of KeyValuePair(Of UndoRedoTextBoxEvent, Object))
          Me.redoStack = New Stack(Of KeyValuePair(Of UndoRedoTextBoxEvent, Object))
  
          Me.controlB = tb
          Me.lastText = tb.Text
  
      End Sub
  
      Public Sub Undo()
  
          If (Me.CanUndo) Then
              Me.InternalUndoRedo(UndoRedoCommand.Undo)
          End If
  
      End Sub
  
      Public Sub Redo()
  
          If (Me.CanRedo) Then
              Me.InternalUndoRedo(UndoRedoCommand.Redo)
          End If
  
      End Sub
  
      ' Undoes or redoues.
      Private Sub InternalUndoRedo(ByVal command As UndoRedoCommand)
  
          Dim undoRedoItem As KeyValuePair(Of UndoRedoTextBoxEvent, Object) = Nothing
          Dim undoRedoEvent As UndoRedoTextBoxEvent
          Dim undoRedoValue As Object = Nothing
  
          Select Case command
  
              Case UndoRedoCommand.Undo
                  Me.isUndoingB = True
                  undoRedoItem = Me.undoStack.Pop
                  Me.AddUndoRedoItem(UndoRedoCommand.Redo, UndoRedoTextBoxEvent.TextChanged, Me.lastText, undoRedoItem.Value)
  
              Case UndoRedoCommand.Redo
                  Me.isRedoingB = True
                  undoRedoItem = Me.redoStack.Pop
                  Me.AddUndoRedoItem(UndoRedoCommand.Undo, UndoRedoTextBoxEvent.TextChanged, undoRedoItem.Value, Me.lastText)
  
          End Select
  
          undoRedoEvent = undoRedoItem.Key
          undoRedoValue = undoRedoItem.Value
  
          Select Case undoRedoEvent
  
              Case UndoRedoTextBoxEvent.TextChanged
                  Me.controlB.Text = CStr(undoRedoValue)
  
          End Select
  
          Me.isUndoingB = False
          Me.isRedoingB = False
  
      End Sub
  
      Private Sub AddUndoRedoItem(ByVal command As UndoRedoCommand, ByVal [event] As UndoRedoTextBoxEvent,
                                  ByVal data As Object, ByVal lastData As Object)
  
          Console.WriteLine()
          Console.WriteLine("command     :" &amp; command.ToString)
          Console.WriteLine("last command:" &amp; lastCommand.ToString)
          Console.WriteLine("can undo    :" &amp; Me.CanUndo)
          Console.WriteLine("can redo    :" &amp; Me.CanRedo)
          Console.WriteLine("is undoing  :" &amp; Me.isUndoingB)
          Console.WriteLine("is redoing  :" &amp; Me.isRedoingB)
          Console.WriteLine("data        :" &amp; data.ToString)
          Console.WriteLine("last data   :" &amp; lastData.ToString)
  
          Dim undoRedoData As Object = Nothing
          Me.lastCommand = command
  
          Select Case command
  
              Case UndoRedoCommand.Undo
  
                  If (Me.isUndoingB) Then
                      Exit Select
                  End If
  
                  undoRedoData = lastData
                  Me.undoStack.Push(New KeyValuePair(Of UndoRedoTextBoxEvent, Object)([event], undoRedoData))
  
              Case UndoRedoCommand.Redo
  
                  If (Me.isRedoingB) Then
                      Exit Select
                  End If
  
                  undoRedoData = lastData
                  Me.redoStack.Push(New KeyValuePair(Of UndoRedoTextBoxEvent, Object)([event], undoRedoData))
  
          End Select
  
      End Sub
  
      Private Sub TextBox_TextChanged(ByVal sender As Object, ByVal e As EventArgs) _
      Handles controlB.TextChanged
  
          Dim currentText As String = Me.controlB.Text
  
          If Not String.Equals(Me.lastText, currentText, StringComparison.Ordinal) Then
  
              Select Case Me.lastCommand
  
                  Case UndoRedoCommand.Undo
  
                      Me.AddUndoRedoItem(UndoRedoCommand.Undo, UndoRedoTextBoxEvent.TextChanged, currentText, Me.lastText)
  
                  Case UndoRedoCommand.Redo
                      Me.AddUndoRedoItem(UndoRedoCommand.Redo, UndoRedoTextBoxEvent.TextChanged, Me.lastText, currentText)
  
              End Select
  
              Me.lastText = currentText
  
          End If
  
      End Sub
  
  End Class
  </code></pre>