8

我正在使用第三方代码来管理 WindowsForm 项目中的撤消/重做操作。

我需要扩展类来管理 Listview 中的撤消/重做操作,这意味着:

·撤消/重做添加/删除项目和子项目

·撤消/重做检查/取消选中行

·撤消/重做一些我可能错过的其他重要事情

我不知道如何开始这样做,代码对我来说太复杂了,任何有关此的帮助/提示/示例对我来说都会非常满意,但是在 3 个月内我无法进行此更改,我想我需要很好的解释或完整的例子,这里是代码:

********************************************************
 Undo/Redo framework (c) Copyright 2009 Etienne Nijboer
********************************************************

http://pastebin.com/Gmh5HS4x

(我没有在这里发布代码,因为它超过了 StackOverflow 的 30.000 个字符的限制)

更新:

这是作者提供的一些有用信息,解释了添加 Listview 支持我需要做的事情,但我自己真的做不到:

顺便说一下,为列表视图添加功能应该不是那么难,而且也是了解它如何工作的好方法。您需要创建一个新的监视器来捕获列表视图更改事件并在更改之前存储当前值。如果您检测到使用撤消或重做操作所需的所有信息进行了更改,则会创建一个命令。而已。只要您的监视器和命令从基类继承,它就会被自动检测和使用。

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

更新:

该类的所有者更新了代码,添加了我需要的一项内容,即我要求的标签项撤消/重做操作。

· 撤消/重做 Listview 内的文本更改(普通模式或详细信息模式)

不幸的是,这个更新不足以让我添加我需要的其他撤消/重做操作,请阅读@Plutonix 评论解释事情

这是更新后的 Class 的一部分,供可以接受想法并帮助扩展它的人使用:

'****************************************************************************************************************
' ListView Undo/Redo Example, (c) Copyright 2013 Etienne Nijboer
'****************************************************************************************************************
' This is an example implementation of the Monitor and Command to add support for listviewitem labeltext changes
' Only the two classes arre needed to add support for an additional control. There were no extra changes needed
' in other code because the UndoRedoManager class uses reflection to discover the new Monitor and if you check 
' the message box on startup you'll notice the new addition of the ListViewMonitor to the list.
'
' Hopefully this example makes it easier for others to understand the mechanism behind this and how to add 
' undo/redo functionality for other actions and controls.
'
' Note: Beware that this example doesn't work if items in the listview can be sorted, moved and/or deleted. You
'       would need to expand the Monitor for these actions and add Command classes as well. Hopefully this 
'       addition to will make it easier for you to do just that ;-)
'
'   Good luck!
'
'****************************************************************************************************************

' Because we want to perform undo on a specific item at a certain index within the listview it is important this
' index is also stored. Otherwise we know that a label is changed but not to which item it belongs
Structure ListViewUndoRedoData
    Public ItemIndex As Integer
    Public LabelText As String
End Structure

'****************************************************************************************************************
' ListViewMonitor
'****************************************************************************************************************
Public Class ListViewMonitor : Inherits BaseUndoRedoMonitor

    Private Data As ListViewUndoRedoData

    Public Sub New(ByVal AUndoRedoManager As UndoRedoManager)
        MyBase.New(AUndoRedoManager)
    End Sub

    Public Overrides Function Monitor(ByVal AControl As System.Windows.Forms.Control) As Boolean
        If TypeOf AControl Is ListView Then
            AddHandler CType(AControl, ListView).BeforeLabelEdit, AddressOf ListView_BeforeLabelEdit
            AddHandler CType(AControl, ListView).AfterLabelEdit, AddressOf ListView_AfterLabelEdit
            Return True
        End If
        Return False
    End Function


    Private Sub ListView_BeforeLabelEdit(sender As System.Object, e As System.Windows.Forms.LabelEditEventArgs)
        ' Before change, make sure to save the data of what it is you want to be able to undo later.  
        Data.ItemIndex = e.Item
        Data.LabelText = CType(sender, ListView).Items(e.Item).Text
    End Sub


    Private Sub ListView_AfterLabelEdit(sender As System.Object, e As System.Windows.Forms.LabelEditEventArgs)
        ' Events that are also fired when the undo/redo value is changed by code, like change events,
        ' it is important to make sure that no undo/redo command is added when performing a undo/redo action.         
        If Not isPerformingUndoRedo Then            
            If Not (Data.ItemIndex = e.Item And String.Equals(Data.LabelText, e.Label)) Then
                AddCommand(UndoRedoCommandType.ctUndo, New ListViewUndoRedoCommand(Me, sender, Data))
                ListView_BeforeLabelEdit(sender, e)
            End If
        End If
    End Sub

