1

ここで、この質問の 2 番目の部分について説明します。ListView項目の追加/削除のための Undo/Redo 操作の実装と、この他のExtend this Class to Undo/Redo in a Listviewです。

ListView アイテムの追加/削除の元に戻す/やり直し操作を実装しようとしています。

この LV UndoManager コードのコーディングをもう少し進めましたが、常に進めようとすると非常に困難です。

現時点では、単一のアイテムを追加してから、追加したアイテムを完全に元に戻す/やり直すことができます。

私が抱えている問題は次のとおりです。

· Listview から 1 つのアイテムを削除すると、LV に削除されたアイテムを再度追加するための「元に戻す」を実行できません。

元に戻せないアイテムの場合に範囲を追加すると、それを呼び出すと例外UndoLastActionがスローされますSystem.Reflection.TargetParameterCountException

·アイテムの範囲を削除すると、操作を元に戻す/やり直すことができず、同じ例外が発生します。

履歴書では、単一のアイテムを追加すると、完全に元に戻す/やり直すことができます。単一のアイテムを削除すると、正しく元に戻すことができず、ListViewItems の範囲を元に戻す/やり直すこともできません。

それらの問題を解決するのを手伝ってくれる人が必要です... または少なくともそのうちの1つを、忍耐強く。

コードが少し大きいので、アップロードしたソース プロジェクトを開いてテストする際に、理解してエラーを見つけるのに時間がかからないのではないかと思います。

fullソースは次のとおりです。

http://elektrostudios.tk/UndoManager%20Test%20Application.zip

ただの画像:

ここに画像の説明を入力

UndoManager クラスは次のとおりです。

Class ListView_UndoManager

    Private action As ListView_Action = Nothing

    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

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

    End Sub

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

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

        action = Redostack.Pop() ' Get the Action from Stack and remove it.
        action.Operation.DynamicInvoke(action.data) ' Invoke the redo 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 ListViewItem()

End Class

ここに私が使用している ListView ユーザー コントロールがあります。トリガーしているイベントが重要であるため、これを投稿します: ItemAddedItemRemovedRangeItemAddedおよびRangeItemRemoved.

Public Class LV : Inherits ListView

Public Shared Event ItemAdded As EventHandler(Of ItemAddedEventArgs)
Public Class ItemAddedEventArgs : Inherits EventArgs
    Public Property Item As ListViewItem
End Class

Public Shared Event ItemRemoved As EventHandler(Of ItemRemovedEventArgs)
Public Class ItemRemovedEventArgs : Inherits EventArgs
    Public Property Item As ListViewItem
End Class

Public Shared Event RangeItemAdded As EventHandler(Of RangeItemAddedEventArgs)
Public Class RangeItemAddedEventArgs : Inherits EventArgs
    Public Property Items As ListViewItem()
End Class

Public Shared Event RangeItemRemoved As EventHandler(Of RangeItemRemovedEventArgs)
Public Class RangeItemRemovedEventArgs : Inherits EventArgs
    Public Property Items As ListViewItem()
End Class

Public Sub New()

    Me.Name = "ListView_Elektro"
    Me.GridLines = True
    Me.FullRowSelect = True
    Me.MultiSelect = True
    Me.View = View.Details

End Sub

''' <summary>
''' Adds an Item to the ListView,
''' to monitor when an Item is added to the ListView.
''' </summary>
Public Function AddItem(ByVal Item As ListViewItem) As ListViewItem

    RaiseEvent ItemAdded(Me, New ItemAddedEventArgs With { _
                             .Item = Item
                       })

    Return MyBase.Items.Add(Item)

End Function

''' <summary>
''' Adds a range of Items to the ListView,
''' to monitor when an Item is added to the ListView.
''' </summary>
Public Sub AddItem_Range(ByVal Items As ListViewItem())

    RaiseEvent RangeItemAdded(Me, New RangeItemAddedEventArgs With { _
                                  .Items = Items
                            })

    MyBase.Items.AddRange(Items)

End Sub

''' <summary>
''' Removes an Item from the ListView
''' to monitor when an Item is removed from the ListView.
''' </summary>
Public Sub RemoveItem(ByVal Item As ListViewItem)

    RaiseEvent ItemRemoved(Me, New ItemRemovedEventArgs With { _
                               .Item = Item
                         })

    MyBase.Items.Remove(Item)

End Sub

''' <summary>
''' Removes a range of Items from the ListView
''' to monitor when an Item is removed from the ListView.
''' </summary>
Public Sub RemoveItem_Range(ByVal Items As ListViewItem())

    RaiseEvent RangeItemRemoved(Me, New RangeItemRemovedEventArgs With { _
                                    .Items = Items
                              })

    For Each Item As ListViewItem In Items
        MyBase.Items.Remove(Item)
    Next

End Sub

End Class

最後に、Test アプリケーションの Form1 コードを示します。項目の追加/削除と元に戻す/やり直しを呼び出すために使用するものを次に示しますが、カスタム ListView ユーザー コントロールのメソッドを呼び出すので、注意が必要です... :

Public Class Form1

    Dim _undoManager As New ListView_UndoManager
    Dim LVItem As ListViewItem

    Delegate Sub AddDelegate(item As ListViewItem)
    Delegate Sub RemoveDelegate(item As ListViewItem)

    Delegate Sub AddRangeDelegate(item As ListViewItem())
    Delegate Sub RemoveRangeDelegate(item As ListViewItem())

    ' Adds a single item
    Private Sub Button_AddItem_Click(sender As Object, e As EventArgs) _
    Handles Button_AddItem.Click

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

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

        LV1.AddItem(LVItem)

    End Sub

    ' Adds a range of 2 items to the ListView
    Private Sub Button_AddRange_Of_Items_Click(sender As Object, e As EventArgs) Handles Button_AddRange_Of_Items.Click

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

        Dim lvitem As New ListViewItem With {.Text = index}
        lvitem.SubItems.AddRange({"Hello " & index, "World " & index})

        Dim lvitem2 As New ListViewItem With {.Text = index + 1}
        lvitem2.SubItems.AddRange({"Hello " & index + 1, "World " & index + 1})

        LV1.AddItem_Range({lvitem, lvitem2})

    End Sub

    ' Removes the last item
    Private Sub Button_RemoveLastItem_Click(sender As Object, e As EventArgs) _
    Handles Button_RemoveLastItem.Click

        If LV1.Items.Count <> 0 Then

            LV1.RemoveItem(LV1.Items.Cast(Of ListViewItem).Last)

        End If

    End Sub

    ' Clear all items
    Private Sub Button_Clear_Items_Click(sender As Object, e As EventArgs) _
    Handles Button_Clear_Items.Click

        LV1.Items.Clear()

    End Sub

    ' Clear the Undo/Redo Stacks
    Private Sub Button_Clear_Stacks_Click(sender As Object, e As EventArgs) _
    Handles Button_Clear_Stacks.Click

        _undoManager.Undostack = New Stack(Of ListView_Action)
        _undoManager.Redostack = New Stack(Of ListView_Action)

        Label_UndoCount_Value.Text = CStr(0)
        Label_RedoCount_Value.Text = CStr(0)

    End Sub

    ' Refreshes the Stacks Count
    Private Sub Refresh_StackCount()

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

    End Sub

    ' Monitors when an Item is added
    Private Sub ListView_ItemAdded(sender As Object, e As LV.ItemAddedEventArgs) _
    Handles LV1.ItemAdded

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

        _undoManager.Undostack.Push(u)

        Refresh_StackCount()

    End Sub

    ' Monitors when a range of Items are added
    Private Sub ListView_RangeItemAdded(sender As Object, e As LV.RangeItemAddedEventArgs) _
    Handles LV1.RangeItemAdded

        ' // Crate an Undo Action
        Dim u As New ListView_Action()
        With u
            .name = "Remove Item Range"
            .Operation = New RemoveRangeDelegate(AddressOf LV1.RemoveItem_Range)
            .data = e.Items
        End With

        _undoManager.Undostack.Push(u)

        Refresh_StackCount()

    End Sub

    ' Monitors when an Item is removed
    Private Sub ListView_ItemRemoved(sender As Object, e As LV.ItemRemovedEventArgs) _
    Handles LV1.ItemRemoved

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

        _undoManager.Redostack.Push(r)

        Refresh_StackCount()

    End Sub

    ' Monitors when a range of Items are removed
    Private Sub ListView_RangeItemRemoved(sender As Object, e As LV.RangeItemRemovedEventArgs) _
    Handles LV1.RangeItemRemoved

        ' // Create a Redo Action
        Dim r As New ListView_Action()
        With r
            .name = "Add Item"
            .Operation = New AddRangeDelegate(AddressOf LV1.AddItem_Range)
            .data = e.Items
        End With

        _undoManager.Redostack.Push(r)

        Refresh_StackCount()

    End Sub

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

        _undoManager.UndoLastAction()

    End Sub

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

        _undoManager.RedoLastAction()

    End Sub

    Private Sub Button_Remove_Range_Of_Items_Click(sender As Object, e As EventArgs) Handles Button_Remove_Range_Of_Items.Click

    If LV1.Items.Count > 1 Then

        Dim lvi1 As ListViewItem = LV1.Items(LV1.Items.Count - 1)
        Dim lvi2 As ListViewItem = LV1.Items(LV1.Items.Count - 2)

        LV1.RemoveItem_Range({lvi1, lvi2})

    End If


    End Sub

End Class

PS: 私が言ったように、ソースをダウンロードしてテストする方が実際には非常に友好的です。

4

1 に答える 1

2

When I remove a single Item from the Listview- 簡単なもの。

RemoveItem は、リストから項目を削除し、ReDoスタックに追加しますが、UnDo スタックにも存在します!!! 5 を追加し、1 を削除してから元に戻すと、やり直しでアイテム 5 のコピーが 2 つ得られます。

最初に、AddItem メカニズムをストレート カウンターに変更して、デバッグを容易にする必要があります。

    nLVItemIndex += 1
    Dim index As String = (nLVItemIndex).ToString

    newItem = New ListViewItem
    newItem.Text = "Item " & index
    newItem.SubItems.Add("Hello " & index)
    newItem.SubItems.Add("World " & index)

    AddItem(newItem)

ListView アイテム カウントで使用CStrすると、元に戻す/やり直しスタックに既に存在する可能性のある名前が作成され、デバッグがより困難になります。

GUI レベルで、RemoveItem のようなユーザーが呼び出したアクションは UnDo スタックに分類されると考えるべきです。AddItem を UnDO と、RemoveItem を Redo と同一視していますが、これは間違っています。GUI フォーム レベルのすべてが Undo スタックに分類され、ReDo に入る唯一の方法は UM.Undo メソッドを使用することです。

それを UnDo スタックに移動すると、別の問題が明らかになります。UnDo Manager 自体はほとんど機能せず、独自の内部手順ではなくフォーム レベルの AddItem/RemoveItem を使用します (独自の UnDo/Redo アクションを作成することさえできません)。すべての Additem アクション Remove アクションを UnDo スタックにプッシュします。そしてすべてのRemoveItemsはReDoアクションをプッシュしますが、ReDoを元に戻したいので有効ではありません!

最終結果は、UM.UndoLastActionUnDo (良い) からDynamicInvokepop が発生し、UnDo Push を発行する Form.AddItem をトリガーすることです (1 つがちょうど pop されたので非常に悪い - 実際、それはまだ行っていることです - 元のものが IsRedoing フラグを持っていた理由です)。GUI レベルの Add/Remove アクションは UnDo/ReDo と同じではないため、UnDo Manager が自分の作業を行うには大がかりな脳手術が必要です。

  • GUI アイテムの追加 ----> 削除アクションをプッシュ
  • GUI 削除 ----> 追加アクションをプッシュ
  • UM ポップ追加 ------> アイテムを追加します。削除をやり直しにプッシュ
  • UM ポップ削除 ------> 削除; Add を Redo にプッシュ

これにより、UnDoManager は、複数の LV を監視する機能はもちろん、「管理」しているコントロールへの参照を持っていないことが明らかになります。AddRange アプローチは上記の問題を悪化させるだけだと思います (コードの壁に本質的なものを見つけることができません)。

最後に、すべての prop XML コメント ヘッダーをテキストの壁に投稿する必要は本当にあるのでしょうか? すべての Draw オーバーライドは Undo と密接に関連していますか? いいえ。

編集

これは大まかに何UnDoManager.UnDoをする必要があるかです(あなたが始めた誇張されたものの私のリワークから):

Friend Function UnDo() As Boolean
    If _undoStack.Count = 0 Then Exit Function

    Dim ur As UndoAction         ' ie Command

    _IgnoreChange = True          ' ie IsUnDoing so you dont Push while Popping
    ur = _undoStack.Pop           ' get the Undo, such as LV.RemoveItem
    ur.Undo()                     ' Undo whatever it is (could be a checkbox etc)
    _IgnoreChange = False         ' open for business
    _redoStack.Push(ur)           ' push the same Action onto the ReDo
                                  ' I dont bother changing a code (yet) because
                                  ' if it is in Undostack it is an UnDo
   return True
End Function

UnDoActionは単にコントロールが元に戻され、Data As Object. MOST コントロールには、ユーザーが混乱することが 1 つしかないため、問題ありません。LV には 2 つの正当なユーザー アクション (Checked と Label Edit) があるため、どちらかを実行できるようにするには、拡張する必要があります。

私のものと他のものは、 undoStack(2) がチェックリストボックスの元に戻すアクションであり、 undoStack(9) がコンボックスアクションである可能性があるポリモーフィズムに依存しています-ウォッチャー(モニター)は、作成するタイプと、アクションを元に戻す/やり直す方法を知っています。Text Undo (TextBox、Combo、MaskedEdit、および DateTimePicker) は次のとおりです。

Friend Overrides Function Undo() As Boolean

    _Ctl.Text = _UndoData
    Return True

End Function

私が疑問に思っているのは、LastItem を実行していることです。RemoveSelectedItem はどうですか? どうやって元に戻すの?任意の種類の注文参照を保持すると、その参照ももう存在しない可能性があるため、無効になる可能性があります。

于 2013-11-04T21:31:01.110 に答える