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