End Class



'****************************************************************************************************************
' ListViewUndoRedoCommand
'****************************************************************************************************************
Public Class ListViewUndoRedoCommand : Inherits BaseUndoRedoCommand

    Public Sub New(ByVal AUndoMonitor As BaseUndoRedoMonitor, ByVal AMonitorControl As Control)
        MyBase.New(AUndoMonitor, AMonitorControl)
        Debug.Assert(False, "This constructor cannot be used because creating the current state of the control should be done at the actual undo or redo action!")
    End Sub

    Public Sub New(ByVal AUndoMonitor As BaseUndoRedoMonitor, ByVal AMonitorControl As Control, ByVal AUndoRedoData As Object)
        MyBase.New(AUndoMonitor, AMonitorControl, AUndoRedoData)
    End Sub

    Public ReadOnly Property Control() As ListView
        Get
            Return CType(UndoRedoControl, ListView)
        End Get
    End Property


    Private ReadOnly Property Data() As ListViewUndoRedoData
        Get
            Return CType(UndoRedoData, ListViewUndoRedoData)
        End Get
    End Property


    Private Function GetCurrentStateData() As ListViewUndoRedoData        
        GetCurrentStateData.ItemIndex = Data.ItemIndex
        GetCurrentStateData.LabelText = Control.Items(Data.ItemIndex).Text
    End Function


    Public Overrides Sub Undo()
        MyBase.Undo(GetCurrentStateData())
        Control.Items(Data.ItemIndex).Text = Data.LabelText
    End Sub

    Public Overrides Sub Redo()
        MyBase.Redo(GetCurrentStateData())
        Control.Items(Data.ItemIndex).Text = Data.LabelText
    End Sub

    Public Overrides Function CommandAsText() As String
        Return String.Format("Item {0}: {1}", Data.ItemIndex, Data.LabelText)
    End Function
End Class

更新 2:

这就是作者所说的如何添加列表视图撤消/重做操作所需的功能:

我认为您不需要重写整个课程。其中最困难的部分是找到一种方法来检测一个项目何时可能被删除以及它何时被实际删除。在 ListViewMonitor 中,您将需要添加必要的事件处理程序(在您找到 BeforeLabelEdit 和 AfterLabelEdit 的 AddHandler 的源代码中)。对于 Command 类,您需要拥有实际的 ListViewItem 以及项目在被删除之前在 ListView 中的位置。您可以使用此信息简单地创建结构,例如 ListViewItemRemoveUndoRedoData。当您撤消删除时,您只需将存储的 ListViewItem 添加到您存储的位置的 ListView 中。我建议向 ListViewItemRemoveUndoRedoData 结构添加一个额外的 Count,该结构包含列表视图中的项目数。此外,我认为您需要的唯一事件是 SelectedIndexChanged。当此事件发生时,有 2 种情况。

1- 项目数与之前存储的计数相同(在创建监视器时将其设置为 -1 或其他值):您存储项目、位置和总项目数。

2- 项目的数量少于你之前存储的数量:一个项目被移除并且你设置它的 UndoRedoCommand 以便它可以被撤消。

  • 当然还有第三个选项,这意味着添加了一个项目

它需要一些创造力来找到正确的事件以及需要存储什么来执行撤消/重做。这甚至可能意味着您需要找到具有更好事件和支持的替代列表视图(您可以在 codeproject 上找到)


更新 3:

尝试遵循@ThorstenC 解决方案时,我遇到了 RedoLastAction 的问题,即使首先我没有撤消任何操作,它也会重做。

我也可以无限次重做,它只重做最后一个动作,我的意思是如果我撤消 3 个不同的 LV 项目,那么我只能重做最后添加的项目。

· UndoManager 类:

Class ListView_UndoManager

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

    Private action As ListView_Action = Nothing

    ''' <summary>
    ''' Undo the top of the stack
    ''' </summary>
    ''' <remarks></remarks>
    Sub UndoLastAction()

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

        action = Undostack.Pop ' Get the Action from Stack.
        action.Operation.DynamicInvoke(action.data) ' Invoke the reverse Action .

    End Sub

    ''' <summary>
    ''' Redo the top of the stack
    ''' </summary>
    ''' <remarks></remarks>
    Sub RedoLastAction()

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

        action = Redostack.Peek  ' Get the Action from Stack, but don't remove it.
        action.Operation.DynamicInvoke(action.data) ' Invoke the reverse 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 Object()

