Отменить / Вернуть для выбора LabelEdit и Checkbox в Listview

Я сделал это пользовательское управление с помощью UndoRedo Manager, интегрированного с помощью некоторых людей.

На данный момент он может отменить / повторить только тогда, когда вы добавляете или удаляете элемент, и все, но я хотел бы реализовать отмену / возврат LabelEdit и флажок отмены / повтора.

PS: Обратите внимание, что Listview_Action ожидает ListviewItem

Вот класс диспетчера Undo / Redo, это не полный код, а просто необходимый.

Какие изменения мне нужно внести в этот класс, чтобы добавить нужные мне улучшения?

Я буду признателен, если кто-нибудь покажет мне полный пример кода.

Public Class myListView : Inherits ListView

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

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

#Region " Undo/Redo Manager "

    ''' <summary>
    ''' Enable or disble the Undo/Redo monitoring.
    ''' </summary>
    Public Property Enable_UndoRedo_Manager As Boolean = False

    ' Stacks to store Undo/Redo actions.
    Private Property Undostack As New Stack(Of ListView_Action)
    Private Property Redostack As New Stack(Of ListView_Action)

    ' Flags to check if it is doing a Undo/Redo operation.
    Private IsDoingUndo As Boolean = False
    Private IsDoingRedo As Boolean = False

    ' Delegate to Add an Item for Undo/Redo operations.
    Private Delegate Sub AddDelegate(item As ListViewItem)

    ' Delegate to Remove an Item for Undo/Redo operations.
    Private Delegate Sub RemoveDelegate(item As ListViewItem)

    ' The Undo/Redo action.
    Private action As ListView_Action = Nothing

    ' The operation.
    Public Enum Operation As Short
        Undo = 0
        Redo = 1
    End Enum

    ' The method for the Undo/Redo operation.
    Public Enum Method As Short
        Add = 0
        Remove = 1
    End Enum

    ''' <summary>
    ''' Creates a Undo/Redo Action.
    ''' </summary>
    Class ListView_Action

        ''' <summary>
        ''' Names the Undo/Redo Action.
        ''' </summary>
        Property Name As String

        ''' <summary>
        ''' Points to a method to excecute.
        ''' </summary>
        Property Operation As [Delegate]

        ''' <summary>
        ''' Method of the Undo/Redo operation.
        ''' </summary>
        Property Method As Method

        ''' <summary>
        ''' Data Array for the method to excecute.
        ''' </summary>
        Property Data As ListViewItem

    End Class

    ''' <summary>
    ''' This event is raised after an Undo/Redo action is performed.
    ''' </summary>
    Public Event UndoRedo_IsPerformed As EventHandler(Of UndoneRedoneEventArgs)
    Public Class UndoneRedoneEventArgs : Inherits EventArgs
        Property Operation As Operation
        Property Method As Method
        Property Item As ListViewItem
        Property UndoStack As Stack(Of ListView_Action)
        Property RedoStack As Stack(Of ListView_Action)
    End Class

    ''' <summary>
    ''' This event is raised when Undo/Redo Stack size changed.
    ''' </summary>
    Public Event UndoRedo_StackSizeChanged As EventHandler(Of StackSizeChangedEventArgs)
    Public Class StackSizeChangedEventArgs : Inherits EventArgs
        Property UndoStack As Stack(Of ListView_Action)
        Property RedoStack As Stack(Of ListView_Action)
        Property UndoStackIsEmpty As Boolean
        Property RedoStackIsEmpty As Boolean
    End Class

    ''' <summary>
    ''' Undo the last action.
    ''' </summary>
    Public Sub Undo()

        If Me.Undostack.Count = 0 Then Exit Sub ' Nothing to Undo.

        Me.IsDoingUndo = True
        Me.action = Me.Undostack.Pop ' Get the Action from the Stack and remove it.
        Me.action.Operation.DynamicInvoke(Me.action.Data) ' Invoke the undo Action.
        Me.IsDoingUndo = False

        Raise_UndoRedo_IsPerformed(Operation.Undo, Me.action.Method, Me.action.Data)

    End Sub

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

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

        Me.IsDoingRedo = True
        Me.action = Me.Redostack.Pop() ' Get the Action from the Stack and remove it.
        Me.action.Operation.DynamicInvoke(Me.action.Data) ' Invoke the redo Action.
        Me.IsDoingRedo = False

        Raise_UndoRedo_IsPerformed(Operation.Redo, Me.action.Method, Me.action.Data)

    End Sub

    ' Reverses an Undo/Redo action
    Private Function GetReverseAction(ByVal e As UndoneRedoneEventArgs) As ListView_Action

        Me.action = New ListView_Action

        Me.action.Name = e.Item.Text
        Me.action.Data = e.Item

        Me.action.Operation = If(e.Method = Method.Add, _
                        New RemoveDelegate(AddressOf Me.RemoveItem), _
                        New AddDelegate(AddressOf Me.AddItem))

        Me.action.Method = If(e.Method = Method.Add, _
                     Method.Remove, _
                     Method.Add)

        Return Me.action

    End Function

    ' Raises the "UndoRedo_IsPerformed" Event
    Private Sub Raise_UndoRedo_IsPerformed(ByVal Operation As Operation, _
                                           ByVal Method As Method, _
                                           ByVal Item As ListViewItem)

        RaiseEvent UndoRedo_IsPerformed(Me, New UndoneRedoneEventArgs _
                   With {.Item = Item, _
                         .Method = Method, _
                         .Operation = Operation, _
                         .UndoStack = Me.Undostack, _
                         .RedoStack = Me.Redostack})

        Raise_UndoRedo_StackSizeChanged()

    End Sub

    ' Raises the "UndoRedo_StackSizeChanged" Event
    Private Sub Raise_UndoRedo_StackSizeChanged()

        RaiseEvent UndoRedo_StackSizeChanged(Me, New StackSizeChangedEventArgs _
                   With {.UndoStack = Me.Undostack, _
                         .RedoStack = Me.Redostack, _
                         .UndoStackIsEmpty = Me.Undostack.Count = 0, _
                         .RedoStackIsEmpty = Me.Redostack.Count = 0})

    End Sub

    ' This handles when an Undo or Redo operation is performed.
    Private Sub UndoneRedone(ByVal sender As Object, ByVal e As UndoneRedoneEventArgs) _
    Handles Me.UndoRedo_IsPerformed

        Select Case e.Operation

            Case Operation.Undo
                ' Create a Redo Action for the undone action.
                Me.Redostack.Push(GetReverseAction(e))

            Case Operation.Redo
                ' Create a Undo Action for the redone action.
                Me.Undostack.Push(GetReverseAction(e))

        End Select

    End Sub

    ' Monitors when an Item is added to create an Undo Operation.
    Private Sub OnItemAdded(sender As Object, e As ItemAddedEventArgs) _
    Handles Me.ItemAdded

        If Me.Enable_UndoRedo_Manager _
            AndAlso (Not Me.IsDoingUndo And Not Me.IsDoingRedo) Then

            Me.Redostack.Clear()

            ' // Crate an Undo Action
            Me.action = New ListView_Action
            Me.action.Name = e.Item.Text
            Me.action.Operation = New RemoveDelegate(AddressOf Me.RemoveItem)
            Me.action.Data = e.Item
            Me.action.Method = Method.Remove

            Me.Undostack.Push(action)

            Raise_UndoRedo_StackSizeChanged()

        End If

    End Sub

    ' Monitors when an Item is removed to create an Undo Operation.
    Private Sub OnItemRemoved(sender As Object, e As ItemRemovedEventArgs) _
    Handles Me.ItemRemoved

        If Me.Enable_UndoRedo_Manager _
            AndAlso (Not Me.IsDoingUndo And Not Me.IsDoingRedo) Then

            Me.Redostack.Clear()

            ' // Crate an Undo Action
            Me.action = New ListView_Action
            Me.action.Name = e.Item.Text
            Me.action.Operation = New AddDelegate(AddressOf Me.AddItem)
            Me.action.Data = e.Item
            Me.action.Method = Method.Add

            Me.Undostack.Push(action)

            Raise_UndoRedo_StackSizeChanged()

        End If

    End Sub

#End Region

End Class

person ElektroStudios    schedule 07.11.2013    source источник
comment
хотите сэкономить время? в основном забываем про ReDo. в этом (и во французском) довольно много недостатков. каждый раз, когда вы нажимаете новое действие UnDo (создаете новый узел истории управления), вы должны очищать очередь ReDo. если вы этого не сделаете, вы можете попробовать повторно выполнить проверку или отправить текст для элемента, которого больше нет. Для демонстрации посмотрите кнопки VS UnDo / ReDo. введите 10 строк мусора, затем отмените * 3. ReDo включен и работает. Когда он включен, введите еще одну строку мусора, она погаснет (новая история). Проверки и текстовые правки можно сохранить и использовать в новой истории, но это кажется действительно плохой идеей.   -  person Ňɏssa Pøngjǣrdenlarp    schedule 08.11.2013


Ответы (1)


Во-первых, поскольку вы начинаете с производного класса, добавьте к нему следующие события:

' (your Item events still lack a Sender and Item to save a lot of work)
Public Event CheckChanged(ByVal sender As Object, _
           ByVal lice As ListItemCheckChangedArgs)
Public Shadows Event BeforeLabelEdit(ByVal sender As Object, _
           ByVal oldText As String)

BeforeLabelEdit необходим, потому что базовый LV сохранил текст старой метки как Nothing в начале, и вы захотите изменить это на свойство .Text, чтобы фактически обнаружить изменение. Затем для отслеживания изменений текста вам потребуется сделать что-то иное, чем для ItemAdded (независимо от того, является ли оно универсальным или внутренним).

Когда срабатывает Before..., сохраните копию ... Наблюдатель будет сравнивать _BeforeText с _AfterText (полученный в результате наблюдения за обычным событием AfterLabelEdit) и, если они разные, помещает _BEforeText в стек.

CheckChanged - это что-то вроде жульничества. Вы можете напрямую щелкнуть LVItem Check и не запускать какие-либо события Enter / GotFocus и т. Д. Это означает, что Наблюдатель не может получить значение _BeforeChecked. Собственный LV ItemChecked срабатывает, и я просто реорганизовываю из него аргументы, чтобы передать то, что мне нужно, в Watcher (через указанное выше событие). Арги вшей:

Public Class ListItemCheckChangedArgs
    Public Index As Integer                ' index of item checked
    Public OldValue As CheckState          
    Public NewValue As CheckState

    Public Sub New(ByVal ndx As Integer, ByVal ov As CheckState, _
            ByVal nv As CheckState)
        Index = ndx
        OldValue = ov
        NewValue = nv

    End Sub
End Class

Затем наблюдатель создает объект UnDoAction с OldValue в качестве данных. Даже без наблюдателя это поможет зафиксировать то, что вам нужно.

Важной частью является класс Undo (внутренняя единая система обмена сообщениями была бы другой):

 Public Class ListViewEUndo
    '... WHO this action applies to
    Friend Ctl As ListViewEX

    ' a listview item, or string (Text) or bool (Check)
    Friend UnDoData As Object

    Friend LVActionType As LVEActionType = LVEActionType.None

    ' original index of items removed / index of item checked
    Friend Index As Integer = -1

  Public Sub New(ByVal _ctl As ListViewEX, ByVal _LvAType As LVEActionType, _
            ByVal _data As Object)
        Ctl = _ctl
        LVActionType = _LvAType
        UnDoData = _data

  End Sub
    ...

LVEActionType - это просто AddItem, RemoveItem и т. Д. (Что это за тип действия)

Вы МОЖЕТЕ разработать базовый класс, а затем унаследовать его для TextUndo, CheckUndo и ItemUndo. Пока не ясно, лучше ли это или итоговые короткие операторы SELECT CASE. Наконец, нет ДЕЛЕГАТОВ, потому что класс / помощники UndoManager будут применять изменения сами, а не экспортировать работу в from или control (это ТАКЖЕ помогает избежать действий Pushing, вызванных действиями Undo / Redo!).

Я не уверен, что это full code example, но может помочь с вашей проблемой в зависимости от того, внутренняя она или нет (внутренние события почти не нуждаются - они нужны в основном для запуска действий наблюдателя - захват до значения, захват / сравнение после значения, ответить на Add / RemoveItem и т. д.).

person Ňɏssa Pøngjǣrdenlarp    schedule 07.11.2013