Ein paar Worte noch zur Idee, ohne OOP:
Eine Funktion:
Code: Alles auswählen
Structure Action
actionId.l // hält Typ der Aktion, z.B. 1=Bild drehen, 2=Bild skalieren, ...
d1.d // Parameter aller möglichen Aktionen ggf. auch jeweils als StructureUnion
d2.d
...
EndStructure
NewList history.Action() // hält alle Aktionen
// Im Programm soll nach einem Eingabedialog nun etwa das Bild rotiert werden, dann muss
// man etwa folgende Funktion aufrufen mit DoAction(#True, #ActionId_Rotate, 12)
Procedure DoAction(addToList.l, actionId.l, d1.d, d2.d, ...) // Parameter gemäß Structure, oder gleich ein neues Structure-Objekt selbst als Parameter
If actionId = #ActionId_Rotate
Rotate(#DO, d1) // rotiere das Bild, und zwar als #DO-Aktion
ElseIf actionId = #ActionId_Swap
...
EndIf
If addToList
// nun wird die Aktion der Liste zugefügt. Zuerst lösche alle Listenelemente, die nach dem aktuellen Listenelement liegen. Damit werden bereits "ge-UNDOte" Schritte endgültig gelöscht, und können auch nicht mehr per ReDo ausgeführt werden.
// nun erzeuge neues Element in Liste und setze dessen Parameter gemäß aktueller Aktion
AddElement(queue)
queue\actionId = actionId
queue\d1 = d1
...
EndIf
EndProcedure
Procedure UnDo()
// Führe aktuelles Element (todo: sofern existent) von Liste als UNDO aus
If queue\actionId = #ActionId_Rotate
Rotate(#UNDO, d1) // rotiere das Bild, und zwar als #UNDO-Aktion
ElseIf actionId = #ActionId_Swap
...
EndIf
// todo: Setze das aktuelle Listen-Element auf eine Position vorher
EndProcedure
Procedure ReDo()
// Falls am Ende der List, dann ProcedureReturn
// todo: Setze das aktuelle Listen-Element auf eine Position später
// Führe aktuelles Element von Liste als DO aus
If queue\actionId = #ActionId_Rotate
Rotate(#DO, d1) // rotiere das Bild, und zwar als #DO-Aktion
ElseIf actionId = #ActionId_Swap
...
EndIf
EndProcedure
Procedure Rotate(undo.l, degree.d)
If undo
degree = -degree
EndIf
// rotiere das Bild
EndProcedure
Auf diese Weise würde ein solches System funktionieren, incl. Undo und Redo. Jede rückgängigmachbare Aktion wird also vom z.B. Mausevent nicht direkt aus aufgerufen, sondern nur indirekt über einen Aufruf von Do(...) mit den jeweiligen Parametern. Später kann jederezti Undo und Redo gemacht werden. Wird nach einigen Undo-Schritten eine neue Aktion ausgeführt, dann wird der Rest der geundoten Schritte endgültig gelöscht.
Bei dieser Implementierung wird allerdings folgende Annahme gemacht: Durch Ausführen der UNDO-Funktion lässt sich eine vorher gemachte Aktion exakt wieder rückgängig machen. Das mag zwar in vielen Anwendungen stimmen, aber nicht immer, z.B. bei der Bildverarbeitung gibt es bei jeder Bildrotation Rundungsverluste. Ein häufiges Undo/Redo einer Drehung würde aber jedesmal neu die Drehung berechnen, einmal vorwärts, einmal rückwärts.
Eine alternative Implementierung, die aber speicherintensiver ist, wäre: Vor jeder Aktion wird der komplette Programmstatus gespeichert, z.B. auch das gegenwärtige bearbeitete Bild, alle Parameterwerte, etc.). Dieses DatenBackup wird in die queue gepackt. Bei einem Undo muss nun lediglich das vorherige Backup wiederhergestellt werden. Eine Speicherbegrenzung für die Undo-Liste ist dabei sicherlich sinnvoll. Ein weiterer Vorteil: Ein REDO wird nicht neu berechnet (ebenso wie die UNDO nicht), sondern lediglich wieder aus dem Backup hergestellt. Das ist für größere Aktionen, wie Bildveränderungen sicherlich manchmal performanter, als jedesmal neu zu berechnen.
Welches der beiden Konzepte du wählen solltest, hängt also von deinen Anforderungen ab.