下面的代码构建了一个实现撤销和重做功能的框架。实现非常简单,只有三个类。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
}
在我的资源中有整个项目的代码。