实现添加/删除ListView项的撤消/重做操作(第2部分)

时间:2013-11-04 19:59:29

标签: .net vb.net winforms listview undo-redo

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

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

我已经使用这个LV UndoManager代码编写了一些进展,但是当我尝试前进时,我总是很难。

目前我可以添加单个项目,然后我可以完全撤消/重做添加的项目,不再需要。

我遇到的问题是:

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

·当我添加范围时,如果我无法撤消的项目,当我致电UndoLastAction时,会抛出System.Reflection.TargetParameterCountException例外

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

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

我需要一个可以帮助我解决这些问题的人......或至少其中一个,耐心等待。

代码有点大,所以我认为可能花费更少的时间来理解并找到错误,打开并测试我上传的源项目。

以下是full来源:

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

只是一张图片:

enter image description here

这是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用户控件,我发布这个因为重要的是我触发的事件:ItemAddedItemRemovedRangeItemAdded和{{1 }}

RangeItemRemoved

最后这里是Test应用程序的Form1代码,这里是我用来添加/删除项目和调用undo / redo但我调用自定义ListView用户控件的方法的东西,所以你需要注意到...

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

1 个答案:

答案 0 :(得分: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)

在ListView项目计数上使用CStr会创建UnDo / Redo堆栈上已存在的名称,并使调试更加困难。

我认为GUI级别,像RemoveItem这样的用户调用动作会落入UnDo堆栈。您将AddItem与UnDO和RemoveItem等同于Redo,这是错误的。 GUI表单级别的所有内容都应该属于撤消堆栈,并且应该通过UM.Undo方法进入ReDo的 方式。

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

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

  • GUI添加项目----&gt;推送删除操作
  • GUI删除----&gt;推送添加操作
  • UM Pop Add ------&gt;新增项目;按删除到ReDo
  • UM Pop删除------&gt;去掉;将Add添加到Redo

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

最后,是否真的有必要在文本墙中发布所有prop XML注释标题?撤消的所有Draw覆盖是否与撤消密切相关?否。

编辑

这里大致是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个合法的用户操作(Checked和Label Edit),因此要做到这一点,就需要进行扩展。

我和另一个依赖于多态,其中undoStack(2)可能是一个checkedlistbox撤消动作,undoStack(9)可能是一个combox动作 - 观察者(监视器)知道要创建哪种类型以及如何撤消/重新执行行动。文本撤消(TextBox,Combo,MaskedEdit和DateTimePicker)只是:

Friend Overrides Function Undo() As Boolean

    _Ctl.Text = _UndoData
    Return True

End Function

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