undo-Funktion - gelöst

Anfängerfragen zum Programmieren mit PureBasic.
Benutzeravatar
uweb
Beiträge: 461
Registriert: 13.07.2005 08:39

undo-Funktion - gelöst

Beitrag von uweb »

Ich denke gerade darüber nach wie ich am besten eine Rückgängig/Wiederholen- oder Zurück/Vor-Funktion realisieren könnte.
Meine beste Idee ist ein Array und Proceduren die beim erreichen einer Grenze am anderen Ende weiter lesen/schreiben.
An Variablen brächte ich wohl : AktuellePosition , MaximalVor und MaximalZurück.
Allerdings scheint es mir sehr schwierig wenn der Anwender nach zehn Schritten z.B. fünf zurück geht und dann drei neue Schritte vor macht.

Hat da jemand eine bessere Idee, einen Link oder vielleicht schon eine fertige Lösung dafür?

Ich frage lieber bevor ich mich da rein stürze. Vielleicht ist ja der Ansatz schon falsch.
Nach "FiFo" und "undo" habe ich schon erfolglos gesucht.
Zuletzt geändert von uweb am 25.09.2009 19:31, insgesamt 1-mal geändert.
Benutzeravatar
ts-soft
Beiträge: 22292
Registriert: 08.09.2004 00:57
Computerausstattung: Mainboard: MSI 970A-G43
CPU: AMD FX-6300 Six-Core Processor
GraKa: GeForce GTX 750 Ti, 2 GB
Memory: 16 GB DDR3-1600 - Dual Channel
Wohnort: Berlin

Re: undo-Funktion

Beitrag von ts-soft »

Falls es für das EditorGadget oder Scintilla sein sollte, dort ist sowas nativ schon integriert.
Benutzeravatar
Fluid Byte
Beiträge: 3110
Registriert: 27.09.2006 22:06
Wohnort: Berlin, Mitte

Re: undo-Funktion

Beitrag von Fluid Byte »

Hat da jemand eine bessere Idee, einen Link oder vielleicht schon eine fertige Lösung dafür?
Das kommt auf die Anwendung an. Was für ein Programm bastelst du denn grad'?
Windows 10 Pro, 64-Bit / Outtakes | Derek
Benutzeravatar
uweb
Beiträge: 461
Registriert: 13.07.2005 08:39

Re: undo-Funktion

Beitrag von uweb »

Danke für die schnellen Antworten.

Code: Alles auswählen

WindowWidth  = 640
WindowHeight = 480
Distance = 10
ToolBarHeight = 10
ComboBoxHeight = 20

Enumeration ; Windows & StatusBars
  #myWindow
  #myStatusBar
EndEnumeration
Enumeration ; Gadgets
  #myComboBoxGadget
  #myExplorerTreeGadget
  #myExplorerListGadget
  #mySplitterGadget
EndEnumeration

WindowFlags | #PB_Window_SizeGadget
WindowFlags | #PB_Window_SystemMenu
WindowFlags | #PB_Window_ScreenCentered

Macro ComboBoxY
  ToolBarHeight+Distance
EndMacro

Macro BodyY
  ToolBarHeight+ComboBoxHeight+2*Distance
EndMacro

