一個簡單的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

    }

 

在我的資源中有整個項目的代碼。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章