我正在使用第三方代码来管理 Windows 窗体项目中的撤消/重做操作。
我需要扩展该类来管理列表视图中的撤消/重做操作,这意味着:
· 撤消/重做添加/删除项目和子项目
· 撤消/重做检查/取消检查行
· 撤消/重做一些我可能错过的其他重要事情
我不知道如何开始这样做,代码对我来说太复杂了,任何关于这方面的帮助/提示/示例对我来说都会非常满意,但是在 3 个月内我一直无法进行这个更改,我我想我需要很好的解释或完整的例子,这里是代码:
********************************************************
Undo/Redo framework (c) Copyright 2009 Etienne Nijboer
********************************************************
http://pastebin.com/Gmh5HS4x
(我没有在这里发布代码,因为它超出了 StackOverflow 的 30.000 个字符限制)
UPDATE:
这是作者提供的一些有用信息,解释了我需要做的事情来添加列表视图支持,但实际上我自己做不到:
顺便说一句,为列表视图添加功能应该不那么难
也是了解其工作原理的好方法。你需要
创建一个新的监视器来捕获列表视图更改事件并
在更改之前存储当前值。如果满足以下条件,则创建命令
您检测到已使用撤消所需的所有信息进行了更改或
重做该动作。就是这样。只要你的监视器和命令继承
从基类中,它将被自动检测和使用。
http://www.codeproject.com/Articles/43436/Undo-Redo-Framework
UPDATE:
该类的所有者更新了代码,添加了我需要的内容之一,即我要求的标签项撤消/重做操作。
· 撤消/重做列表视图内的文本更改(普通模式或详细模式)
不幸的是,这个更新不足以让我添加我需要的其他撤消/重做操作,请阅读 @Plutonix 评论解释事情
以下是更新课程的部分内容,供那些可以吸收想法并帮助扩展它的人使用:
'****************************************************************************************************************
' 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)。为了
命令类您需要有实际的 ListViewItem 和
项目在被删除之前在 ListView 中的位置。你可以
只需使用这些信息创建您的结构,例如
ListViewItemRemoveUndoRedoData。当您撤消删除操作时,您只需
将存储的ListViewItem添加到ListView中您存储的位置
接着就,随即。我建议添加一个额外的计数
ListViewItemRemoveUndoRedo保存数量的数据结构
列表视图中的项目。此外,我认为你唯一需要的事件是
选定索引已更改。当这个事件发生时,有两种情况。
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