Question

I've done this usercontrol with an UndoRedo Manager integrated with the help of some people.

At the moment it only can undo/redo when you add or delete an item and that's all, but I would like to implement LabelEdit undo/redo and checkbox undo/redo.

PS: Notice that the Listview_Action expects an ListviewItem

Here is the Undo/Redo manager Class, is not the full code but just the necessary.

What changes I need to perform in this Class to add the improvements that I need?

Please I will appreciate it if someone could show me a full code example.

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
Was it helpful?

Solution

first off, since you are starting with a derived class, add these events to it:

' (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 is needed because the basic LV stored the old label text as Nothing at the start and you'll want to change that to the .Text property to actually detect a change. Next, watching for Text changes will require you to do something different than you have for ItemAdded (no matter if it is universal or internal).

When Before... fires, store a copy...a Watcher would compare _BeforeText to _AfterText (gotten from watching the normal AfterLabelEdit event) and if they are different, push the _BEforeText onto the stack.

CheckChanged is a bit of a cheat. You can click on a LVItem Check directly and not fire any sort of Enter/GotFocus etc event. Which means a Watcher cannot get a _BeforeChecked value. The native LV ItemChecked does fire and I just reorganize the args from it to pass what I need to the Watcher (via the above event). THe lice args:

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

The watcher then makes an UnDoAction object with OldValue as the data. Even without a watcher, this will help capture what you need.

The important part is the Undo class (an internal UM would be different):

 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 is just AddItem, RemoveItem etc (what this action type is)

You COULD design a base class and then inherit it for a TextUndo, CheckUndo and ItemUndo. It is a toss up whether that or the resulting short SELECT CASE statements are better. Finally, there are no DELEGATES because UndoManager class/helpers are going to do apply the changes themselves rather than export the work to the from or control (this ALSO helps to avoid Pushing actions caused by Undo/Redo actions!).

I'm not sure it is a full code example, but may help with your issue depending on whether it is internal or not (internal needs almost no events - those are needed mainly to trigger Watcher actions - capture before value, capture/compare After value, respond to Add/RemoveItem etc).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top