zoukankan      html  css  js  c++  java
  • Undo/Redo的C#实现方式

    现代软件很多都配备了Undo/Redo功能,这个功能对于用户来说是十分方便,可以明显节约用户时间,增加软件的易用性,其中以Office和 AutoCAD最为典型。本文以.NET环境中最常用的C#语言为例,详细分析了实现Undo/Redo功能的必要技术,并提供了可运行的C#代码清 单。 
         实现Undo/Redo功能,就必须记录用户的操作和这些操作发生前受该操作影响的对象的值。当然不是每个操作都是值得关注的,比如“打开文件”,“最大化窗口”等这类不改变软件核心对象数据的操作,是完全不必记录下来的。 
         其 中一些操作只改变一个对象的一个域的值,而一些操作可以改变很对个对象的多个域的值(例如AutoCAD中的分解命令,可以同时改变N个对象的状态)。为 了简化问题,我们肯定希望不管是只改变一个对象的某个域的操作(被称为原子操作)还是一次改变多个对象的操作(被称为复合操作)都被以一种单一接口被提 供 。这就要对操作进行包装,将原子操作打包成复合操作,或者说把复合操作伪装成原子操作,因为对于操作的调用者来说,这完全是透明的,他将感觉不到自己 调用的是原子操作还是复合操作,也不必关心这个问题。 
         一个复合操作到底含有多少原子操作是事先无法确定的。ArrayList 是.NET提供的一种可变的万能数组,它实际上是一个****对象,但是它的使用方式上看上去更像个可变的一维数组,很适合用于这种未知长度的情况。所以 复合操作用ArrayList存放是合适的。 
         接下来的事情就是理解Undo/Redo的本质了。用户在Undo时,软件到底做了些什 么呢?上面分析了所有有效操作都必须被记录下来,而且是被存放在数组或者是链表中,当用户Undo时,显然是要从数组或者链表中取回被保存的数据,然后赋 值给相对应的对象。Redo发生时大概也是发生了这些事情,只是方向和Undo刚好是相反的。用户的操作不一定总是要插入数组/链表的末端。举个例子:当 用户的先进行了10个操作,然后又Undo掉了5个操作,然后又进行了一个操作,这个操作是应该保存在数组/链表的第11个位置上还是应该保存在第6个位 置上呢?答案显然是后者。只要又进行了一个操作,先前的被保存在Undo数组/链表中的第6到10个记录将全部变成无效。

     
        那么用户在Redo时发生了什么呢?还是刚才的例子,用户Undo掉5个操作后又Redo了3次,显然应该和用户只进行了前8个操作是等价的。如果用户Undo掉5个操作而没有Redo,而是又进行了3个其他操作呢?这时候他再Redo会怎样?根据经验,结论是无法Redo了,Redo操作必须紧随Undo才行。因为Undo数组中第9,10两个操作早已经是无效了,再Redo它们,是绝对错误的事情。 
        至此,关于Undo/Redo的分析就完成了,接下来就是编码。下面提供的是在VS2005上编译通过的C#源代码:
     public  class CUndo 
        { 
           //private ArrayList actionList;//复合操作队列,每个actionList的子成员为CAction对象 
           private CActionNode[] actionList; 
           private int iMaxUndoTime;//操作队列的最大长度 
           private bool bCanUndo=false;//当前状态下是否可以进行Undo操作 
           public bool CanUndo 
           { 
               get { return bCanUndo; } 
           } 
           private bool bCanRedo = false;//当前状态下是否可以进行Redo操作 
           public bool CanRedo 
           { 
               get { return bCanRedo; } 
           } 
           public bool bStartState = false;//指示是否是第一次记录操作 
           private int iActionListLength= 0;//有效操作队列的长度,它并不一定等于actionList.count 
           private int iCurrentActionPointer= -1;//指向actionList当前操作的指针,新添加的操作从该 
           //位置的下一位开始,Undo操作从它的上一个位置开始,Redo操作从它下一位开始 
           public CUndo(int maxUndoTime) 
           { 
               actionList = new CActionNode[maxUndoTime];//为actionList分配内存空间 
               iMaxUndoTime = maxUndoTime;//设置可Undo的最大次数 
           } 
           public int ActionListLength//获取当前操作队列的实际长度 
           { 
                get { return iActionListLength;} 
           } 
           public int CurrentActionPointer//获取当前Undo指针的位置 
           { 
                get {return iCurrentActionPointer;} 
           } 
    
           //向操作队列中加入操作,插入成功就返回true,否则为false 
           public bool addActionIntoActionList(CActionNode act) 
           { 
               //如果不超过Undo队列的最大允许长度就可以插入 
               if (iCurrentActionPointer< iMaxUndoTime-1) 
               { 
                   actionList[++iCurrentActionPointer]= act; 
                   iActionListLength = iCurrentActionPointer; 
                   bCanUndo = true;//只要有操作进入队列,bCanUndo就为真 
                   return true; 
               } 
               else 
                   return false; 
           } 
            public bool undo(int iUndoTime) 
            { 
                //如果undo次数大于iCurrentActionPointer指示的长度,就报错 
                //因为Undo队列的最大长度就是iCurrentActionPointer+1 
                if (iUndoTime > iCurrentActionPointer+1) 
                    return false; 
                CActionNode act; 
                while (iUndoTime > 0 && iCurrentActionPointer>0) 
                { 
                    //从Undo队列中取回操作 
                    act = actionList[--iCurrentActionPointer]; 
                    iUndoTime--; 
                    //如果取回操作失败,返回false 
                    if (act.getActionsInActionNode() == false) 
                        return false;       
                } 
                bCanRedo = true;//如果成功Undo,就置bCanRedo为真 
                return true; 
            } 
            public bool redo(int iRedoTime) 
            { 
                if (iRedoTime > iActionListLength - iCurrentActionPointer) 
                    return false; 
                CActionNode act; 
                while (iRedoTime>0 ) 
                { 
                    act = actionList[++iCurrentActionPointer]; 
                    iRedoTime--; 
                    Debug.Assert(act != null, iCurrentActionPointer.ToString()); 
                     
                    if (act.getActionsInActionNode()==false) 
                        return false;   
                } 
                bCanUndo = true;//如果成功Redo,就置bCanUndo为真 
                return true; 
            } 
        } 
    
     
     
     


     
     

  • 相关阅读:
    HDU6808 Go Running(未解决问题
    K
    E
    D
    B
    I
    HDU 2255 奔小康赚大钱 (KM算法模板)
    hdu 1150 Machine Schedule(二分图模板题)
    ACM-ICPC 2018 焦作赛区网络预赛G Give Candies(欧拉降幂)
    ACM-ICPC 2018 焦作赛区网络预赛 L:Poor God Water(杜教BM)
  • 原文地址:https://www.cnblogs.com/ck235/p/4901029.html
Copyright © 2011-2022 走看看