Provide undo and redo in C#

[undo]

This example adds undo and redo features to the example Save and restore pictures drawn by the user in C#. It’s not terribly hard, but it is long so I’ll break it into sections to make it easier to understand.

That example uses XML to serialize and deserialize pictures drawn by the user. Once you can serialize and deserialize the data used by an application, providing undo and redo is relatively easy. Whenever the user makes a change, you can serialize the data to make a snapshot. To undo a change you restore the most recent snapshot. To redo, you reapply the snapshot you took before the undo.


Saving Snapshots

This example uses the following undo and redo lists.

// The undo and redo history lists.
private Stack<string> UndoList = new Stack<string>();
private Stack<string> RedoList = new Stack<string>();

Whenever the user makes a change, the program calls the following SaveSnapshot method to save a snapshot of the current data.

// Save a snapshot in the undo list.
private void SaveSnapshot()
{
    // Save the snapshot.
    UndoList.Push(GetSnapshot());

    // Empty the redo list.
    if (RedoList.Count > 0) RedoList = new Stack();

    // Enable or disable the Undo and Redo menu items.
    EnableUndo();
}

This code calls the GetSnapshot method to get a serialization of the data. (That method simply serializes the data. See the previous post for details about how it works.) It pushes that serialization onto the UndoList.

If the RedoList is not empty, the code creates a new empty RedoList. (If the user undoes some changes, they are saved in the RedoList so the user can reapply them. If the user then makes a change, this code discards the RedoList snapshots.)

This method finishes by calling the following EnableUndo method.

// Enable or disable the Undo and Redo menu items.
private void EnableUndo()
{
    mnuEditUndo.Enabled = (UndoList.Count > 0);
    mnuEditRedo.Enabled = (RedoList.Count > 0);
}

This method simply enables the undo or redo lists if there are snapshots in the corresponding lists.


Undo

The following code shows how the program undoes a change.

// Undo.
private void mnuEditUndo_Click(object sender, EventArgs e)
{
    // Move the most recent change to the redo list.
    RedoList.Push(UndoList.Pop());

    // Restore the top item in the Undo list.
    RestoreTopUndoItem();

    // Enable or disable the Undo and Redo menu items.
    EnableUndo();
}

When the user selects the Undo menu item, this code pops the most recent serialization from the undo list and pushes it onto the redo list. It then calls the following RestoreTopUndoItem method to restore the previous serialization and finishes by calling EnableUndo to enable the appropriate menu undo and redo items.

// Use an XML serialization to load a drawing.
private void RestoreTopUndoItem()
{
    if (UndoList.Count == 0)
    {
        // The undo list is empty. Display a blank drawing.
        Polylines = new List<Polyline>();
    }
    else
    {
        // Restore the first serialization from the undo list.
        XmlSerializer xml_serializer =
            new XmlSerializer(Polylines.GetType());
        using (StringReader string_reader =
            new StringReader(UndoList.Peek()))
        {
            List<Polyline> new_polylines =
                (List<Polyline>)
                    xml_serializer.Deserialize(string_reader);
            Polylines = new_polylines;
        }
    }
    picCanvas.Refresh();
}

This code uses the undo list’s Peek method to get the most recent serialization without removing it from the list. The code then deserializes it and displays the result.


Redo

The following code shows how the program reapplies an undone change.

// Redo.
private void mnuEditRedo_Click(object sender, EventArgs e)
{
    // Move the most recently undone item back to the undo list.
    UndoList.Push(RedoList.Pop());

    // Restore the top item in the Undo list.
    RestoreTopUndoItem();

    // Enable or disable the Undo and Redo menu items.
    EnableUndo();
}

This code removes the most recent serialization from the redo list (which is the serialization that was most recently undone) and pushes it back onto the undo list. It then calls RestoreTopUndoItem. That method restores the top serialization in the undo list, so it reapplies this serialization.

That’s all there is to it. As I said at the beginning, it’s not really all that complicated. Most of the real work is done by the code that does serialization and deserialization. See the previous example for information about how that works.


Download Example   Follow me on Twitter   RSS feed   Donate




About RodStephens

Rod Stephens is a software consultant and author who has written more than 30 books and 250 magazine articles covering C#, Visual Basic, Visual Basic for Applications, Delphi, and Java.
This entry was posted in drawing, graphics, serialization and tagged , , , , , , , , , , , , , , , , , , , , , , , , , , , , . Bookmark the permalink.

4 Responses to Provide undo and redo in C#

  1. Pingback: Provide autosave in C# - C# HelperC# Helper

  2. Aso says:

    Dear Sir
    You are greate
    I do appritiate all your coding…
    I do have a question:
    Is it possible to save the list to a database and then retrive it again to our pciture box (redraw it) instead of serialization, which can be lost on our computer…
    I do appritiate you answer

    • RodStephens says:

      You can do that, but it will be relatively slow because the serializations can be fairly large and database access isn’t terribly fast. In fact ADO.NET (the latest .NET DB tool) only loads and saves data in batches. It’s not really designed to save many changes frequently.

      What I would do is save a snapshot every now and then. Perhaps every 10 or 20 changes, or perhaps every few minutes. That way if the program crashes, you can restore most of the picture.

      I would probably store those backups in text files rather than the database so they will be easier to manage. You could store a picture’s backup in a file with the same name and a .bak extension and then overwrite it whenever you save a new backup. The user can then open that file with the program if necessary. (You might also want the program to look for a .bak file when you start, see if it is newer than the most recent version of the file, and let the user revert to that version if desired.)

      I would still store the undo and redo lists within the program. That’s much faster and won’t fragment the database.

      (In fact, this whole thing might make a good example. I’ll look more at it when I have the time.)

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.