2

I'm getting kind of confused right now, having one of those days I guess.

I need to implement an Undo and Redo functionality for a form. For simplicities sake, let's say that I only save the control which was modified and the value it had when it left Focus.

How do I save this information in a way that lets me go back or forth in the 'timeline'.

I thought about using a Stack, but while I was testing my little demo, I had a mild aneurysm and here I am.

Code needed, not really but would help. I'm more interested in the algorithm I'd need to implement. Any suggestions?

Only Bolivian Here
  • 35,719
  • 63
  • 161
  • 257

4 Answers4

4

A stack is perfect if you push a "change" onto it, and when undo pop a "change" from it. You then push that popped change into another stack representing redo. At some point in the future, hopefully on save, you clear both stacks.

It's not actually as simple as that, as you need to record the type of change, understand the old and new values etc. So when you pop from the undo stack, the thing you pop must describe what the prior value was and what control it was set to.

Inverse for the redo stack, it needs to understand what the new value was and where it went. But yes, the idea of two stacks is a good start for a homebrew undo-redo.

A good example of a business object based undo is CSLA.NET, which has UndoableBase:

http://www.lhotka.net/cslanet/

http://www.koders.com/csharp/fidCF6AB2CF035B830FF6E40AA22C8AE7B135BE1FC0.aspx?s=serializationinfo

However this records a snapshot of an object's state, so it would be more advanced that your form-based concept. However, CSLA.NET offers full data binding support so a data bound object inheriting from UndoableBase would naturally support undo (not redo) in the UI.

Adam Houldsworth
  • 63,413
  • 11
  • 150
  • 187
  • @Sergio Yes as you do changes you push it on to the undo stack, as people undo operations you pop from the undo stack and push on the redo stack. If they do any operations other than redo you clear the redo stack as you could get invalid states. – Scott Chamberlain Jul 28 '11 at 16:31
  • @Adam, I don't like the idea of two stacks. Do you not think this would have issue when the user makes a "new change" after an undo. I think at this point the redo list would be cleared. Therefore, I would personally try it with a list and a pointer – musefan Jul 28 '11 at 16:33
  • 2
    @musefan I think by this point it becomes a matter of personal taste. Stacks are a perfectly valid choice of container, you don't need to understand where you are with them. A list is also a good choice, but you get context from understanding where in that list you currently are. – Adam Houldsworth Jul 28 '11 at 16:35
  • 1
    I've done it both ways, and I prefer using two stacks. When the user does something new and you need to clear the redo stack, `RedoStack.Clear()` is much simpler, more readable, and more obviously correct than `while (UndoList.Count > UndoPointer) UndoList.RemoveAt(UndoList.Count - 1);`. It also makes it easy to enable and disable the Undo and Redo buttons -- CanUndo is as simple as `UndoStack.Any()`, CanRedo is `RedoStack.Any()`. – Joe White Jul 28 '11 at 17:25
  • @Joe, Good points - I will try to keep this all in mind for when the need arises – musefan Jul 29 '11 at 08:48
4

Yes, you would use a stack. There are a couple ways to do it; read these references:

http://en.wikipedia.org/wiki/Command_pattern

http://en.wikipedia.org/wiki/Memento_pattern

Each has its pros/cons.

James Johnston
  • 9,264
  • 9
  • 48
  • 76
  • 1
    I think for my use case (UI values changing and whatnot) using the Memento pattern makes more sense. Plus I loved the movie and will feel good while coding. – Only Bolivian Here Jul 28 '11 at 16:54
  • I really prefer the Command pattern for UI´s for various reasons (e.g. less memory, better seperation of UI from Data-model and the ability to merge multiple value changes into a single step... Also it reduces the coupling between UI and Data...) But this may be just a personal preference. – sanosdole Jul 28 '11 at 17:26
  • Yup, it's a great movie! :) Memento is easy to implement since you just have to duplicate the data model and not make command objects. But it's not always an option if the data set is large (memory constraints). On the other hand, if the time to execute each command is long, then memento could be the best choice. In your application I'm guessing it doesn't matter unless you're making an image editor, for example. – James Johnston Jul 28 '11 at 18:53
4

I would use an IUndoableAction interface. The implementations could store whatever data they needed to be done and undone. Then yes, I would use a Stack to hold them.

interface IUndoableAction
{
    void Do();
    void Undo();
}
Stack<IUndoableAction> Actions;

Each kind of action would implement the Do and Undo methods.

Then, somewhere there would be these two methods:

    void PerformAction(IUndoableActionaction)
    {
        Actions.Push(action);
        action.Do();
    }

    void Undo()
    {
        var action = Actions.Pop();
        action.Undo();
    }

As for what to store in the action classes, some actions could just store the old value. However, once I had an action to swap two rows in a spreadsheet. I didn't store the values of every cell in both rows -- I just stored the row indices so they could be swapped back. It could be easy to fill up tons of memory if you stored all of that state for every action.

Then you want a Redo stack as well, and when you undo an action it is pushed onto the redo stack. The redo stack will need to be cleared when a new action is performed, so things don't get out of order.

Philip
  • 694
  • 5
  • 14
0

Probably the most straightforward is to have the undo/redo stack combination.

An alternative is to have an array or list of actions, and just increment/decrement a pointer to an index in the array. When an action is undone, the index is moved back by one, and when the action is redone, the index is moved forward by one. The advantage here is that you don't require a pop-and-then-push sequence for each action.

Things to consider:

  • If you undo several times, and then perform an action, all of the redo actions must be eliminated.
  • Make sure you check the boundaries and ensure that there is an action available to undo/redo before trying to perform the undo/redo.
user807566
  • 2,828
  • 3
  • 20
  • 27