End Class

· 主窗体代码:

' Undo/Redo
Dim _undoManager As New ListView_UndoManager
Delegate Sub RemoveDelegate(item As Object)
Delegate Sub AddDelegate(text As String, subtext1 As String, subtext2 As String)

' Button Add Song [Click]
Private Sub Button_Add_Song_Click(sender As Object, e As EventArgs) _
Handles Button_Add_Song.Click

    AddItem(ListView_Monitor.Items.Count + 1, WinampFile, ComboBox_Sendto.Text)

End Sub

Sub AddItem(ByVal name As String, ByVal subitem1 As String, ByVal subitem2 As String)

    Dim newItem = ListView_Monitor.Items.Add(name)
    newItem.SubItems.Add(subitem1)
    newItem.SubItems.Add(subitem2)

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

    _undoManager.Undostack.Push(u)

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

    _undoManager.Redostack.Push(r)

End Sub

Sub RemoveItem(item As Object)
    ListView_Monitor.Items.Remove(item)
End Sub
4

3 回答 3

5

如果仔细查看第 328 行,它已经处理了一个 ListView。它在某些方面缺乏吗?

于 2013-10-13T13:02:05.643 回答
1

试试这个方法:忘记这个当前的实现,开始实现你自己的 Undo/Redo 类。

每个操作某些东西的方法都需要创建自己的 Undo 方法。存储委托并在需要时调用它。我做了一个简单的添加/删除列表视图项目的例子。

Public Class Form1

Dim _undoManager As New UndoManager

''' <summary>
''' Delegates to Remove an item
''' </summary>
''' <param name="rowNumber"></param>
''' <remarks></remarks>
Delegate Sub RemoveDelegate(item As Object)

''' <summary>
''' Delegates to Add an Item
''' </summary>
''' <param name="text"></param>
''' <remarks></remarks>
Delegate Sub AddDelegate(text As String)


Sub AddItem(name As String)


    Dim newItem = ListView1.Items.Add(name)

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

    _undoManager.Undostack.Push(a)

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

    _undoManager.Redostack.Push(a)



End Sub

Sub RemoveItem(item As Object)
    ListView1.Items.Remove(item)
End Sub

''' <summary>
''' Changes the Text of the Item
''' </summary>
''' <param name="item"></param>
''' <param name="text"></param>
''' <remarks></remarks>
Sub changetext(item As Object, text As String)
    Dim oldtext As String = item.text

End Sub


Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
    Me.AddItem("new Item")
End Sub

Private Sub Button2_Click(sender As System.Object, e As System.EventArgs) Handles Button2.Click
    _undoManager.UndoLastAction()
End Sub

End Class

Class UndoManager


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

''' <summary>
''' Undos the top of the stack
''' </summary>
''' <remarks></remarks>
Sub UndoLastAction()
    Dim action As action = Undostack.Pop ' Get the Action from Stack
    action.Operation.DynamicInvoke(action.data) ' Invoke the reverse Action 

End Sub

Sub RedoLastAction()
    Dim action As action = Redostack.Peek' Get the Action from Stack, but dont remove
    action.Operation.DynamicInvoke(action.data) ' Invoke the reverse Action 

End Sub


End Class

Class 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
于 2013-10-30T10:16:00.020 回答
1

我懂了。这是您定义“重做”应该做什么的方式。在您的情况下,您想要重做撤消操作。默认情况下,重做会重复上一个操作。即使您撤消某些操作,重做也会再次撤消。尝试这种方法:仅将代码片段理解为构建块。“RemoveItem”方法不会将代码添加到 Undo/Redo 堆栈 - 像在 Add - 方法中一样添加此 Undo Redo。如果您不需要“撤消撤消” - 操作,请添加

Property IsDoingUndo as boolean

到 UndoManager 并在执行 Undo 时将其设置为 true。在添加/删除方法中检查此属性,不要向撤消/重做堆栈添加任何内容。喜欢 :

If not _UndoManager.IsDoingUndo then 
...
else
...
endif

有了这个,您将控制哪些应该是可撤销和可重做的。很抱歉这次我不能提供源代码。

于 2013-11-03T10:31:40.553 回答