Getting started
editInstall the package
Add the Meziantou.Framework.UndoRedo package to a project:
dotnet package add Meziantou.Framework.UndoRedoThe examples of this documentation omit the using directives: they need using Meziantou.Framework.UndoRedo;.
Record, undo and redo
Create one UndoRedoManager for the document the user edits, and record each change with the code that applies it and the code that reverts it:
var manager = new UndoRedoManager();
var document = new List<string>();
// Each recorded action runs immediately and becomes one undo step
await manager.RecordActionAsync(
execute: () => document.Add("Hello"),
unexecute: () => document.RemoveAt(document.Count - 1));
await manager.RecordActionAsync(
execute: () => document.Add("World"),
unexecute: () => document.RemoveAt(document.Count - 1));
Assert.Equal(["Hello", "World"], document);
await manager.UndoAsync();
Assert.Equal(["Hello"], document);
Assert.True(manager.CanUndo);
Assert.True(manager.CanRedo);
// Recording a new action clears the redo history
await manager.RecordActionAsync(
execute: () => document.Add("Everyone"),
unexecute: () => document.RemoveAt(document.Count - 1));
Assert.Equal(["Hello", "Everyone"], document);
Assert.False(manager.CanRedo);- RecordActionAsync applies the change by calling
execute, then adds it to the undo history. Whenexecutethrows, nothing is recorded. - UndoAsync reverts the most recent change by calling
unexecute, and moves it to the redo history. RedoAsync applies it again. Both do nothing when the history is empty. - CanUndo and CanRedo tell whether UndoAsync and RedoAsync would do something.
- Recording a change after an undo clears the redo history, as in most editors.
- Clear empties both histories, for instance after the document is saved or closed.
Asynchronous changes
execute and unexecute can be asynchronous: they receive the CancellationToken passed to RecordActionAsync, UndoAsync or RedoAsync. A synchronous and an asynchronous delegate can be mixed:
var manager = new UndoRedoManager();
var items = new List<string>();
// Asynchronous execute, synchronous unexecute
var action = new UndoRedoDelegateAction(
execute: async cancellationToken =>
{
await Task.Delay(TimeSpan.FromMilliseconds(1), cancellationToken);
items.Add("item");
},
unexecute: () => items.Remove("item"));
await manager.RecordActionAsync(action);
Assert.Equal(["item"], items);UndoRedoDelegateAction is the action the delegate overloads of RecordActionAsync create. Create it yourself to set its Description or AllowToMergeWithPrevious.
A token that is already canceled makes RecordActionAsync, UndoAsync and RedoAsync throw an OperationCanceledException before anything runs.
Next steps
- Actions: write reusable action classes.
- Transactions: make several changes a single undo step.