下面的代碼構建了一個實現撤銷和重做功能的框架。實現非常簡單,只有三個類。ICommand類定義了一個可以重做和撤銷的命令所需要實現的接口。CompositeCommand類實現了該類,封裝了將一組Command作爲單一命令撤銷和重做的功能,這是一個Composite模式的一個簡單應用;該類還支持是否將子命令作爲一個事務來執行。CommandManager類是其中最關鍵的一個類,不過仍然非常簡單,它包含了兩個棧用來存放可以重做的命令和可以撤銷的命令。需要注意的是,當執行一個新的命令的時候,需要調用ExecuteCommand方法。撤銷和重做已經添加到列表中的命令只需要調用Redo和Undo方法。因爲如果在一個命令裏調用Redo和Undo以及ExecuteCommand方法會引起混亂。所以CommandManager會檢測這種調用,並拋出異常。除了這三個類以外,還定義了一個可以複用的DelegateCommand的類。如果需要重做和撤銷的命令比較簡單,就不用自己定義命令類,而是複用此類。在MainWindow中對這些類進行了測試。代碼的註釋裏提供了更多的細節。
ICommand接口定義:
///<summary>
/// Defines a command.
/// </summary>
/// <remarks>
/// Define a command whenever we need an operation can beundoable and redoable.
/// </remarks>
public interface ICommand
{
/// <summary>
/// Define the method to be called when the command is invokedor redone by a CommandManager instance.
/// </summary>
/// <remarks>
/// The method is defined for CommandManager use. It shouldnot be called by client code.
/// </remarks>
voidExecute();
/// <summary>
/// Define the method to be called when the command is undoneby a CommandManager instance.
/// </summary>
/// <remarks>
/// The method is defined for CommandManager use. It shouldnot be called by client code.
/// </remarks>
voidUndoExecute();
}
CompositeCommand類的定義:
///<summary>
/// Define a composite command which can contain other commandobjects.
/// </summary>
/// <remarks>
/// Define a composite command if you want many commands canbe executed, redone, undone together.
/// </remarks>
public class CompositeCommand: IList<ICommand>,ICommand
{
privatereadonly List<ICommand> _commands = newList<ICommand>();
privatereadonly bool_isTransactional;
/// <summary>
/// Get whether the current CompositeCommand instance will beexecuted in a transaction.
/// </summary>
public bool IsTransactional
{
get{ return _isTransactional; }
}
/// <summary>
/// Initialize a new instance of the CompositeCommand classwith IsTransactional set to true.
/// </summary>
publicCompositeCommand()
: this(true)
{ }
/// <summary>
/// Initialize a new instance of the CompositeCommand class.
/// </summary>
/// <paramname="isTransaction">Indicateswhether the command will be executed as a transaction.</param>
publicCompositeCommand(bool isTransaction)
{
_isTransactional = isTransaction;
}
#region ICommand Members
/// <summary>
/// Execute all child commands.
/// </summary>
/// <remarks>
/// If IsTransactional property is true, the child commandswill be executed in a transaction.
/// That means if any of the child command throws anexception, the child commands which have already
/// executed will be rollback. Their UndoExecute methods willbe executed inversely.
/// </remarks>
public void Execute()
{
Stack<ICommand> executedCmds = new Stack<ICommand>();
for(int i = 0; i < _commands.Count; i++)
{
try
{
_commands[i].Execute();
executedCmds.Push(_commands[i]);
}
catch
{
if(_isTransactional)
{
while (executedCmds.Count > 0)
{
ICommand undoCmd = executedCmds.Pop();
undoCmd.UndoExecute();
}
}
throw;
}
}
}
/// <summary>
/// Undo all child commands in an inverse order to Executemethods.
/// </summary>
/// <remarks>
/// If IsTransactional property is true, the child commandswill be executed in a transaction.
/// That means if any of the child command throws anexception, the child commands which have already
/// undone will be redone again. Their Execute methods will beexecuted inversely.
/// </remarks>
public void UndoExecute()
{
Stack<ICommand> undoedCmds = newStack<ICommand>();
for (int i = _commands.Count - 1; i >= 0; i--)
{
try
{
_commands[i].UndoExecute();
undoedCmds.Push(_commands[i]);
}
catch
{
if(_isTransactional)
{
while (undoedCmds.Count > 0)
{
ICommand redoCmd = undoedCmds.Pop();
redoCmd.Execute();
}
}
throw;
}
}
}
#endregion
#region IList<ICommand>Members
public int IndexOf(ICommanditem)
{
return_commands.IndexOf(item);
}
public void Insert(intindex, ICommand childCommand)
{
ThrowCommandArgumentNullException(childCommand);
_commands.Insert(index,childCommand);
}
public void RemoveAt(intindex)
{
_commands.RemoveAt(index);
}
public ICommand this[int index]
{
get
{
return_commands[index];
}
set
{
ThrowCommandArgumentNullException(value);
_commands[index] = value;
}
}
#endregion
#region ICollection<ICommand>Members
public void Add(ICommandcommand)
{
ThrowCommandArgumentNullException(command);
_commands.Add(command);
}
public void Clear()
{
_commands.Clear();
}
public bool Contains(ICommandcommand)
{
return_commands.Contains(command);
}
/// <summary>
/// Don't want this member showed in IntelliSense
/// </summary>
void ICollection<ICommand>.CopyTo(ICommand[] array, intarrayIndex)
{
_commands.CopyTo(array,arrayIndex);
}
public int Count
{
get{ return _commands.Count; }
}
/// <summary>
/// Don't want this member showed in IntelliSense
/// </summary>
bool ICollection<ICommand>.IsReadOnly
{
get{ return false;}
}
public bool Remove(ICommandcommand)
{
return_commands.Remove(command);
}
#endregion
#region IEnumerable<ICommand>Members
public IEnumerator<ICommand>GetEnumerator()
{
return_commands.GetEnumerator();
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return_commands.GetEnumerator();
}
#endregion
privatevoid ThrowCommandArgumentNullException(ICommand command)
{
if(command == null)
{
thrownew ArgumentNullException();
}
}
}
CommandManager類的定義:
///<summary>
/// Manages a list of commands and provides Redo and Undofunctions.
/// </summary>
public class CommandManager
{
privateStack<ICommand>_undoList = new Stack<ICommand>();
privateStack<ICommand>_redoList = new Stack<ICommand>();
privatebool _commandIsExecuting = false;
/// <summary>
/// Redo current command in redo command list.
/// </summary>
/// <remarks>
/// The current Redo-Command would never goto the Undo-Commandlist if the command throws an exception.
/// </remarks>
public void Redo()
{
if(CanRedo())
{
ThrowCommandIsExecutingException();
ICommandredoCmd = _redoList.Peek();
_commandIsExecuting = true;
try
{
redoCmd.Execute();
_redoList.Pop();
_undoList.Push(redoCmd);
}
finally
{
_commandIsExecuting = false;
}
}
}
/// <summary>
/// Undo current command in undo command list.
/// </summary>
/// <remarks>
/// The current Undo-Command would never goto the Redo-Commandlist if the command throws an exception.
/// </remarks>
public void Undo()
{
if(CanUndo())
{
ThrowCommandIsExecutingException();
ICommandundoCmd = _undoList.Peek();
_commandIsExecuting = true;
try
{
undoCmd.UndoExecute();
_undoList.Pop();
_redoList.Push(undoCmd);
}
finally
{
_commandIsExecuting = false;
}
}
}
/// <summary>
/// Get if there exists any command in redo command list.
/// </summary>
/// <returns>
/// true if there are redo commands in redo list, otherwisefalse.
/// </returns>
public bool CanRedo()
{
return_redoList.Count != 0;
}
/// <summary>
/// Get if there exists any command in undo command list.
/// </summary>
/// <returns>
/// true if there are undo commands in undo list, otherwisefalse.
/// </returns>
public bool CanUndo()
{
return_undoList.Count != 0;
}
/// <summary>
/// Execute a command, and put this command to undo list ifthe command is executed successfully.
/// </summary>
/// <paramname="command">The command thatwill be executed.</param>
/// <remarks>
/// Shouldn't catch exceptions in command's Execute andUndoExecute method, because we need the exception
/// to know if the command is executed or un-executedsuccessfully. If it is executed successfully, the command
/// will be push to undo-stack and the redo-list will becleared, otherwise we will do nothing.
/// </remarks>
public void ExecuteCommand(ICommandcommand)
{
ThrowCommandIsExecutingException();
command.Execute();
_undoList.Push(command);
_redoList.Clear();
}
privatevoid ThrowCommandIsExecutingException()
{
if(_commandIsExecuting)
{
thrownew InvalidOperationException("Can't access CommandManger while Command isexecuting.");
}
}
}
DelegateCommand類的定義:
public class DelegateCommand: ICommand
{
publicDelegateCommand(Action executeDelegate, Action undoExecuteDelegate)
{
ExecuteDelegate = executeDelegate;
UndoExecuteDelegate =undoExecuteDelegate;
}
publicDelegateCommand()
{
}
public Action ExecuteDelegate { get;set; }
public Action UndoExecuteDelegate { get; set; }
#region ICommand Members
public void Execute()
{
if(ExecuteDelegate != null)
{
ExecuteDelegate();
}
}
public void UndoExecute()
{
if(UndoExecuteDelegate != null)
{
UndoExecuteDelegate();
}
}
#endregion
}
在我的資源中有整個項目的代碼。