Dot-Net

將此類擴展到列表視圖中的撤消/重做

  • November 3, 2013

我正在使用第三方程式碼來管理 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

試試這個方法:忘記這個目前的實現,開始實現你自己的 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

如果仔細查看第 328 行,它已經處理了一個 ListView。它在某些方面缺乏嗎?

引用自:https://stackoverflow.com/questions/19343102