My undo/redo approach is slightly different. I have an array of dictionaries and a CurrentIndex property.
Each action that makes changes adds a new dictionary to the array and increments CurrentIndex. This dictionary contains 3 sub-dictionaries: “Before”, “After” and “Common” (these are the name of the keys of the root dictionary).
The “Common” dictionary contains keys as string. One key is mandatory: “KindOfAction”. This one holds the action that was done (paste, drag, change a property, move, etc.).
Other keys in the “Common” dictionary holds data pertaining to both undo and redo (like an identifier for the target item or action-specific data that are common for undo and redo).
The “Before” and “After” dictionaries contain the properties that have changed. For undoing, I take the value in the “Before” dictionary; for redoing, the one in the “After” one.
For example, when changing the colour of a line, the “Common” dictionary would contain “KindOfAction”=“ChangeProperty” and “PropertyDesc”=“LineColour”. The “Before” dictionary contains “Colour”=RGB(0,255,0) and the “After” dictionary contains “Colour”=RGB(0,0,255) (if the line was green and went blue).
Each action obviously defines its set of properties (mandatory or not).
One thing I had to do is to add an index reference to each object to uniquely reference it). That’s because if you delete an item, undo the deletion and redo it, storing the object as a direct reference in the dictionary breaks (that’s not the same object in memory than it used to be). So each object has an ID as integer that will never change and is unique across all the objects in the same document.
The CurrentIndex property keeps track of where we are in the stack, for the next undo/redo. When changing the direction (either from undo to redo or the other way around), don’t forget you won’t increment/decrement as you would when the direction is the same (logical, but important).
In various places of your code, where appropriate, you add a new dictionary to the stack, with the sub-dictionaries filled. For that, I keep a reference to the current dictionary (the one that hasn’t been completed yet) fill it (with according methods) and terminate/append the dictionary. Something like this (this is my own implementation, to be translated):
AddToDictionary “Common”,“KindOfAction”,“Move”
AddToDictionary “Common”,“Objects”,GetIndexOfSelectedItems
AddToDictionary “Before”,“Location”,GetPropertyOfSelectedItems(Properties.Location)
AddToDictionary “After”,“Location”,NewPosition //or an array, or delta value, depending on your application
TerminateActionStack
Sub AddToDictionary(DictionaryName as string,PropertyName as string,PropertyValues as variant)
if CurrentAction=nil then
CurrentAction=New Dictionary
CurrentAction.value(“After”)=new Dictionary
CurrentAction.value(“Before”)=new Dictionary
CurrentAction.value(“Common”)=new Dictionary
end if
//Add the property (key) and its value to the corresponding dictionary
End Sub
Sub TerminateActionStack()
Redim UndoStack(CurrentIndex-1) 'Discard newest changes that were undone
UndoStack.Add CurrentAction
CurrentAction=nil 'Be ready when another part of the code adds an action (this will be a new one)
End Sub
Just an example, but it works for me.