Pergunta

I have too much problems trying to implement an Undo/Redo operation in a ListView Control, just to add/remove items.

I realized time ago a relative question here Extend this Class to Undo/Redo in a Listview where I was started multiple bountyes of 50, 100, 200 and 300 points, a total of 650 points... but no body could really helped me to finalize this issue in weeks and months.

But after time in that question finally a user ( @ThorstenC ) showed me a possible solution and a great idea, the code of him is incomplete so the code of him is what I'm trying to realize/finish.

The problem is Simple "undo" works fine, but when I try to redo more than 1 time it throws an exception about it can't add the same item again in the listview, also the code has more problems for example at the moment I'm not able to redo a undo operation, or undo a redo operation.

Just I need help to make a working Undo/Redo manager for Listview Item adding/removing, that's all, I have written the half part of the code, I need help to finish it I have a mess in my head with this.

Here is a simple WinForms source project in VS2012 that I've uploaded to test the undo manager fails:

http://elektrostudios.tk/UndoManager.zip

enter image description here

Here is a video to show you the errors that I get trying to undo/redo: http://www.youtube.com/watch?v=MAzChURATpM

Here is the UndoManager Class of @ThorstenC with a little retouches:

Class ListView_UndoManager

    Public Property Undostack As New Stack(Of ListView_Action)
    Public Property Redostack As New Stack(Of ListView_Action)

    Public Property IsDoingUndo As Boolean ' = False
    Public Property IsDoingRedo As Boolean ' = False

    Private action As ListView_Action = Nothing

    ''' <summary>
    ''' Undo the last action.
    ''' </summary>
    ''' <remarks></remarks>
    Sub UndoLastAction()

        If Undostack.Count = 0 Then Exit Sub ' Nothing to Undo.

        action = Undostack.Pop ' Get the Action from Stack and remove it.
        action.Operation.DynamicInvoke(action.data) ' Invoke the undo Action.

        'Redostack = New Stack(Of ListView_Action)(Redostack)
        'Redostack.Pop()
        'Redostack = New Stack(Of ListView_Action)(Redostack)

    End Sub

    ''' <summary>
    ''' Redo the last action.
    ''' </summary>
    ''' <remarks></remarks>
    Sub RedoLastAction()

        ' If Redostack.Count = Undostack.Count Then Exit Sub

        If Redostack.Count = 0 Then Exit Sub ' Nothing to Redo.

        'Redostack = New Stack(Of ListView_Action)(Redostack) ' Reverse the Stack contents.

        action = Redostack.Pop() ' Get the Action from Stack and remove it.
        ' action = Redostack.Peek()

         action.Operation.DynamicInvoke(action.data) ' Invoke the redo Action.

        'Redostack = New Stack(Of ListView_Action)(Redostack) ' Re-Reverse the Stack contents.

    End Sub

End Class

Class ListView_Action

    ''' <summary>
    ''' Name the Undo / Redo Action
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Property name As String

    ''' <summary>
    ''' Points to a method to excecute
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Property Operation As [Delegate]

    ''' <summary>
    ''' Data Array for the method to excecute
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Property data As Object()

End Class

And here is the rest of the code where I'm trying to Undo/Redo Adding/Deleting listview items:

