zoukankan      html  css  js  c++  java
  • 推荐:介绍一个UndoFramework

      由于其他工作,好多天又没有对MetaModelEngine进行思考了,这两天又腾出时间可以思考一下了,本篇介绍一下在图形编辑器中对操作如何实现Undo操作。

      在图形设计器操作中,每个操作按钮都对应到一个命令,很多情况下我们都应该允许用户执行操作后回滚这些操作,或者回滚后又再次执行。在我做的报表引擎中,我是在每次操作后把设计文件都保留下来,这个在报表设计中是没有问题,但是在毕竟不是很好的设计。接下来要考虑对OpenExpressApp提供建模支持了,所以也需要考虑一下如何让图形设计器更好的支持这种Undo操作。

      在公司的一个项目组中内部是使用命令模式,只是在传统命令模式中增加了一个UnExecute方法,这个方法就是用来做Undo操作的。在codeplex上我找到了一类似的轻量级UndoFramework,后期准备就用它了,在这里我就给大家介绍一下。

    UndoFramework项目

    Codeplex网站地址:http://undo.codeplex.com/

    下载地址:http://undo.codeplex.com/releases/view/29440

    项目描述:

      It's a simple framework to add Undo/Redo functionality to your applications, based on the classical Command design pattern. It supports merging actions, nested transactions, delayed execution (execution on top-level transaction commit) and possible non-linear undo history (where you can have a choice of multiple actions to redo).

      The status of the project is Stable (released). I might add more stuff to it later, but right now it fully satisfies my needs. It's implemented in C# 3.0 (Visual Studio 2008) and I can build it for both desktop and Silverlight. The release has both binaries.

    现有应用

    A good example of where this framework is used is the Live Geometry project (http://livegeometry.codeplex.com). It defines several actions such as AddFigureAction, RemoveFigureAction, MoveAction and SetPropertyAction.

    如何使用

      我学习这些东西一般都喜欢先看如何使用,因为从使用方式就能看出封装得是否简单易用。

      以下是一个控制台的演示程序,代码如下:

    using System;
    using GuiLabs.Undo;
    
    namespace MinimalSample
    {
        class Program
        {
            static void Main(string[] args)
            {
                Console.WriteLine("Original color");
    
                SetConsoleColor(ConsoleColor.Green);
                Console.WriteLine("New color");
    
                actionManager.Undo();
                Console.WriteLine("Old color again");
    
                using (Transaction.Create(actionManager))
                {
                    SetConsoleColor(ConsoleColor.Red); // you never see Red
                    Console.WriteLine("Still didn't change to Red because of lazy evaluation");
                    SetConsoleColor(ConsoleColor.Blue);
                }
                Console.WriteLine("Changed two colors at once");
    
                actionManager.Undo();
                Console.WriteLine("Back to original");
    
                actionManager.Redo();
                Console.WriteLine("Blue again");
                Console.ReadKey();
            }
    
            static void SetConsoleColor(ConsoleColor color)
            {
                SetConsoleColorAction action = new SetConsoleColorAction(color);
                actionManager.RecordAction(action);
            }
    
            static ActionManager actionManager = new ActionManager();
        }
    
        class SetConsoleColorAction : AbstractAction
        {
            public SetConsoleColorAction(ConsoleColor newColor)
            {
                color = newColor;
            }
    
            ConsoleColor color;
            ConsoleColor oldColor;
    
            protected override void ExecuteCore()
            {
                oldColor = Console.ForegroundColor;
                Console.ForegroundColor = color;
            }
    
            protected override void UnExecuteCore()
            {
                Console.ForegroundColor = oldColor;
            }
        }
    }
    
    运行后界面如下:

    下载代码你会看到,它还自带一个Form的例子,感兴趣可以自己去看看


    Actions

      所有操作都从 IAction继承下来,必须实现两个操作:一个是执行操作,一个是反执行操作

    /// <summary>
    /// Encapsulates a user action (actually two actions: Do and Undo)
    /// Can be anything.
    /// You can give your implementation any information it needs to be able to
    /// execute and rollback what it needs.
    /// </summary>
    public interface IAction
    {
        /// <summary>
        /// Apply changes encapsulated by this object.
        /// </summary>
        void Execute();
    
        /// <summary>
        /// Undo changes made by a previous Execute call.
        /// </summary>
        void UnExecute();
    
        /// <summary>
        /// For most Actions, CanExecute is true when ExecuteCount = 0 (not yet executed)
        /// and false when ExecuteCount = 1 (already executed once)
        /// </summary>
        /// <returns>true if an encapsulated action can be applied</returns>
        bool CanExecute();
    
        /// <returns>true if an action was already executed and can be undone</returns>
        bool CanUnExecute();
    
        /// <summary>
        /// Attempts to take a new incoming action and instead of recording that one
        /// as a new action, just modify the current one so that it's summary effect is 
        /// a combination of both.
        /// </summary>
        /// <param name="followingAction"></param>
        /// <returns>true if the action agreed to merge, false if we want the followingAction
        /// to be tracked separately</returns>
        bool TryToMerge(IAction followingAction);
    
        /// <summary>
        /// Defines if the action can be merged with the previous one in the Undo buffer
        /// This is useful for long chains of consecutive operations of the same type,
        /// e.g. dragging something or typing some text
        /// </summary>
        bool AllowToMergeWithPrevious { get; set; }
    }
    

    ActionManager

    ActionManager负责跟踪undo/redo记录,提供RecordAction(IAction)来记录操作步骤,提供ActionManager.Undo(), ActionManager.Redo(), CanUndo(), CanRedo()等其他方法。

    其完整代码如下:

      /// <summary>
        /// Action Manager is a central class for the Undo Framework.
        /// Your domain model (business objects) will have an ActionManager reference that would 
        /// take care of executing actions.
        /// 
        /// Here's how it works:
        /// 1. You declare a class that implements IAction
        /// 2. You create an instance of it and give it all necessary info that it needs to know
        ///    to apply or rollback a change
        /// 3. You call ActionManager.RecordAction(yourAction)
        /// 
        /// Then you can also call ActionManager.Undo() or ActionManager.Redo()
        /// </summary>
        public class ActionManager
        {
            public ActionManager()
            {
                History = new SimpleHistory();
            }
    
            #region Events
    
            /// <summary>
            /// Listen to this event to be notified when a new action is added, executed, undone or redone
            /// </summary>
            public event EventHandler CollectionChanged;
            protected void RaiseUndoBufferChanged(object sender, EventArgs e)
            {
                if (CollectionChanged != null)
                {
                    CollectionChanged(this, e);
                }
            }
    
            #endregion
    
            #region RecordAction
    
            #region Running
    
            /// <summary>
            /// Currently running action (during an Undo or Redo process)
            /// </summary>
            /// <remarks>null if no Undo or Redo is taking place</remarks>
            public IAction CurrentAction { get; internal set; }
    
            /// <summary>
            /// Checks if we're inside an Undo or Redo operation
            /// </summary>
            public bool ActionIsExecuting
            {
                get
                {
                    return CurrentAction != null;
                }
            }
    
            #endregion
    
            /// <summary>
            /// Defines whether we should record an action to the Undo buffer and then execute,
            /// or just execute it without it becoming a part of history
            /// </summary>
            public bool ExecuteImmediatelyWithoutRecording { get; set; }
    
            /// <summary>
            /// Central method to add and execute a new action.
            /// </summary>
            /// <param name="existingAction">An action to be recorded in the buffer and executed</param>
            public void RecordAction(IAction existingAction)
            {
                if (existingAction == null)
                {
                    throw new ArgumentNullException(
                        "ActionManager.RecordAction: the existingAction argument is null");
                }
                // make sure we're not inside an Undo or Redo operation
                CheckNotRunningBeforeRecording(existingAction);
    
                // if we don't want to record actions, just run and forget it
                if (ExecuteImmediatelyWithoutRecording
                    && existingAction.CanExecute())
                {
                    existingAction.Execute();
                    return;
                }
    
                // Check if we're inside a transaction that is being recorded
                ITransaction currentTransaction = RecordingTransaction;
                if (currentTransaction != null)
                {
                    // if we're inside a transaction, just add the action to the transaction's list
                    currentTransaction.AccumulatingAction.Add(existingAction);
                    if (!currentTransaction.IsDelayed)
                    {
                        existingAction.Execute();
                    }
                }
                else
                {
                    RunActionDirectly(existingAction);
                }
            }
    
            void CheckNotRunningBeforeRecording(IAction existingAction)
            {
                string existing = existingAction != null ? existingAction.ToString() : "";
    
                if (CurrentAction != null)
                {
                    throw new InvalidOperationException
                    (
                        string.Format
                        (
                              "ActionManager.RecordActionDirectly: the ActionManager is currently running "
                            + "or undoing an action ({0}), and this action (while being executed) attempted "
                            + "to recursively record another action ({1}), which is not allowed. "
                            + "You can examine the stack trace of this exception to see what the "
                            + "executing action did wrong and change this action not to influence the "
                            + "Undo stack during its execution. Checking if ActionManager.ActionIsExecuting == true "
                            + "before launching another transaction might help to avoid the problem. Thanks and sorry for the inconvenience.",
                            CurrentAction.ToString(),
                            existing
                        )
                    );
                }
            }
    
            object recordActionLock = new object();
            /// <summary>
            /// Adds the action to the buffer and runs it
            /// </summary>
            void RunActionDirectly(IAction actionToRun)
            {
                CheckNotRunningBeforeRecording(actionToRun);
    
                lock (recordActionLock)
                {
                    CurrentAction = actionToRun;
                    if (History.AppendAction(actionToRun))
                    {
                        History.MoveForward();
                    }
                    CurrentAction = null;
                }
            }
    
            #endregion
    
            #region Transactions
    
            public Transaction CreateTransaction()
            {
                return Transaction.Create(this);
            }
    
            public Transaction CreateTransaction(bool delayed)
            {
                return Transaction.Create(this, delayed);
            }
    
            private Stack<ITransaction> mTransactionStack = new Stack<ITransaction>();
            public Stack<ITransaction> TransactionStack
            {
                get
                {
                    return mTransactionStack;
                }
                set
                {
                    mTransactionStack = value;
                }
            }
    
            public ITransaction RecordingTransaction
            {
                get
                {
                    if (TransactionStack.Count > 0)
                    {
                        return TransactionStack.Peek();
                    }
                    return null;
                }
            }
    
            public void OpenTransaction(ITransaction t)
            {
                TransactionStack.Push(t);
            }
    
            public void CommitTransaction()
            {
                if (TransactionStack.Count == 0)
                {
                    throw new InvalidOperationException(
                        "ActionManager.CommitTransaction was called"
                        + " when there is no open transaction (TransactionStack is empty)."
                        + " Please examine the stack trace of this exception to find code"
                        + " which called CommitTransaction one time too many."
                        + " Normally you don't call OpenTransaction and CommitTransaction directly,"
                        + " but use using(var t = Transaction.Create(Root)) instead.");
                }
    
                ITransaction committing = TransactionStack.Pop();
    
                if (committing.AccumulatingAction.Count > 0)
                {
                    RecordAction(committing.AccumulatingAction);
                }
            }
    
            public void RollBackTransaction()
            {
                if (TransactionStack.Count != 0)
                {
                    var topLevelTransaction = TransactionStack.Peek();
                    if (topLevelTransaction != null && topLevelTransaction.AccumulatingAction != null)
                    {
                        topLevelTransaction.AccumulatingAction.UnExecute();
                    }
    
                    TransactionStack.Clear();
                }
            }
    
            #endregion
    
            #region Undo, Redo
    
            public void Undo()
            {
                if (!CanUndo)
                {
                    return;
                }
                if (ActionIsExecuting)
                {
                    throw new InvalidOperationException(string.Format("ActionManager is currently busy"
                        + " executing a transaction ({0}). This transaction has called Undo()"
                        + " which is not allowed until the transaction ends."
                        + " Please examine the stack trace of this exception to see"
                        + " what part of your code called Undo.", CurrentAction));
                }
                CurrentAction = History.CurrentState.PreviousAction;
                History.MoveBack();
                CurrentAction = null;
            }
    
            public void Redo()
            {
                if (!CanRedo)
                {
                    return;
                }
                if (ActionIsExecuting)
                {
                    throw new InvalidOperationException(string.Format("ActionManager is currently busy"
                        + " executing a transaction ({0}). This transaction has called Redo()"
                        + " which is not allowed until the transaction ends."
                        + " Please examine the stack trace of this exception to see"
                        + " what part of your code called Redo.", CurrentAction));
                }
                CurrentAction = History.CurrentState.NextAction;
                History.MoveForward();
                CurrentAction = null;
            }
    
            public bool CanUndo
            {
                get
                {
                    return History.CanMoveBack;
                }
            }
    
            public bool CanRedo
            {
                get
                {
                    return History.CanMoveForward;
                }
            }
    
            #endregion
    
            #region Buffer
    
            public void Clear()
            {
                History.Clear();
                CurrentAction = null;
            }
    
            public IEnumerable<IAction> EnumUndoableActions()
            {
                return History.EnumUndoableActions();
            }
    
            private IActionHistory mHistory;
            internal IActionHistory History
            {
                get
                {
                    return mHistory;
                }
                set
                {
                    if (mHistory != null)
                    {
                        mHistory.CollectionChanged -= RaiseUndoBufferChanged;
                    }
                    mHistory = value;
                    if (mHistory != null)
                    {
                        mHistory.CollectionChanged += RaiseUndoBufferChanged;
                    }
                }
            }
    
            #endregion
        }
    

    参考

    http://blogs.msdn.com/kirillosenkov/archive/2009/06/29/new-codeplex-project-a-simple-undo-redo-framework.aspx 
    http://blogs.msdn.com/kirillosenkov/archive/2009/07/02/samples-for-the-undo-framework.aspx

    其他Undo框架

    欢迎转载,转载请注明:转载自周金根 [ http://zhoujg.cnblogs.com/ ]



  • 相关阅读:
    基于ARM的指纹识别门禁系统设计方案
    基于ARM9的指纹识别系统的设计和实现
    使用TI 的低功耗C5x DSP的指纹识别方框图和解决方
    基于ATmega162的指纹识别电子机械锁设计
    一种光学指纹识别系统的设计方案
    利用DSP高速处理能力对指纹识别的系统方案
    加性噪声--传递概率密度函数=噪声概率密度函数
    信号的方差与功率的关系
    Bayes factor
    频率学派贝叶斯学派估计的区别
  • 原文地址:https://www.cnblogs.com/zhoujg/p/1808305.html
Copyright © 2011-2022 走看看