Reputation: 20464
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
Reputation: 38875
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