.net - 将此类扩展为在 ListView 中撤消/重做

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

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

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

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

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

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

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

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

http://pastebin.com/Gmh5HS4x

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

UPDATE:



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

Adding functionality for a listview shouldn't be that hard by the way and also a great way of getting into how it works. You would need to create a new monitor that will capture the listview change events and store the current value before it is changed. A command is created if you detect that a change was made with all the info needed to undo or redo the action. Thats it. As long as your monitor and command inherit from the base classes it will be detected and used automatically.



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

更新:

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

· 撤消/重做 ListView 中的文本更改(正常模式或详细信息模式)

不幸的是,此更新不足以让我能够添加我需要的其他撤消/重做操作,请阅读解释事物的@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

UPDATE 2:



这是作者关于如何添加 ListView 撤消/重做操作所需的功能的内容:

I don't think you need to rewrite the full class. The hardest part of this, is finding a way of detecting when an item might be up for removal and when it is actually deleted. In the ListViewMonitor you will need to add the necessary event handlers (In the source where you find the AddHandler for BeforeLabelEdit and AfterLabelEdit). For the Command class you need to have the actual ListViewItem and the position of the item in the ListView before it was removed. You can simply create your structure with this information, something like ListViewItemRemoveUndoRedoData. When you undo the removal, you simply add the stored ListViewItem to the ListView at the position you stored with that. I would suggest adding an extra Count to the ListViewItemRemoveUndoRedoData structure that holds the number of items in the listview. Furthermore I think the only event you need is SelectedIndexChanged. When this event happens there are 2 situations.

1- The number of items is the same as the count stored earlier (set it to -1 or something on creation of the monitor): You store the item, position and the total item count.

2- The number of items is less than the count you stored earlier: An item is removed and you setup its UndoRedoCommand so it can be undone.

  • There is a 3th option of course, which would mean that an item is added

It needs some creativity to find the right events and what needs to be stored to perform the undo/redo. It might even mean that you need to find an alternative listview with better events and support (which you can find right here on codeproject)



UPDATE 3:



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

我也可以无限次重做,它只重做最后一个 Action ,我的意思是如果我撤消 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

最佳答案

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

关于.net - 将此类扩展为在 ListView 中撤消/重做,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19343102/

相关文章:

c# - 如何在 UWP 中检查有线互联网连接?

mysql - 解决多基表不支持动态SQL生成

vb.net - 如何在DataTable中搜索特定记录?

.net - 重启系统后Redis密码丢失

.net - 如何防止 Visual Studio Windows 窗体设计器删除控件?

c# - 从另一个线程捕获异常

c# - TextBox - TextChanged 事件 Windows C#

vb.net - 如何在 .NET 中制作水平列表框

c# - 在 Debug模式下单步执行时 GUI 不会重绘

c# - 调用线程一定是STA错误