一个简单的Undo Redo Framework

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

    }

 

在我的资源中有整个项目的代码。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值