Implement Undo/Redo operations for Adding/Deleting ListView Items
Asked Answered
M

2

1

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
Morie answered 3/11, 2013 at 19:34 Comment(2)
please shorten your code to represent the relevant pieces. remove unnecessary comments and please point out in what line your exception is thrown!Occult
@Pilgerstorfer Franz thanks for comment,sorry but the code and my words cant be less shorten because I've needed to give all the necessary information, the exception is thrown at this line:_undoManager.RedoLastAction() you could see it in the video that I've uploaded, but that exception is not all the problem,I'll quote this from my question please read:The problem is when I try to redo more than 1 time it throws an exception about it cant add the same item again in the listview, also the code has more problems for example ATM I'm not able to redo a undo operation, or undo a redo operationMorie
D
2

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

Durno answered 3/11, 2013 at 22:8 Comment(9)
Thankyou for answer, "El URL requerido no fue encontrado en este servidor." I've fixed the url, now you could download the solution to see it if you want try to fix the code. About the peek/pop I've left written "peek" by a mistake but I've tried "all" (peek/pop/push/reversing/clear) before posting the question. Just for annotation my LV has a LVItemAdded and LVItemRemoved Events.Morie
About the last paragraph of your answer I know that the code has those problems, I mean when I do a "undo" I don't add the redo and when I do a "redo" I don't add a undo, but the redo code is wrong, but I cannot continue trying to building a redo when I undo and undo when I redo because I must first correct the redo code to correct the other two problems, redo at the moment is wrong, I can't fix them without helpMorie
I know is not right but I'm trying to say that I've also tried with "pop" and got no fix. PS: Sorry for my English.Morie
If not too much trouble to ask you Do you think you could check the project to help to fix the code with examples (with fixes)? you know my old request I'm really needed and desperate, anyways really thanks for the info! but I need more than info for this damn UndoManager :(Morie
Sorry dude, I changed 'Peek' To 'Pop' and it worked fine. is that the actual test bed you are using? I had to remark out loads of your freaky code to get it to draw and the buttons to be visible, but after that it worked fine (well, it is all manual which is probably why the stacks are Public and UndoManager is not doing any of the work??). It also lumps SubItems with Items rather than separate Undo candidates? I also linked it to a regular LV and it worked fine (after changing AddItem to items.Add etc)Anglesite
Only with the "Redostack.Pop()" method fixed you could try something to reproduce an error, just press 5 times "add a new item" (or add some items manually), press 2 times "undo" and press 2 times "Redo", now press again 2 times "undo" and try to press "redo" 2 times. Notice that the last undo does not undos so the redo attempt will throw an exception about trying to insert the same LV Item in the Listview.Morie
I cant just change Peek to Pop, your code eats the controls on the form! Your ReDo wasnt caching any of the UnDo actions. It only looked like it was due to the artificial test data. See editAnglesite
Just... AMAZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIING, with your fix now it seems that all the problems about undo/redo are solved in the application, but anyways I need to deep test it before mark an answer as accepted, Thanks for your help.Morie
new question is here!: #19776514Morie
H
1

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)
Hydrosome answered 17/5, 2014 at 10:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.