1

I have too much problems trying to implement an Undo/Redo operation in a ListView Control, just to add/remove items.

I realized time ago a relative question here Extend this Class to Undo/Redo in a Listview where I was started multiple bountyes of 50, 100, 200 and 300 points, a total of 650 points... but no body could really helped me to finalize this issue in weeks and months.

But after time in that question finally a user ( @ThorstenC ) showed me a possible solution and a great idea, the code of him is incomplete so the code of him is what I'm trying to realize/finish.

The problem is Simple "undo" works fine, but when I try to redo more than 1 time it throws an exception about it can't add the same item again in the listview, also the code has more problems for example at the moment I'm not able to redo a undo operation, or undo a redo operation.

Just I need help to make a working Undo/Redo manager for Listview Item adding/removing, that's all, I have written the half part of the code, I need help to finish it I have a mess in my head with this.

Here is a simple WinForms source project in VS2012 that I've uploaded to test the undo manager fails:

http://elektrostudios.tk/UndoManager.zip

enter image description here

Here is a video to show you the errors that I get trying to undo/redo: http://www.youtube.com/watch?v=MAzChURATpM

Here is the UndoManager Class of @ThorstenC with a little retouches:

Class ListView_UndoManager

    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

    Private action As ListView_Action = Nothing

    ''' <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.

        'Redostack = New Stack(Of ListView_Action)(Redostack)
        'Redostack.Pop()
        'Redostack = New Stack(Of ListView_Action)(Redostack)

    End Sub

    ''' <summary>
    ''' Redo the last action.
    ''' </summary>
    ''' <remarks></remarks>
    Sub RedoLastAction()

        ' If Redostack.Count = Undostack.Count Then Exit Sub

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

        'Redostack = New Stack(Of ListView_Action)(Redostack) ' Reverse the Stack contents.

        action = Redostack.Pop() ' Get the Action from Stack and remove it.
        ' action = Redostack.Peek()

         action.Operation.DynamicInvoke(action.data) ' Invoke the redo Action.

        'Redostack = New Stack(Of ListView_Action)(Redostack) ' Re-Reverse the Stack contents.

    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

And here is the rest of the code where I'm trying to Undo/Redo Adding/Deleting listview items:

Public Class Form1


    Dim _undoManager As New ListView_UndoManager
    Delegate Sub RemoveDelegate(item As ListViewItem)
    Delegate Sub AddDelegate(item As ListViewItem)

    Dim newItem As ListViewItem = Nothing



    Sub AddItem(ByVal item As ListViewItem)

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

        _undoManager.Undostack.Push(u)

        ListView_Elektro1.AddItem(item)

    End Sub

    Sub RemoveItem(item As ListViewItem)

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

        _undoManager.Redostack.Push(r)

        ' Remove the ListViewItem from ListView
        ListView_Elektro1.RemoveItem(item)

    End Sub

    Private Sub Button_AddItem_Click(sender As Object, e As EventArgs) _
    Handles Button_AddItem.Click

        Dim index As String = CStr(ListView_Elektro1.Items.Count + 1)

        newItem = New ListViewItem _
                  With {.Text = index}
        newItem.SubItems.AddRange({"Hello " & index, "World " & index})

        AddItem(newItem)

    End Sub

    Private Sub Button_RemoveItem_Click(sender As Object, e As EventArgs) _
    Handles Button_RemoveItem.Click

        newItem = ListView_Elektro1.Items.Cast(Of ListViewItem).Last

        RemoveItem(newItem)

    End Sub

    Private Sub Button_Undo_Click(sender As Object, e As EventArgs) _
    Handles Button_Undo.Click

        ' _undoManager.IsDoingUndo = True
        _undoManager.UndoLastAction()
        ' _undoManager.IsDoingUndo = False

    End Sub

    Private Sub Button_Redo_Click(sender As Object, e As EventArgs) _
    Handles Button_Redo.Click

        '_undoManager.IsDoingRedo = True
        _undoManager.RedoLastAction()
        '_undoManager.IsDoingRedo = False

    End Sub

    Private Sub ListView_Elektro1_ItemAdded() _
    Handles ListView_Elektro1.ItemAdded, _
            ListView_Elektro1.ItemRemoved

        Label_UndoCount_Value.Text = CStr(_undoManager.Undostack.Count)
        Label_RedoCount_Value.Text = CStr(_undoManager.Redostack.Count)

    End Sub

End Class
4

2 回答 2

2

“El URL requerido no fue encontrado en este servidor。” 所以我很确定就是这样:

action = Redostack.Peek() ' Get the Action from Stack and remove it.

不,您正在查看它而没有从堆栈中获取它。我确实使用了它的原始和快速返工:

action = Redostack.Pop() 

由于您将实际的 LV 项目存储在堆栈中以发回 LV,因此第二次按下它时,您正在查看并尝试恢复已在 LV 中的项目。

由于大多数原始“命令”将撤消/重做数据保存为对象,为什么不直接AddLVUndoItem(item)在 UnDoReDoManager 上公开一个以使用现有代码将 LV 操作与其他控件集成?它的问题是没有 LVItemAdded 事件来自动抓取这些东西。将此作为用户控制功能与另一个问题一起使用的一个问题是,您现在有 2 个堆栈,一个跳过 LV,另一个只跳过 LV。用户可以清空其他堆栈以尝试执行 LV 撤消操作。

此外,添加项目会落入 UnDo 存储桶,但不会落入 RemoveItem 中,反之亦然 RemoveItem(无法撤消 RemoveItem)。在原始 Undo 中自动将命令添加到 ReDo 堆栈中。它在标题和旧请求中,但不在代码中。

编辑 这是错误的:

Sub RemoveItem(item As ListViewItem)
    ' // Create a Redo Action
    Dim r As New ListView_Action() With {.name = "Add Item",
                .Operation = New AddDelegate(AddressOf AddItem),
                        .data = New Object() {item}}   ' wrong!

    _undoManager.Redostack.Push(r)

    ' Remove the ListViewItem from ListView
    ListView_Elektro1.RemoveItem(item)
End Sub

您没有为 undoStack 创建新的 LVI,而是使用已通过的已删除的 LVI(回想一下,我必须更改 VS 版本的语法):

Sub RemoveItem(ByVal item As ListViewItem)

    ' // Create a Redo Action
    Dim r As New ListView_Action()
    With r
        .name = "Add Item"
        .Operation = New AddDelegate(AddressOf AddItem)
        .data = item           ' use the one passed!!!
    End With

    _undoManager.Redostack.Push(r)

    ' Remove the ListViewItem from ListView
    LVE.RemoveItem(item)
    _undoManager.ShowStacks()

End Sub

结果,您的 ReDo 没有缓存任何 UnDo 操作。看起来只是因为人工测试数据。

于 2013-11-03T22:08:43.277 回答
1

您可能还想查看这个用 VB.NET 编写的撤消/重做框架

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

它专为以下类型的控件而设计(但也应在大多数情况下与自定义控件一起使用)

  • 文本框
  • 组合框
  • 日期时间选择器
  • 数字上下
  • 蒙面文本框
  • ListBox(单选和多选)
  • 复选框
  • 单选按钮
  • 月历
  • ListView(标签文本更改)
于 2014-05-17T10:43:05.957 回答