博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
推荐:介绍一个UndoFramework
阅读量:6691 次
发布时间:2019-06-25

本文共 14627 字,大约阅读时间需要 48 分钟。

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

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

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

UndoFramework项目

Codeplex网站地址:

下载地址:

项目描述:

  It's a simple framework to add Undo/Redo functionality to your applications, based on the classical . 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 (). 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;        }    }}
运行后界面如下:

2010082516362998.png

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

2010082516372827.png

Actions

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

/// /// 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./// public interface IAction{    ///     /// Apply changes encapsulated by this object.    ///     void Execute();    ///     /// Undo changes made by a previous Execute call.    ///     void UnExecute();    ///     /// For most Actions, CanExecute is true when ExecuteCount = 0 (not yet executed)    /// and false when ExecuteCount = 1 (already executed once)    ///     /// 
true if an encapsulated action can be applied
bool CanExecute(); ///
true if an action was already executed and can be undone
bool CanUnExecute(); /// /// 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. /// /// ///
true if the action agreed to merge, false if we want the followingAction /// to be tracked separately
bool TryToMerge(IAction followingAction); /// /// 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 /// bool AllowToMergeWithPrevious { get; set; }}

ActionManager

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

其完整代码如下:

///     /// 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()    ///     public class ActionManager    {        public ActionManager()        {            History = new SimpleHistory();        }        #region Events        ///         /// Listen to this event to be notified when a new action is added, executed, undone or redone        ///         public event EventHandler CollectionChanged;        protected void RaiseUndoBufferChanged(object sender, EventArgs e)        {            if (CollectionChanged != null)            {                CollectionChanged(this, e);            }        }        #endregion        #region RecordAction        #region Running        ///         /// Currently running action (during an Undo or Redo process)        ///         /// 
null if no Undo or Redo is taking place
public IAction CurrentAction { get; internal set; } /// /// Checks if we're inside an Undo or Redo operation /// public bool ActionIsExecuting { get { return CurrentAction != null; } } #endregion /// /// 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 /// public bool ExecuteImmediatelyWithoutRecording { get; set; } /// /// Central method to add and execute a new action. /// /// An action to be recorded in the buffer and executed 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(); /// /// Adds the action to the buffer and runs it /// 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
mTransactionStack = new Stack
(); public Stack
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
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 }

 

参考

其他Undo框架

  • by Sergei Arhipenko
  • by Marc Clifton
  • - Undo/Redo for Entity Framework by Matthieu MEZIL

 

欢迎转载,转载请注明:转载自 [ ]

你可能感兴趣的文章
Hessian HTTP POST访问时,Nginx返回411问题
查看>>
Exif图片方向的一些发现
查看>>
iOS之传值
查看>>
pandas 修改 DataFrame 列名
查看>>
《2018年云上挖矿态势分析报告》发布,非Web类应用安全风险需重点关注
查看>>
Nervos 双周报第 3 期:佛系新年之后的开工大吉!
查看>>
【PHP 扩展开发】Zephir 基础篇
查看>>
怎么将在线录制的视频转为GIF动态图
查看>>
【剑指offer】顺时针打印矩阵
查看>>
聊聊JavaScript和Scala的表达式 Expression
查看>>
CSS3中的box-sizing
查看>>
云计算新风向:多云战略优化企业云支出
查看>>
Windows改Linux(一),新建Ubuntu虚拟机小白向导
查看>>
HTML5调用手机前置摄像头或后置摄像头拍照,canvas显示,经过Android测试
查看>>
Spring Cloud构建微服务架构:分布式服务跟踪(入门)【Dalston版】
查看>>
【355天】跃迁之路——程序员高效学习方法论探索系列(实验阶段113-2018.01.26)...
查看>>
Rust编程语言的核心部件
查看>>
BZOJ 1061: [Noi2008]志愿者招募【单纯形裸题】
查看>>
v8世界探险(3) - v8的抽象语法树结构
查看>>
《C语言及程序设计》实践项目——用if语句实现分支结构
查看>>