Public Class Form1


    Dim _undoManager As New ListView_UndoManager
    Delegate Sub RemoveDelegate(item As ListViewItem)
    Delegate Sub AddDelegate(item As ListViewItem)

    Dim newItem As ListViewItem = Nothing



    Sub AddItem(ByVal item As ListViewItem)

        ' // Crate an Undo Action
        Dim u As New ListView_Action() With {.name = "Remove Item",
                            .Operation = New RemoveDelegate(AddressOf RemoveItem),
                                    .data = New Object() {newItem}}

        _undoManager.Undostack.Push(u)

        ListView_Elektro1.AddItem(item)

    End Sub

    Sub RemoveItem(item As ListViewItem)

        ' // Create a Redo Action
        Dim r As New ListView_Action() With {.name = "Add Item",
                    .Operation = New AddDelegate(AddressOf AddItem),
                            .data = New Object() {item}}

        _undoManager.Redostack.Push(r)

        ' Remove the ListViewItem from ListView
        ListView_Elektro1.RemoveItem(item)

    End Sub

    Private Sub Button_AddItem_Click(sender As Object, e As EventArgs) _
    Handles Button_AddItem.Click

        Dim index As String = CStr(ListView_Elektro1.Items.Count + 1)

        newItem = New ListViewItem _
                  With {.Text = index}
        newItem.SubItems.AddRange({"Hello " & index, "World " & index})

        AddItem(newItem)

    End Sub

    Private Sub Button_RemoveItem_Click(sender As Object, e As EventArgs) _
    Handles Button_RemoveItem.Click

        newItem = ListView_Elektro1.Items.Cast(Of ListViewItem).Last

        RemoveItem(newItem)

    End Sub

    Private Sub Button_Undo_Click(sender As Object, e As EventArgs) _
    Handles Button_Undo.Click

        ' _undoManager.IsDoingUndo = True
        _undoManager.UndoLastAction()
        ' _undoManager.IsDoingUndo = False

    End Sub

    Private Sub Button_Redo_Click(sender As Object, e As EventArgs) _
    Handles Button_Redo.Click

        '_undoManager.IsDoingRedo = True
        _undoManager.RedoLastAction()
        '_undoManager.IsDoingRedo = False

    End Sub

    Private Sub ListView_Elektro1_ItemAdded() _
    Handles ListView_Elektro1.ItemAdded, _
            ListView_Elektro1.ItemRemoved

        Label_UndoCount_Value.Text = CStr(_undoManager.Undostack.Count)
        Label_RedoCount_Value.Text = CStr(_undoManager.Redostack.Count)

    End Sub

End Class
Foi útil?

Solução

"El URL requerido no fue encontrado en este servidor." So I am only pretty sure this is it:

action = Redostack.Peek() ' Get the Action from Stack and remove it.

No, you are looking at it without getting it from the stack. Both the original and a quick rework of it I did use:

action = Redostack.Pop() 

Since you are storing actual LV Items in the stack to post back to the LV, the second time you press it your are looking at and trying to restore one already in the LV.

Since the most of the original "Commands" saved the Undo/ReDo data as object why didnt you just expose an AddLVUndoItem(item) on UnDoReDoManager to use the existing code to integrate LV actions with the other controls? The problem it had was that there is no LVItemAdded event to automatically grab those things. One problem using this as a user-controlled feature along with the other one is that you now have 2 stacks one skips over LV, the other only does LV. The user could empty the other stack trying to get to LV undo actions.

Also, adding an item is falling into the UnDo bucket, but not RemoveItem and vice-versa for RemoveItem (cant Undo RemoveItem). In the original Undo automatically added the command to the ReDo stack. It is in the title and the old request but not the code.

Edit This is wrong:

Sub RemoveItem(item As ListViewItem)
    ' // Create a Redo Action
    Dim r As New ListView_Action() With {.name = "Add Item",
                .Operation = New AddDelegate(AddressOf AddItem),
                        .data = New Object() {item}}   ' wrong!

    _undoManager.Redostack.Push(r)

    ' Remove the ListViewItem from ListView
    ListView_Elektro1.RemoveItem(item)
End Sub

You dont create a new LVI for the undoStack, use the one passed which is the one removed (recall I had to change the syntax for my VS ver):

Sub RemoveItem(ByVal item As ListViewItem)

    ' // Create a Redo Action
    Dim r As New ListView_Action()
    With r
        .name = "Add Item"
        .Operation = New AddDelegate(AddressOf AddItem)
        .data = item           ' use the one passed!!!
    End With

    _undoManager.Redostack.Push(r)

    ' Remove the ListViewItem from ListView
    LVE.RemoveItem(item)
    _undoManager.ShowStacks()

End Sub

As a result, your ReDo wasnt caching any of the UnDo actions. It only looked like it was due to the artificial test data.

Outras dicas

You might also want to check out this Undo/Redo Framework written in VB.NET

http://www.codeproject.com/Articles/43436/Undo-Redo-Framework

Its designed for the following type of controls (but should also work with custom controls for the most part)

  • TextBox
  • ComboBox
  • DateTimePicker
  • NumericUpDown
  • MaskedTextBox
  • ListBox (single and multi-select)
  • CheckBox
  • RadioButton
  • MonthCalendar
  • ListView (Label text changes)
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top