1

好吧,我在这里谈谈这个问题的第二部分,Implement Undo/Redo operations for Add/Deleting ListView Items和另一个Extend this Class to Undo/Redo in a Listview

我正在尝试为添加/删除 ListView 项实现撤消/重做操作。

我在这个 LV UndoManager 代码的编码方面已经进步了一点,但是当我尝试进步时,这对我来说总是很困难。

目前我可以添加单个项目,然后我可以完美地撤消/重做添加的项目,仅此而已。

我遇到的问题是:

·当我从列表视图中删除单个项目时,我无法执行“撤消”以再次将删除的项目添加到 LV 中。

·当我添加一个范围,如果我无法撤消的项目,当我调用UndoLastAction它时会抛出System.Reflection.TargetParameterCountException异常

·当我删除一系列项目时,我无法撤消/重做操作并启动相同的异常。

在简历中,如果我添加一个项目,我可以完美地撤消/重做,如果我删除一个项目,我无法正确撤消,我也无法撤消/重做一系列 ListViewItem。

我需要有人能耐心地帮助我解决这些问题……或者至少其中一个问题。

代码有点大,所以我认为可能需要更少的时间来理解和发现打开和测试我上传的这个源项目的错误。

这是full来源:

http://elektrostudios.tk/UndoManager%20Test%20Application.zip

只是一张图片:

在此处输入图像描述

这是 UndoManager 类:

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

这是我正在使用的 ListView 用户控件,我发布这个是因为我正在触发的事件很重要:ItemAddedItemRemoved和。RangeItemAddedRangeItemRemoved

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

最后是测试应用程序的 Form1 代码,这里是我用来添加/删除项目和调用撤消/重做的东西,但我调用了我的自定义 ListView 用户控件的方法,所以你需要注意... :

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:就像我说的那样,下载源并测试它真的会更友好。

4

1 回答 1

2

When I remove a single Item from the Listview- 简单的一个。

RemoveItem 从列表中删除一个项目并将其添加到ReDo堆栈,但它仍然驻留在 UnDo 堆栈中!!!如果您添加 5,删除 1,然后撤消,您将在重做中获得 2 个项目 5 的副本!

首先,您应该将 AddItem 机制更改为直接计数器,以使调试更容易

    nLVItemIndex += 1
    Dim index As String = (nLVItemIndex).ToString

    newItem = New ListViewItem
    newItem.Text = "Item " & index
    newItem.SubItems.Add("Hello " & index)
    newItem.SubItems.Add("World " & index)

    AddItem(newItem)

使用CStrListView 项目计数会创建可能已经存在于 UnDo/Redo 堆栈中的名称,并使调试更加困难。

我应该认为 GUI 级别、用户调用的操作(如 RemoveItem)会落入 UnDo 堆栈。您将 AddItem 等同于 UnDO 并将 RemoveItem 等同于 Redo,这是错误的。GUI 表单级别的所有内容都应该落入 Undo 堆栈,并且它应该进入 ReDo 的唯一方法是通过 UM.Undo 方法。

将它移到 UnDo 堆栈将揭示另一个问题:您的 UnDo Manager 自己做的很少,并且使用表单级别的 AddItem/RemoveItem 而不是它自己的内部过程(他甚至不能创建自己的 UnDo/Redo Actions。)结果是所有 Additem 动作 将 Remove Action 推入 UnDo 堆栈;并且所有 RemoveItems 都会推送一个无效的 ReDo 操作,因为您确实想要撤消删除!

最终结果是UM.UndoLastAction从 UnDo 弹出(好)然后DynamicInvoke触发 Form.AddItem ,它发出 UnDo Push (非常糟糕,因为刚刚弹出 - 事实上这就是我们仍在做的事情 - 这就是为什么原来有 IsRedoing 标志)。UnDo Manager 需要进行脑部大手术来完成自己的工作,因为 GUI 级别的 Add/Remove 操作与 UnDo/ReDo 不同。

  • GUI 添加项目 ----> 推送删除操作
  • GUI 删除 ----> 推送添加操作
  • UM Pop Add ------> 添加项目;将删除推到重做
  • UM Pop 删除 ------> 删除;将添加推到重做

然后这表明 UnDoManager 没有引用他正在“管理”的控件,更不用说监视多个 LV 的能力了。我认为 AddRange 方法只会加剧上述问题(无法在代码墙中找到要领)。

最后,真的有必要把所有的prop XML评论头都贴在文字墙里吗?所有的 Draw 覆盖都与 Undo 密切相关吗?不。

编辑

以下是大致UnDoManager.UnDo需要做的事情(根据我对你开始的那个夸大其词的返工):

Friend Function UnDo() As Boolean
    If _undoStack.Count = 0 Then Exit Function

    Dim ur As UndoAction         ' ie Command

    _IgnoreChange = True          ' ie IsUnDoing so you dont Push while Popping
    ur = _undoStack.Pop           ' get the Undo, such as LV.RemoveItem
    ur.Undo()                     ' Undo whatever it is (could be a checkbox etc)
    _IgnoreChange = False         ' open for business
    _redoStack.Push(ur)           ' push the same Action onto the ReDo
                                  ' I dont bother changing a code (yet) because
                                  ' if it is in Undostack it is an UnDo
   return True
End Function

UnDoAction的只是正在撤消的控制和Data As Object. 由于 MOST Controls 只有一件事让用户搞砸,所以没问题。LV 有 2 个合法的用户操作(检查和标签编辑),因此要能够执行任何一个操作,都需要对其进行扩展。

我的和另一个依赖于多态性,其中 undoStack(2) 可能是选中的列表框撤消操作,而 undoStack(9) 可能是组合框操作 - 观察者(监视器)知道要创建哪种类型以及如何撤消/重做操作。文本撤消(TextBox、Combo、MaskedEdit 和 DateTimePicker)只是:

Friend Overrides Function Undo() As Boolean

    _Ctl.Text = _UndoData
    Return True

End Function

我想知道的是你现在只是在做 LastItem - RemoveSelectedItem 呢?你怎么把它放回去?如果您保留任何类型的订单参考,它可能是无效的,因为该参考可能不再存在。

于 2013-11-04T21:31:01.110 回答