Title: Provide undo and redo in C#
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 the example to experiment with it and to see additional details.
|