Well here I am to talk about the second part of this question Implement Undo/Redo operations for Adding/Deleting ListView Items and this other Extend this Class to Undo/Redo in a Listview.
I'm trying to implement Undo/Redo operations for Adding/Deleting ListView Items.
I've advanced a little more with the coding of this LV UndoManager code but it is very hard for me always when I try to advance.
At the moment I can add single items and then I can Undo/Redo perfectly that ADDED items, no more.
The problems that I have are these:
· When I remove a single Item from the Listview I'm unable to perform a "undo" to add again that item removed into the LV.
· When I add a range if items I can't undo, When I call UndoLastAction
it throws a System.Reflection.TargetParameterCountException
exception
· When I remove a range of Items I'm unable to undo/redo the operation and launchs the same exception.
In resume, If I add a single Item I can undo/redo perfecly, if I remove a single Item I can't undo right, also I can't undo/redo a range of ListViewItems.
I need someone who could help me to fix those problems... or at least one of them, with patience.
The code is a little big so I think that maybe can take less time to understand and to find errors opening and testing this source project that I've uploaded.
Here is the full
Source:
http://elektrostudios.tk/UndoManager%20Test%20Application.zip
Just an image:
here is the UndoManager class:
Class ListView_UndoManager
Private action As ListView_Action = Nothing
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
''' <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.
End Sub
''' <summary>
''' Redo the last action.
''' </summary>
''' <remarks></remarks>
Sub RedoLastAction()
If Redostack.Count = 0 Then Exit Sub ' Nothing to Redo.
action = Redostack.Pop() ' Get the Action from Stack and remove it.
action.Operation.DynamicInvoke(action.data) ' Invoke the redo Action.
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 ListViewItem()
End Class
Here is the ListView user control that I'm using, I post this because is important the Events which I'm triggering: ItemAdded
, ItemRemoved
, RangeItemAdded
and RangeItemRemoved
.
Public Class LV : Inherits ListView
Public Shared Event ItemAdded As EventHandler(Of ItemAddedEventArgs)
Public Class ItemAddedEventArgs : Inherits EventArgs
Public Property Item As ListViewItem
End Class
Public Shared Event ItemRemoved As EventHandler(Of ItemRemovedEventArgs)
Public Class ItemRemovedEventArgs : Inherits EventArgs
Public Property Item As ListViewItem
End Class
Public Shared Event RangeItemAdded As EventHandler(Of RangeItemAddedEventArgs)
Public Class RangeItemAddedEventArgs : Inherits EventArgs
Public Property Items As ListViewItem()
End Class
Public Shared Event RangeItemRemoved As EventHandler(Of RangeItemRemovedEventArgs)
Public Class RangeItemRemovedEventArgs : Inherits EventArgs
Public Property Items As ListViewItem()
End Class
Public Sub New()
Me.Name = "ListView_Elektro"
Me.GridLines = True
Me.FullRowSelect = True
Me.MultiSelect = True
Me.View = View.Details
End Sub
''' <summary>
''' Adds an Item to the ListView,
''' to monitor when an Item is added to the ListView.
''' </summary>
Public Function AddItem(ByVal Item As ListViewItem) As ListViewItem
RaiseEvent ItemAdded(Me, New ItemAddedEventArgs With { _
.Item = Item
})
Return MyBase.Items.Add(Item)
End Function
''' <summary>
''' Adds a range of Items to the ListView,
''' to monitor when an Item is added to the ListView.
''' </summary>
Public Sub AddItem_Range(ByVal Items As ListViewItem())
RaiseEvent RangeItemAdded(Me, New RangeItemAddedEventArgs With { _
.Items = Items
})
MyBase.Items.AddRange(Items)
End Sub
''' <summary>
''' Removes an Item from the ListView
''' to monitor when an Item is removed from the ListView.
''' </summary>
Public Sub RemoveItem(ByVal Item As ListViewItem)
RaiseEvent ItemRemoved(Me, New ItemRemovedEventArgs With { _
.Item = Item
})
MyBase.Items.Remove(Item)
End Sub
''' <summary>
''' Removes a range of Items from the ListView
''' to monitor when an Item is removed from the ListView.
''' </summary>
Public Sub RemoveItem_Range(ByVal Items As ListViewItem())
RaiseEvent RangeItemRemoved(Me, New RangeItemRemovedEventArgs With { _
.Items = Items
})
For Each Item As ListViewItem In Items
MyBase.Items.Remove(Item)
Next
End Sub
End Class
And finally here is the Form1 code of the Test application, here are the stuff which I use to add/remove items and to call undo/redo but I call to methods of my custom ListView user control so you need to notice that...:
Public Class Form1
Dim _undoManager As New ListView_UndoManager
Dim LVItem As ListViewItem
Delegate Sub AddDelegate(item As ListViewItem)
Delegate Sub RemoveDelegate(item As ListViewItem)
Delegate Sub AddRangeDelegate(item As ListViewItem())
Delegate Sub RemoveRangeDelegate(item As ListViewItem())
' Adds a single item
Private Sub Button_AddItem_Click(sender As Object, e As EventArgs) _
Handles Button_AddItem.Click
Dim index As String = CStr(LV1.Items.Count + 1)
LVItem = New ListViewItem With {.Text = index}
LVItem.SubItems.AddRange({"Hello " & index, "World " & index})
LV1.AddItem(LVItem)
End Sub
' Adds a range of 2 items to the ListView
Private Sub Button_AddRange_Of_Items_Click(sender As Object, e As EventArgs) Handles Button_AddRange_Of_Items.Click
Dim index As String = CStr(LV1.Items.Count + 1)
Dim lvitem As New ListViewItem With {.Text = index}
lvitem.SubItems.AddRange({"Hello " & index, "World " & index})
Dim lvitem2 As New ListViewItem With {.Text = index + 1}
lvitem2.SubItems.AddRange({"Hello " & index + 1, "World " & index + 1})
LV1.AddItem_Range({lvitem, lvitem2})
End Sub
' Removes the last item
Private Sub Button_RemoveLastItem_Click(sender As Object, e As EventArgs) _
Handles Button_RemoveLastItem.Click
If LV1.Items.Count <> 0 Then
LV1.RemoveItem(LV1.Items.Cast(Of ListViewItem).Last)
End If
End Sub
' Clear all items
Private Sub Button_Clear_Items_Click(sender As Object, e As EventArgs) _
Handles Button_Clear_Items.Click
LV1.Items.Clear()
End Sub
' Clear the Undo/Redo Stacks
Private Sub Button_Clear_Stacks_Click(sender As Object, e As EventArgs) _
Handles Button_Clear_Stacks.Click
_undoManager.Undostack = New Stack(Of ListView_Action)
_undoManager.Redostack = New Stack(Of ListView_Action)
Label_UndoCount_Value.Text = CStr(0)
Label_RedoCount_Value.Text = CStr(0)
End Sub
' Refreshes the Stacks Count
Private Sub Refresh_StackCount()
Label_UndoCount_Value.Text = CStr(_undoManager.Undostack.Count)
Label_RedoCount_Value.Text = CStr(_undoManager.Redostack.Count)
End Sub
' Monitors when an Item is added
Private Sub ListView_ItemAdded(sender As Object, e As LV.ItemAddedEventArgs) _
Handles LV1.ItemAdded
' // Crate an Undo Action
Dim u As New ListView_Action()
With u
.name = "Remove Item"
.Operation = New RemoveDelegate(AddressOf LV1.RemoveItem)
.data = {e.Item}
End With
_undoManager.Undostack.Push(u)
Refresh_StackCount()
End Sub
' Monitors when a range of Items are added
Private Sub ListView_RangeItemAdded(sender As Object, e As LV.RangeItemAddedEventArgs) _
Handles LV1.RangeItemAdded
' // Crate an Undo Action
Dim u As New ListView_Action()
With u
.name = "Remove Item Range"
.Operation = New RemoveRangeDelegate(AddressOf LV1.RemoveItem_Range)
.data = e.Items
End With
_undoManager.Undostack.Push(u)
Refresh_StackCount()
End Sub
' Monitors when an Item is removed
Private Sub ListView_ItemRemoved(sender As Object, e As LV.ItemRemovedEventArgs) _
Handles LV1.ItemRemoved
' // Create a Redo Action
Dim r As New ListView_Action()
With r
.name = "Add Item"
.Operation = New AddDelegate(AddressOf LV1.AddItem)
.data = {e.Item}
End With
_undoManager.Redostack.Push(r)
Refresh_StackCount()
End Sub
' Monitors when a range of Items are removed
Private Sub ListView_RangeItemRemoved(sender As Object, e As LV.RangeItemRemovedEventArgs) _
Handles LV1.RangeItemRemoved
' // Create a Redo Action
Dim r As New ListView_Action()
With r
.name = "Add Item"
.Operation = New AddRangeDelegate(AddressOf LV1.AddItem_Range)
.data = e.Items
End With
_undoManager.Redostack.Push(r)
Refresh_StackCount()
End Sub
' Undo
Private Sub Button_Undo_Click(sender As Object, e As EventArgs) _
Handles Button_Undo.Click
_undoManager.UndoLastAction()
End Sub
' Redo
Private Sub Button_Redo_Click(sender As Object, e As EventArgs) _
Handles Button_Redo.Click
_undoManager.RedoLastAction()
End Sub
Private Sub Button_Remove_Range_Of_Items_Click(sender As Object, e As EventArgs) Handles Button_Remove_Range_Of_Items.Click
If LV1.Items.Count > 1 Then
Dim lvi1 As ListViewItem = LV1.Items(LV1.Items.Count - 1)
Dim lvi2 As ListViewItem = LV1.Items(LV1.Items.Count - 2)
LV1.RemoveItem_Range({lvi1, lvi2})
End If
End Sub
End Class
PS: Like I've said, really would be very more friendly to downlaod the source and test it.