C#使用命令模式實現撤銷和恢復功能


第一次寫關於設計模式的隨筆,最近在使用C#做一個WinForm的項目,其中要求需要支持撤銷和恢復功能,想到了以前看過Command模式支持撤銷和恢復操作,就在項目中使用了。對命令模式理解的不夠深,各位看客請指正。

Gof23種設計模式中的Command模式,其意圖是這么描述的“將一個請求封裝為一個對象,從而是你可以用不同的請求對客戶進行參數化;對請求排隊或記錄請求日志,以及支持可撤銷的操作”;另外個人的理解就是可以將調用者和接受者解耦出來。下圖為Command的類圖:

首先理解將調用者和接受者解耦出來:Invoker中只需要持有一個Command接口的引用即可,但是具體是其哪一個子類,Invoker並不知道。而真正執行動作的接受者,Invoker並不用知道,這樣就講調用者和接收者解耦出來。另外理解實現撤銷和恢復操作。程序需要將所有操作過的命令對象存儲起來,存儲的每一個命令對象保存了其當前狀態和上一個狀態,因此可用來做撤銷和恢復操作。下面用代碼進行說明,本文使用C#語言實現的。

    首先,我們定義一個Command的接口。定義如下:

interface Command
{
     void execute();
     void undo();
 }

其中僅僅聲明了兩個函數,其如果在c++的實現的話,使用純虛類。

我們要實現的具體功能為,可以為一個TextBox控件實先撤銷和恢復功能。因此,我們實先一個TextChangedCommand的類,定義如下:

 1 public class TextChangedCommand: Command
 2 {
 3      private TextBox ctrl;
 4      private String newStr;
 5      private String oldStr;
 6 
 7      public TextChangedCommand(TextBox ctrl, String newStr, String oldStr)
 8      {
 9          this.ctrl = ctrl;
10          this.newStr = newStr;
11          this.oldStr = oldStr;
12      }
13 
14      public void execute()
15      {
16          this.ctrl.Text = newStr;
17          this.ctrl.SelectionStart = this.ctrl.Text.Length;
18      }
19 
20      public void undo()
21      {
22          this.ctrl.Text = oldStr;
23          this.ctrl.SelectionStart = this.ctrl.Text.Length;
24      }
25 }

其中有三個成員變量,分別為TextBox類型的ctrl,和String類型的newStr、oldStr。這里的ctrl就可以理解為類圖中的Invoker,而newStr和oldStr即為當前狀態和上一個狀態。

由於我們需要實現其撤銷和恢復功能,因此這里創建兩個棧對象,分別用來存儲已經執行了的命令對象和撤銷后的命令對象。即在撤銷動作中,從棧中彈出一個Command對象,然后執行對象的undo操作,執行完成后將其存儲到恢復的棧中,在點擊恢復時,用從恢復棧彈出一個Command對象,執行其execute動作。兩個棧的定義如下:

Stack<Command> undoStack = new Stack<Command>();
Stack<Command> redoStack = new Stack<Command>();

下面實現類圖中的Client部分,其主要功能是創建一個具體的Command對象,同時為為其設置接收者。對於一個TextBox來說,在其Text熟悉變化的時候,將其上一個狀態和當前狀態保存下來。具體試下如下:

private void textBox1_TextChanged(object sender, EventArgs e)
         {
if(flag){ TextChangedCommand com
= new TextChangedCommand((TextBox)textBox1, ((TextBox)textBox1).Text, oldStr); undoStack.Push(com); oldStr = ((TextBox)textBox1).Text;
} }

這個函數是控件textBox1的Text屬性變化時的回調函數。在其中創建了Command基類的實力,並將其存到棧對象undoStack中。下面來做恢復和撤銷操作的代碼:

撤銷操作:

 private void button1_Click(object sender, EventArgs e) //撤銷
{
      if (undoStack.Count == 0)
          return;
    
flag = false;
Command com
= undoStack.Pop(); com.undo(); redoStack.Push(com); }

恢復操作:

private void button2_Click(object sender, EventArgs e) //恢復
         {
             if (redoStack.Count == 0)
                 return;

flag = false;
             Command com = redoStack.Pop();
             com.execute();

             undoStack.Push(com);
         }

我將兩個操作分別放到兩個button的操作里面,一個用於撤銷,另外一個用於恢復。由於我們在進行撤銷和恢復時,在TextChangedCommand中是對TextBox控件進行賦值,因此也會觸發textBox1控件的Text屬性變化的回調函數,我這這里使用了一個標志flag,如果是在撤銷和恢復的動作中,則不做Command對象的壓棧動作。到這里,已經完全可以實現了TextBox的撤銷和恢復功能,當然在我自己的項目中,出了TextBox控件為,還有ComboBox、DateTimePicker、CheckBox控件都實現了撤銷和恢復功能,但是ComboBox控件的回調函數需要使用SelectIndexChanged作為回調,不要使用SelectTextChanged,因為當Index變化時也會觸發Text屬性的變化。

這里簡單介紹一下在撤銷恢復時,不觸發textBox1的Text的屬性回調函數的另外一種方法。我在這里將其稱之為原子操作。可以將textBox1的回到函數textBox1_TextChanged作為一個參數,傳入到Command對象中。在對TextBox的Text屬性變化前先做這個動作:ctrl.TextChanged -=動作,賦值完成后,再做Ctrl.TextChanged +=動作。下面用一個函數簡單模擬一下:

void setTextBox(TextBox ctrl, String str,  EventHandler eventhandler)

{

  ctrl.TextChanged -= eventhandler;

     ctrl.Text = str;

     ctrl.TextChanged += eventhandler;

}

 附上源碼:http://files.cnblogs.com/files/youyipin/test_Charp.zip


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM