8

WindowsForm プロジェクトで元に戻す/やり直し操作を管理するために、サード パーティのコードを使用しています。

リストビューで元に戻す/やり直し操作を管理するには、クラスを拡張する必要があります。これは、次のことを意味します。

・元に戻す/やり直し アイテムとサブアイテムの追加/削除

· 行のチェックを元に戻す/チェックをやり直す/チェックを外す

· 見逃した可能性のある他の重要なことを元に戻す/やり直す

これをどのように開始すればよいかわかりません。コードは私には複雑すぎます。これに関するヘルプ/ヒント/例は非常に喜ばしいものですが、3 か月間、この変更を実行できませんでした。適切な説明または完全な例が必要になると思います。コードは次のとおりです。

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

http://pastebin.com/Gmh5HS4x

(StackOverflow の 30.000 文字制限を超えるため、ここにコードを投稿しませんでした)

アップデート:

これは、リストビューのサポートを追加するために必要なことを説明する著者からの有用な情報ですが、実際には自分ではできません。

ちなみに、リストビューに機能を追加することはそれほど難しいことではなく、それがどのように機能するかを理解するための優れた方法でもあります。リストビューの変更イベントをキャプチャし、変更前の現在の値を保存する新しいモニターを作成する必要があります。アクションを元に戻すまたはやり直すために必要なすべての情報を使用して変更が行われたことを検出すると、コマンドが作成されます。それでおしまい。モニターとコマンドが基本クラスから継承されている限り、自動的に検出されて使用されます。

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

アップデート:

クラスの所有者がコードを更新して、私が必要としていたものの 1 つである、私が要求したラベル項目の取り消し/やり直し操作を追加しました。

· リストビュー内のテキストの変更を元に戻す/やり直す (通常モードまたは詳細モード)

残念ながら、この更新は、必要な他の元に戻す/やり直し操作を追加するには不十分です。@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 を見つけるソース内)。Command クラスの場合、実際の ListViewItem と、削除される前の ListView 内の項目の位置が必要です。この情報を使用して、ListViewItemRemoveUndoRedoData のような構造を簡単に作成できます。削除を元に戻すときは、保存した ListViewItem を、保存した位置の ListView に追加するだけです。リストビュー内の項目数を保持する ListViewItemRemoveUndoRedoData 構造に追加の Count を追加することをお勧めします。さらに、必要なイベントは SelectedIndexChanged だけだと思います。このイベントが発生すると、2 つの状況が発生します。

1-アイテムの数は、以前に保存されたカウントと同じです(モニターの作成時に-1または何かに設定します):アイテム、位置、および合計アイテム数を保存します。

2- アイテムの数が、以前に保存した数よりも少ない: アイテムが削除され、その UndoRedoCommand をセットアップして元に戻せるようにします。

  • もちろん、アイテムが追加されることを意味する 3 番目のオプションがあります。

適切なイベントと、元に戻す/やり直しを実行するために保存する必要があるものを見つけるには、ある程度の創造性が必要です。より良いイベントとサポートを備えた代替のリストビューを見つける必要があることを意味するかもしれません (これは 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
4

3 に答える 3

5

328 行目をよく見ると、既に ListView を処理しています。ある意味足りない?

于 2013-10-13T13:02:05.643 に答える
1

分かりました。「やり直し」が何をすべきかを定義する方法の一部です。あなたの場合、元に戻す操作をやり直したいとします。デフォルトでは、やり直しは最後のアクションを繰り返します。何かを元に戻しても、やり直しは再び元に戻します。次のアプローチを試してください: コード フラグメントはビルディング ブロックとしてのみ理解してください。「RemoveItem」メソッドは、元に戻す/やり直しスタックにコードを追加しません。追加 - メソッドのように、この元に戻すやり直しを追加します。「元に戻す」操作が不要な場合は、

Property IsDoingUndo as boolean

To UndoManager and set it true if doing an Undo. Check this Property in Add/Remove Method and don't add something to the Undo/Redo Stack. Like :

If not _UndoManager.IsDoingUndo then 
...
else
...
endif

With this you will get control of what should be undo-able and redo-able. Sorry that I can not provide sourcecode this time.

于 2013-11-03T10:31:40.553 に答える