ElektroStudios
ElektroStudios

Reputation: 20464

UserControl event is handled for all the same controls

The problem is this, I have a ListView with own events and two Stacks

And a property like this which need to be enabled to handle those events:

Public property Enable_Something as boolean

Well, I add my user control to the UI and I enable the property, but if I add the same control one more time in the UI then the events is handled for both controls! and the stacks are pushed/poped by both controls...

So the property is in conflict and also the events and stacks are in conflict because the second control adds some things into the first control stacks.

I want to separate the Operations/Events/Stacks for each control.

This is the form1 class (read the comments):

Public Class Form1

    Friend WithEvents Undom As ListView_Elektro.UndoRedoManager

    Private lvi As ListViewItem

    Private Sub Test(sender As Object, e As EventArgs) Handles MyBase.Shown

        ' Enable undo manager in listview1 but not in listview2
        ' But no way, the undomanager is handled by both controls...
        ListView_Elektro1.Enable_UndoRedo_Manager = True
        ListView_Elektro2.Enable_UndoRedo_Manager = False

        Undom = New ListView_Elektro.UndoRedoManager(ListView_Elektro1)

        lvi = New ListViewItem("hello1")
        ListView_Elektro1.AddItem(lvi)

        lvi = New ListViewItem("hello2")
        ListView_Elektro2.AddItem(lvi)

    End Sub

    Private Sub Button1_Click(sender As Object, e As EventArgs) _
    Handles Button1.Click
        Undom.UndoLastAction()
    End Sub

    Private Sub Button2_Click(sender As Object, e As EventArgs) _
    Handles Button2.Click
        Undom.RedoLastAction()
    End Sub

    Private Sub OnItemAdded() Handles ListView_Elektro1.ItemAdded
        MsgBox("Item added")
        ' ( No way, both ListView_Elektro1.ItemAdded and ListView_Elektro2.ItemAdded are handled here... )
    End Sub

End Class

And here this is the important part of the user control:

Public Class ListView_Elektro : Inherits ListView

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

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

   Public Function AddItem(ByVal Item As ListViewItem) As ListViewItem
        MyBase.Items.Add(Item)
        RaiseEvent ItemAdded(Me, New ItemAddedEventArgs With {.Item = Item})
        Return Item
    End Function

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

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

    End Class

  Public Class UndoRedoManager

        Private WithEvents LV As ListView_Elektro

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

        ' Delegate to Remove an Item for Undo/Redo operations.
        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>
        ''' This event is raised after an Undo/Redo action is performed.
        ''' </summary>
        Event UndoRedo_IsPerformed As EventHandler(Of UndoneRedoneEventArgs)
        Class UndoneRedoneEventArgs : Inherits EventArgs
            Public Property Operation As Operation
            Public Property Method As Method
            Public Property Item As ListViewItem
            Public Property UndoStack As Stack(Of ListView_Action)
            Public Property RedoStack As Stack(Of ListView_Action)
        End Class

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

        Public Sub New(ByVal ListView As ListView_Elektro)
            LV = ListView
            ' MsgBox(LV.ToString)
        End Sub

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

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

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

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

        End Sub

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

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

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

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

        End Sub

        ' 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 = LV.Undostack, _
                             .RedoStack = LV.Redostack})

            Raise_UndoRedo_StackSizeChanged()

        End Sub

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

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

        End Sub

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

            action = New ListView_Action

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

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

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

            Return action

        End Function

        ' 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.
                    LV.Redostack.Push(GetReverseAction(e))

                Case Operation.Redo
                    ' Create a Undo Action for the redone action.
                    LV.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 LV.ItemAdded

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

                LV.Redostack.Clear()

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

                LV.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 LV.ItemRemoved

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

                LV.Redostack.Clear()

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

                LV.Undostack.Push(action)

                Raise_UndoRedo_StackSizeChanged()

            End If

        End Sub

    End Class

    End Class

Upvotes: 0

Views: 149

Answers (1)

You started out going a totally different way with this, so forgive us/me for not getting the updated mission statement. This might remove the immediate issue:

Public Property Enable_UndoRedo_Manager As Boolean = False

 ...
Public Function AddItem(ByVal Item As ListViewItem) As BOOLEAN
    MyBase.Items.Add(Item)

    ' TEST to see if this LV instance should signal a change:
    If Enable_UndoRedo_Manager THen
        RaiseEvent ItemAdded(Me, New ItemAddedEventArgs With {.Item = Item})
        Return True
    end if
    Return False
End Function

You'll still have other issues because you have ONE form level UM, so you cant really expect it to create separate stacks.

EDIT

Since it would be HIGHLY detrimental for an LV to change its Enable_UM status, I would make it a ctor argument that cannot be changed (as long as UM is not a totally internal helper class to LV).

EDIT EDIT

A conceptual for an internal UM:

Private _undoStack As Stack(Of ListViewEUndo)


Public Function AddItem(ByVal Item As ListViewItem) As Boolean
    ' need to not stack Undos
    _IgnoreChange = True
    ' NOT NEEDED for an internal UM
    'RaiseEvent ItemAdded(Me, Item)

    MyBase.Items.Add(Item)
    AddUnDoAction(Item)          ' create an undo action, push it
    _IgnoreChange = False
End Function

If you wanted, the UM code could be in a UM Class which you instance, and add actions thus:

MyBase.Items.Add(Item)
myUndoMgr.AddUnDoAction(Item)       ' _undoStack would be inside this class'
                                    ' in this case       

No need for events for this LV to tell this UM to add something. You COULD still use a watcher and event, but that seems to be something you are avoiding. The architectural problem is that you could not use LVE without UM and UM would have no use outside LVEs

I should add that the only extra hurdle for 1 UM to do multiple controls is that you have to store a reference to the control to UnDo/ReDo in the UndoAction class. This is of course easily captured using the sender in the various events.

EDIT * 3

One of the first things to change if you are going to use events is this:

Public Shared Event ItemAdded As EventHandler(Of ItemAddedEventArgs)

to:

' a good UnDo Mgr will need to know WHO changed 
'     and might as well tell WHAT changed to make things easy
Public Event ItemAdded(ByVal sender As Object, ByVal item As ListViewItem)
Public Event ItemRemoved(ByVal sender As Object, ByVal item As ListViewItem)

With sender as one of the event args, you code can either: a) set the ListUndoAction.Ctl = sender so that a universal UM knows which control this action applies to OR b) evaluate if sender.UndoManagerEnabled = True

Upvotes: 1

Related Questions