Undo realisieren

jokey2

Erfahrenes Mitglied
Hallo Gemeinde!

Mal eine prinzipielle Frage: Wir realisiere ich am Besten ein Undo? Erstmal einstufig, das dann in mehrstufig ändern sollte nicht so schwierig sein. Gibt es da irgendwelche allgemeinen Vorgehensweisen, Datenstrukturen oder Hilfsmittel (z.B. in MFC oder SDK)?
Sonst müßte ich mir das alles selber ausdenken, wie sowas zu realisieren wäre. Ich hätte da zwar schon ein paar Vorstellungen, aber bevor ich das implementiere, wollte ich mich mal umhören, da ich ja nicht der Erste bin, der ein Undo programmiert.
 
Hat hier wirklich noch keiner sowas gemacht? Kann ich mir eigentlich nicht vorstellen... Ich will ja keine fertigen Funktionen oder irgendwelche Betriebsgeheimnisse, nur ein paar Anregungen.
 
Also fertige Sachen gibt es da nicht wirklich, nur die üblichen Ansätze.
Ich würde direkt das mehrstufige Undo angehen, die unterscheiden sich ja doch beträchtlich.

Ich habe ein schönes MFC-Zeichenprogramm gebaut und da (praktisch) unbegrenzte Undos eingebaut.

Ich habe einen UndoManager, der verwaltet zwei Listen, einmal für normale Undos und eine für Redos (STL eignet sich genial für sowas). Ein Redo ist der Einfachheit halber genau dasselbe wie ein Undo (später mehr dazu).

Dann gibt es eine Undo-Funktion-Basisklasse, die eine Gruppen-ID hat (um mehrere Undos zu einer Gruppe zusammenzufassen). Zusätzlich hat die Basisklasse eine Funktion, um ein Undo aus sich selbst zu erstellen (also ein Undo, das die Aktion des Undos rückgängig macht). Damit kann man bei einem Undo die Funktion durchführen, und das erzeugte Undo aus der Funktion als Redo in die Liste packen (bzw. umgekehrt bei einem Redo).

Wichtige Sache:
Undos müssen dann konsequent durchgezogen werden, entweder alle Funktion undo-bar oder keine. Sonst bekommst du Zustände, wo das Undo etwas rückgängig machen will, was es nicht mehr gibt.

Von der Undo-Basisklasse leiten sich dann die jeweiligen Undos für die unterschiedlichen Funktionen ab. Bei dem Malprogramm gibt es zum Beispiel ein Undo für Rechteckige Fläche verändern, die sich den originalen Bildausschnitt und dessen Koordinaten speichert. Das Redo-Erstellen hat dann eigentlich genau dasselbe gemacht, erstellt ein Undo für rechteckige Fläche und speichert den jetzigen Zustand an den Koordinaten. In die Liste packen, fertig.
 
Erstmal Danke für die Antwort!
Ich muß also für JEDE mögliche Aktion ein eigenes UNDO-Objekt haben, das den Zustand des geänderten Objektes vor der Aktion speichert. Wenn ich also z.B. aus einer Liste ein Element lösche, brauche ich ein UNDO_DELETE_LISTELEMENT, welches das gelöschte Element und eine Referenz auf die Liste enthält.
Ist das so richtig?
 
Ziemlich genau so. Eine praktische Idee: Du musst das eigentliche Element nicht immer wirklich löschen. Du kannst es aus der Liste nehmen, und in der jeweiligen Undo-Klasse speichern. Dann kannst du es ganz einfach "wiederherstellen"; es muss nur wieder in die Liste eingefügt werden.

Des öfteren sind die Undo/Redo-Paar ja auch umgekehrt einsetzbar:
Dein Beispiel, UNDO_DELETE_LISTELEMENT hat als Komplement ein UNDO_INSERT_LISTELEMENT.
Für die Funktion neues-Element-anlegen hast du ein UNDO_INSERT_LISTELEMENT mit dem Komplement UNDO_DELETE_LISTELEMENT.

Aua, bei den Bezeichnungen musst evtl. aufpassen bzw. konsequent sein, da das Undo meistens den Namen der Funktion bekommt, die es rückgängig machen soll (und nicht die Funktion, die es beim Undo dann tatsächlich ausführt).

[edit: Blöde Rechtschreibung, nicht reformieren, abschaffen sollte man die...mümmelmammel]
 
Mein Gott, was ein Stress!
Ich glaube, das UNDO kommt ganz ans Ende meiner ToDo-Liste! ;-)

[grummel]
Jetzt haben wir schon sooo intelligente Maschinen und man muß ihnen immer noch ganz genau auseinanderdröseln, was sie zu tun haben!
[/grummel]

Danke für Deine Hilfe, Endurion!
 
Nur nicht nervös werden!

In den meisten Fällen reicht es, wenn man sich für ein Undo/Redo den Zustand eines Objektes merkt und nicht die Aktion, die zur Veränderung oder deren Rückgängigmachung geführt hat. Das heisst, für viele Aktionen kannst du die selben Undos benutzen. Aber den Namen der Aktion solltest du dir merken! Den sollte man im Bearbeiten-Menü anzeigen! Mit diesem gespeicherten Zustand ist es einfach, den vorhergehenden Zustand wiederherzustellen.

Das Löschen ist in der Tat die kniffligste rückgängig zu machende Aktion.

Auch kannst du zur Übung erst einmal nur einen Teil der Funktionen undo-able machen (ja, ich weiss: Gefahr!). Mach dein Undo einfach abschaltbar, falls du gezwungen bist, zwischendurch schon Versionen rauszugeben. Undo ist komplex, und du wirst es nicht hinkriegen, ohne zwischendurch immer wieder zu testen.

@Endurion: Schön erklärt!
 
Von "Entwurfsmustern" von Gamma usw. (GoF) rate ich ab. Nun, ich habe schon einige Erfahrung mit Entwurfsmustern. Ich habe es mir selber vor einer Weile besorgt und muss sagen, dass es mir wirklich zu dröge und umständlich ist. Nichts für Einsteiger!
 
Kachelator hat gesagt.:
Von "Entwurfsmustern" von Gamma usw. (GoF) rate ich ab. Nun, ich habe schon einige Erfahrung mit Entwurfsmustern. Ich habe es mir selber vor einer Weile besorgt und muss sagen, dass es mir wirklich zu dröge und umständlich ist. Nichts für Einsteiger!
Ich habe auch so meine Erfahrungen mit Entwurfsmustern ;) und die sind sehr positiv.
Entwurfsmuster machen vieles zwar Anfangs scheinbar umständlich, aber wenn man so ein Gerüst implementiert hat, dann ist es herrlich einfach, seine Programme zu erweitern und den Überblick zu behalten.
Es ist eins der ganz wenigen Bücher (abgesehen von API-Referenzen) in das ich immer wieder reinschaue.
Ich fand das Buch übrigends sehr interessant.
Wer sich an ein Undo/Redo ranwagt, sollte weit genug vom "Anfängerstatus" entfernt sein, um Entwurfsmuster zu verstehen.

Gruß hpvw
 
Zurück