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; 
        } 
    } 

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