When I remove a single Item from the Listview
- easy one.
RemoveItem removes an item from the list AND adds it to the ReDo stack, but it still also resides on the UnDo stack!!! If you Add 5, remove 1 and then Undo, you get 2 copies of item 5 on the Redo!
First, you should change the AddItem mechanism to a straight counter to make debugging easier
nLVItemIndex += 1
Dim index As String = (nLVItemIndex).ToString
newItem = New ListViewItem
newItem.Text = "Item " & index
newItem.SubItems.Add("Hello " & index)
newItem.SubItems.Add("World " & index)
AddItem(newItem)
Using CStr
on ListView item count creates names that can already exist on the UnDo/Redo stack and makes debugging more difficult.
I should think a GUI level, user invoked action like RemoveItem would fall into the UnDo stack. You are equating AddItem with UnDO and RemoveItem with Redo which is wrong. Everything from the GUI Form level should fall into the Undo stack, and the only way it should get into the ReDo is via a UM.Undo method.
Moving it to the UnDo stack will reveal another problem: Your UnDo Manager does very little for itself and uses the form level AddItem/RemoveItem rather than its own internal procedures (he cant even create his own UnDo/Redo Actions.) The result is that ALL Additem actions Push a Remove Action onto the UnDo stack; and All RemoveItems push a ReDo action which is NOT valid since you do want to UnDo a Remove!
The end result is that UM.UndoLastAction
pops from UnDo (good) then DynamicInvoke
triggers Form.AddItem which issues an UnDo Push (very bad because one was just popped - in fact thats what we are still doing - thats why the original had IsRedoing flags). UnDo Manager needs major brain surgery to do his own work because GUi level Add/Remove actions are not the same as UnDo/ReDo.
- GUI Add Item ----> Push a remove action
- GUI Remove ----> Push an add action
- UM Pop Add ------> Add item; Push Remove onto ReDo
- UM Pop Remove ------> Remove; Push Add onto Redo
This then reveals that UnDoManager doesnt have reference to the control he is "managing" let alone the ability to monitor more than one LV. I would think that an AddRange approach would just aggravate the issues above (cant find the essentials in the wall of code).
Finally, is it really necessary to post all the prop XML comment headers in the wall of text? Are all the Draw overrides germane to the Undo? No.
EDIT
Here is roughly what UnDoManager.UnDo
needs to do (from my rework of that overblown one you started with):
Friend Function UnDo() As Boolean
If _undoStack.Count = 0 Then Exit Function
Dim ur As UndoAction ' ie Command
_IgnoreChange = True ' ie IsUnDoing so you dont Push while Popping
ur = _undoStack.Pop ' get the Undo, such as LV.RemoveItem
ur.Undo() ' Undo whatever it is (could be a checkbox etc)
_IgnoreChange = False ' open for business
_redoStack.Push(ur) ' push the same Action onto the ReDo
' I dont bother changing a code (yet) because
' if it is in Undostack it is an UnDo
return True
End Function
My UnDoAction
is just the Control being undone and the Data As Object
. Since MOST Controls have only 1 thing a user messes with, no problem. LV has 2 legitimate user actions (Checked and Label Edit) so to be able to do either, it would need to be expanded.
Mine and the other one rely on polymorphism where undoStack(2) might be a checkedlistbox undo action and undoStack(9) might be a combox action - the watchers(monitors) KNOW which type to create AND how to Undo/ReDo the action. A Text Undo (TextBox, Combo, MaskedEdit and DateTimePicker) is just:
Friend Overrides Function Undo() As Boolean
_Ctl.Text = _UndoData
Return True
End Function
What I wonder about is now you are just doing LastItem - what about RemoveSelectedItem? How do you put it back in order? If you keep ANY sort of order ref it might be invalid because that ref might not be there anymore either.