Macro BodyHeight
  WindowHeight(#myWindow)-(BodyY+StatusBarHeight(#myStatusBar)+Distance)
EndMacro

If OpenWindow(#myWindow, #PB_Ignore, #PB_Ignore, WindowWidth, WindowHeight, "Menü Test", WindowFlags)
 
  CreateStatusBar(#myStatusBar, WindowID(#myWindow))
  AddStatusBarField(WindowWidth/2) 
  AddStatusBarField(#PB_Ignore) ; Größe dieses Feldes automatisch festlegen

  ComboBoxGadget(#myComboBoxGadget, 0, ComboBoxY, WindowWidth(#myWindow), ComboBoxHeight, #PB_ComboBox_Editable)

  ExplorerTreeGadget(#myExplorerTreeGadget, 0, BodyY, 0, 0, "*.wf;*.wfx",#PB_Explorer_AlwaysShowSelection|#PB_Explorer_AutoSort)
  ExplorerListGadget(#myExplorerListGadget, 0, BodyY, 0, 0, "C:\QMSystems\", #PB_Explorer_AlwaysShowSelection|#PB_Explorer_FullRowSelect|#PB_Explorer_MultiSelect)
  SplitterGadget(#mySplitterGadget, 0, BodyY, WindowWidth(#myWindow), BodyHeight, #myExplorerTreeGadget, #myExplorerListGadget, #PB_Splitter_Vertical|#PB_Splitter_Separator)
 
  StatusBarText(#myStatusBar, 0, "Area 0")
  StatusBarText(#myStatusBar, 1, "Area 1")
  
  LastFolder.s = GetGadgetText(#myExplorerListGadget)
  SetGadgetText(#myComboBoxGadget,LastFolder.s)
  SetGadgetText(#myExplorerTreeGadget,LastFolder.s)
  
  Repeat
   
    Event = WaitWindowEvent()
    Select Event
   
    Case  #PB_Event_Gadget

    Select EventGadget()

      Case #myExplorerTreeGadget ; EventGadget()

        Select EventType()
          Case #PB_EventType_Change ; #myExplorerTreeGadget - EventType()
              StatusBarText(#myStatusBar, 0, LastFolder.s)
              LastFolder.s = GetGadgetText(#myExplorerTreeGadget)
              StatusBarText(#myStatusBar, 1, LastFolder.s)
            SetGadgetText(#myComboBoxGadget,LastFolder.s)
            SetGadgetText(#myExplorerListGadget,LastFolder.s)
        EndSelect

      Case #myExplorerListGadget ; EventGadget()

        Select EventType()
          Case #PB_EventType_Change ; #myExplorerListGadget - EventType()
            If LastFolder.s <> GetGadgetText(#myExplorerListGadget)
              StatusBarText(#myStatusBar, 0, LastFolder.s)
              LastFolder.s = GetGadgetText(#myExplorerListGadget)
              StatusBarText(#myStatusBar, 1,LastFolder.s)
              SetGadgetText(#myComboBoxGadget,LastFolder.s)
              SetGadgetText(#myExplorerTreeGadget,LastFolder.s)            
            EndIf

        EndSelect

    EndSelect
     
    Case  #PB_Event_SizeWindow ; WindowEvent()
      ResizeGadget(#mySplitterGadget, #PB_Ignore, #PB_Ignore, WindowWidth(#myWindow), BodyHeight) ; Our 'master' splitter gadget
      ; die beiden im SplitterGadget liegenden Gadgets benötigen kein Resize
      ; das erledigt das SplitterGadget automatisch.
      ResizeGadget(#myComboBoxGadget, #PB_Ignore, #PB_Ignore, WindowWidth(#myWindow), #PB_Ignore) 
    
    EndSelect
  Until Event = #PB_Event_CloseWindow
 
EndIf

End
Im Moment stehe ich wieder ganz am Anfang und muß halt wieder kleine Brötchen backen.
:-)

Ich hätte vermutet, daß es da eine universelle Datenstruktur bzw einen universellen Algorithmus gibt.
Welche Lösungen gibt es denn? Worin unterscheiden die sich?

edit
code leicht verändert
& Ich habe eben festegestellt, daß es schon schwierig ist LastFolder.s zu pflegen wenn das Verzeichnis über das ExplorerListGadget gewechselt wird.
Little John

Re: undo-Funktion

Beitrag von Little John »

uweb hat geschrieben:Nach "FiFo" und "undo" habe ich schon erfolglos gesucht.
FiFo (First In, First Out) beschreibt ja das Prinzip der Schlange. Ich würde hingegen eine Undo-Funktion mit Hilfe der Datenstruktur Stack (= Stapel) realisieren (Last In, First Out). Das ist eigentlich logisch: Wenn man etwas rückgängig machen möchte, braucht man zunächst wieder das, was man zuletzt gemacht hatte. Technisch kann man einen Stapel in PB in Form eines Arrays oder einer Linked List implementieren.

Wundert mich also nicht sooooooo, dass eine Suche nach "FiFo" und "undo" nicht der Bringer ist. ;-)
Ich selbst habe sowas noch nie gemacht, aber ich finde das Thema interessant.

Für eine Redo-Funktion hatte ich keine gute Idee, aber eine Google Suche nach
Datenstruktur undo redo
brachte was:
Zusätzlich zur Undo-Funktion ist es auch nicht weiter schwer, eine Redo-Funktion zu
implementieren. Dazu muss der Command Processor nur einen weiteren Stack, den
Redo-Stack, einführen. Immer wenn die „Undo“-Methode des Command Processor
aufgerufen wird, wird dann nicht einfach der oberste Befehl vom Undo-Stack
entfernt, sondern auf den Redo-Stack gelegt. Wenn die „Redo“-Methode des
Command Processor aufgerufen wird, wird die „FuehreAus“-Methode des obersten
Befehls des Redo-Stack ausgeführt und dieser Befehl wird wieder vom Redo-Stack
auf den Undo-Stack gelegt.
<http://bis.informatik.uni-leipzig.de/de ... mmando.pdf>, S. 6

Klingt logisch, finde ich. :-)

Gruß, Little John
Benutzeravatar
uweb
Beiträge: 461
Registriert: 13.07.2005 08:39

Re: undo-Funktion

Beitrag von uweb »

Super, Danke!

FiFo / LiFo - wusste ich doch immerhin noch, daß es da einen Fo gibt. :-)
Zum FiFo war die Assoziation einfach größer.

Ich mache mich gleich daran wenn ich das Problem mit dem ExplorerListGadget gelöst habe.
Dazu mache ich wohl besser einen eigenen Post auf sonst geht das hier unter.
Little John

Re: undo-Funktion

Beitrag von Little John »

Schön dass es das war, was Du suchtest!
uweb hat geschrieben:Zum FiFo war die Assoziation einfach größer.
FiFo klingt für mich auch vertrauter. Könnte bei mir daran liegen, dass mein Lieblingsbrowser der FireFox ist. ;-)

Gruß, Li Jo
Benutzeravatar
Froggerprogger
Badmin
Beiträge: 855
Registriert: 08.09.2004 20:02

Re: undo-Funktion - gelöst

Beitrag von Froggerprogger »

Ich hätte vermutet, daß es da eine universelle Datenstruktur bzw einen universellen Algorithmus gibt.
Gibt es. Solche "komplexen Standard-Konzepte" nennen sich "Design Pattern" (=> Google).

Hab über Undo/Redo unabhängig davon auch schonmal nachgedacht: Ich würde jede Aktion in ein "invertierbares Aktionsobjekt" kapseln, also ein Aktionsobjekt, dass man anwenden kann, aber auch wieder rückgängig machen. Sofern ein solches Konzept konsequent eingebaut ist, braucht man eine Aktion nur noch an die Aktionsqueue zu schicken. Dafür genügt eine einfache Liste, in der man sich vor- und zurückbewegt, mit der speziellen Eigenschaft, dass: Sobald man mehrere Dinge Undo gemacht hat, und dann eine *neue* Aktion ausführt, wird zuvor alles, was man per Redo noch hätte erreichen können, endgültig gelöscht.

Ist das klar, was ich schreibe? Oder diffus? (Hab schon Wein getrunken und geh gleich pennen... ;) )
!UD2
Benutzeravatar
uweb
Beiträge: 461
Registriert: 13.07.2005 08:39

Re: undo-Funktion - gelöst

Beitrag von uweb »

diffus
das muß aber nicht an dir liegen und ist immer noch besser als totale dunkelheit
danke
Benutzeravatar
Froggerprogger
Badmin
Beiträge: 855
Registriert: 08.09.2004 20:02

Re: undo-Funktion - gelöst

Beitrag von Froggerprogger »

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.
!UD2
Antworten