Undo/Redo-Puffer
Autor: Steffen Ploetz
(Stand der EN Seite vom 01.04.2024)
Einleitung
Eine Undo/Redo-Funktion ist ein "Muss" in einer modernen Anwendung. Wenn Sie diese Funktion von vornherein berücksichtigen, bedeutet sie fast keinen zusätzlichen Aufwand, hilft aber sehr bei der Strukturierung Ihres Codes.
Es sind vier Komponenten zu berücksichtigen:
-
1 Die "generische" Aktion, die eine atomare Operation ist, die für Undo/Redo bereit sein sollte.
-
2 Der Undo/Redo-Puffer, der die Aktionen verwaltet.
-
3 Ihre spezifischen Aktionen, die anwendungsspezifische Funktionen implementieren.
-
4 Die Verwendung des Undo/Redo-Puffers in Ihrer Anwendung.
Die "generische" Aktion
Die "generische" Aktion ist die Basisklasse für alle anwendungsspezifischen Aktionen, die von der Undo/Redo-Funktionalität unterstützt werden sollen. Sie liefert auch den benutzerfreundlichen Anzeigenamen für die Undo/Redo-Aktion. Einige Anwendungen bieten mehr als Rückgängig-/Wiederherstellen-Schaltflächen und benötigen einen Anzeigenamen.
Der Undo/Redo-Puffer
Der Undo/Redo-Puffer verwaltet die Aktionen. Der typische Ablauf ist wie folgt:
-
Die Anwendung erstellt eine (anwendungsspezifische) Aktion.
-
Die Anwendung ruft die Methode
Do()
der Aktion auf.
-
Die Anwendung registriert die Aktion im Undo/Redo-Puffer.
Der Undo/Redo-Puffer verwaltet die Aktion dann völlig selbständig und führt bei Bedarf ein Undo oder Redo aus.
Die anwendungsspezifischen Aktionen
Die anwendungsspezifischen Aktionen sind von der "generischen" Aktion abgeleitet. Sie erben den benutzerfreundlichen Anzeigenamen und bieten den Zustand vor der Aktion (gleich dem Zustand nach der Rückgängig-Aktion) und den Zustand nach der Aktion sowie die anwendungsspezifischen Funktionen.
Die Verwendung des Undo/Redo-Puffers
Die Verwendung des Undo/Redo-Puffers umfasst die Bereitstellung des Puffers und den Aufruf seiner Methoden.
Code-Beispiel
Code-Beispiel für die "generische" Aktion
Die Methode
_SetName()
ist versteckt (durch den vorangestellten Unterstrich). Sie kann überall im Code aufgerufen werden, wenn man ihren Namen kennt, wird aber nicht aktiv angeboten. Wir wollen diese Methode nur in abgeleiteten Klassen verwenden. Da
Gambas keine
geschützten
Methoden anbietet, ist dies ein tolerierbarer Kompromiss.
Das Property
NeedsRedraw
ist hilfreich (eigentlich sogar unerlässlich) in GUI Appliaktionen. Werden durch
Do()
oder
Undo()
darzustellende Objekte manipuliert, muss anschließend ein
Redraw()
ausgelöst werden.
' Gambas class file
Export
'' Provides the user-friendly display name of this action.
Property Read Name As String
'' Provides the flag, determinimg whether the application of this action needs a redraw.
Property Read NeedsRedraw As Boolean
' Stores the user-friendly display name of this action.
Private $sName As String
'' Creates a new instance of this class.
''
'' The **nameValue** defines the name of the action. It might be displayed.
'' The **needsRedrawValue** defines whether the application of this action needs a redraw.
Public Sub _new(Optional nameValue As String = "??", Optional needsRedrawValue As Boolean = False)
$sName = "??"
$bNeedsRedraw = needsRedrawValue
End
' Stores the flag, determinimg whether the application of this action needs a redraw.
Private $bNeedsRedraw As Boolean
'' Destroys the current instance of this class.
Public Sub _free()
$sName = Null
End
' This method implements a property.
Private Function Name_Read() As String
Return $sName
End
' This method implements a property.
Private Function NeedsRedraw_Read() As Boolean
Return $bNeedsRedraw
End
' This method is hidden.
Public Sub _SetName(nameValue As String)
$sName = nameValue
End
'' This is to be overridden by any derived class.
Public Sub Do()
' Intentionally left blank.
End
'' This is to be overridden by any derived class.
Public Sub Undo()
' Intentionally left blank.
End
Code-Beispiel für Undo/Redo-Puffer
Es gibt Implementierungen, die mit separaten Listen für
Undo()
und
Redo()
arbeiten. Diese Implementierung verwaltet beides mit einer Liste und verwendet den Index
$iCurrentUndo
, um zwischen
Undo()
und
Redo()
zu unterscheiden.
Undo()
kann nur für Aktionen ab dem Beginn des Puffers bis einschließlich des Index
$iCurrentUndo
ausgeführt werden.
Redo()
kann nur für Aktionen nach dem Index
$iCurrentUndo
ausgeführt werden.
Wenn eine neue Aktion in den Puffer eingetragen wird, wird sie an nach dem Index
$iCurrentUndo
eingetragen und alle Aktionen nach dem Index
$iCurrentUndo
müssen verworfen werden.
Die Methoden
CanUndo()
und
CanRedo()
können verwendet werden, um die Schaltflächen
Undo und
Redo in der GUI zu aktivieren oder zu deaktivieren, je nachdem, ob der Puffer mit dem aktuellen Index
$iCurrentUndo
keine, eine oder beide Funktionen unterstützt.
' Gambas class file
Export
'' Buffer the actions. Any object, derived from **UndoRedoAction** is fine.
Private $aActions As UndoRedoAction[]
'' Store the usage of the undo-redo-buffer.
Private $iCount As Integer
'' Store the index of the current action.
Private $iCurrentUndo As Integer
'' Gets the number of actions, currently stored in the undo-redo-buffer.
Property Read Count As Integer
'' Checks whether the current position of undo-redo buffer represents a valid undo action.
Property Read CanUndo As Boolean
'' Checks whether the current position of undo-redo buffer represents a valid (re-) do action.
Property Read CanRedo As Boolean
'' Constructs a new undo-redo-buffer.
Public Sub _new()
$aActions = New UndoRedoAction[8]
$iCount = 0
$iCurrentUndo = -1
End
'' Released this undo-redo-buffer.
Public Sub _free()
For index As Integer = 0 To $iCount - 1
$aActions[index] = Null
Next
$aActions = Null
End
'' Counts the number of actions, registered to the buffer.
Private Function Count_Read() As Integer
Return $iCount
End
' Property implementation.
Private Function Count_Read() As Integer
Return $iCount
End
' Property implementation.
Private Function CanUndo_Read() As Boolean
Return ($iCurrentUndo >= 0) And ($iCurrentUndo < $iCount)
End
' Property implementation.
Private Function CanRedo_Read() As Boolean
Return ($iCurrentUndo < $iCount - 1)
End
'' Adds a new action to the buffer (it's Do() method is not called here).
'' Action will be added after the last undo action in the buffer.
'' Redo actions after the new action are removed.
''
'' The **action** represents an atomic operation that can be undone / redone.
Public Sub AddUndoAction(action As UndoRedoAction)
If action = Null Then Return
If $iCount >= $aActions.Length Then
$aActions.Resize($aActions.Length + 4)
Endif
While $iCount > $iCurrentUndo + 1
$iCount = $iCount - 1
$aActions[$iCount] = Null
Wend
$iCount = $iCount + 1
$iCurrentUndo = $iCurrentUndo + 1
$aActions[$iCurrentUndo] = action
End
'' Checks whether the current position of undo-redo buffer
'' represents a valid undo action
Public Function CanUndo() As Boolean
Return ($iCurrentUndo >= 0) And ($iCurrentUndo < $iCount)
End
'' Checks whether the current position of undo-redo buffer
'' represents a valid (re-) do action
Public Function CanRedo() As Boolean
Return ($iCurrentUndo < $iCount - 1)
End
'' Executes the undo action at the current position of undo-redo buffer
'' and moves the current position one step back
Public Function Undo() As Boolean
If CanUndo() <> True Then Return False
$aActions[$iCurrentUndo].Undo()
$iCurrentUndo = $iCurrentUndo - 1
Return bNeedsRedraw
End
'' Executes the (re-) do action at the current position of undo-redo buffer
'' and moves the current position one step forward
Public Function Redo() As Boolean
If CanRedo() <> True Then Return False
$iCurrentUndo = $iCurrentUndo + 1
$aActions[$iCurrentUndo].Do()
Return bNeedsRedraw
End
Code-Beispiel für eine anwendungsspezifische Aktion
Diese anwendungsspezifische Aktion ist sehr einfach (zu Demonstrationszwecken) und setzt die Hintergrundfarbe eines Steuerelements. Sie benötigt das Steuerelement und die Farbzustände vor und nach der
Do()
-Aktion.
' Gambas class file
Inherits UndoRedoAction
' Store the object, to apply the action to.
Private $oControl As Control
' Store the state before the **Do()** action.
Private $iOriginalState As Integer
' Store the state after the **Do()** action.
Private $iNewState As Integer
'' Constructs a new action.
Public Sub _new(controlValue As Control, originalState As Integer, newState As Integer)
$oControl = controlValue
$iOriginalState = originalState
$iNewState = newState
Super._SetName("ControlSetColor(" & $oControl.Name & ")")
End
'' Executes the do action.
Public Sub Do()
If $oControl <> Null Then
$oControl.Background = $iNewState
Endif
End
'' Executes the undo action.
Public Sub Undo()
If $oControl <> Null Then
$oControl.Background = $iOriginalState
Endif
End
Code-Beispiel für die Verwendung des Undo/Redo-Puffers
' Gambas class file
'' Provide the undo/redo buffer.
Private $oUndoRedoBuffer As UndoRedoBuffer = New UndoRedoBuffer
'' Help to create distinct actions.
Private $nCount As Integer = 0
'' Demonstrates how to create an action, call **Do()** and register the action to the undo/redo buffer.
Public Sub TestActions_Click()
Dim newColor As Integer
If $nCount % 4 = 0 Then
newColor = Color.SoftYellow
Else If $nCount % 4 = 1 Then
newColor = Color.SoftBlue
Else If $nCount % 4 = 2 Then
newColor = Color.Green
Else
newColor = Color.Red
Endif
Dim action As ControlSetColor_Action = New ControlSetColor_Action(Redo, Redo.Background, newColor, "SetBackgroundColor", True)
$oUndoRedoBuffer.AddUndoAction(action)
action.Do()
$nCount = $nCount + 1
End
'' Demonstrates the **Redo** functionality.
Public Sub btnRedo_Click()
If $oUndoRedoBuffer.CanRedo Then
If $oUndoRedoBuffer.Redo() Then
Draw(ctrlPictureBox.Image)
ctrlPictureBox.Refresh
Endif
Endif
btnUndo.Enabled = $oUndoRedoBuffer.CanUndo
btnRedo.Enabled = $oUndoRedoBuffer.CanRedo
End
'' Demonstrates the **Undo** functionality.
Public Sub btnUndo_Click()
If $oUndoRedoBuffer.CanUndo Then
If $oUndoRedoBuffer.Undo() Then
Draw(ctrlPictureBox.Image)
ctrlPictureBox.Refresh
Endif
Endif
btnUndo.Enabled = $oUndoRedoBuffer.CanUndo
btnRedo.Enabled = $oUndoRedoBuffer.CanRedo
End
Änderungen der Seite
Undo/redo Puffer von: Steffen Ploetz - 28. März 2024
- aktualisiert: 1. April 2024 von Steffen Ploetz
- Property "NeedsRedraw" hinzugefügt