對.NET Framework "事件"機制理解的程式碼分析 (轉)

worldblog發表於2007-12-15
對.NET Framework "事件"機制理解的程式碼分析 (轉)[@more@]

下面的文章是我自認為對"事件"機制理解比較透徹的後寫的程式碼分析,但寫完之後,鄙人又惟恐理解有所偏差,所以特貼出來讓各位高手指教一二,若能讓發現理解錯誤之處,將不勝感激(此問完全乃本人"獨立自主"之作,絕非抄襲)

同時我相信此文對初學者也有一定幫助!

為了闡述清晰,特舉例說明;
該範例是在一箇中完全自定義一組事件,並在另外的集中對事件被激發作出反映(也就是事件被激發後預先定義好的方法).

一.含有自定義事件的控制元件主體程式碼及對應剖析 (注意,此控制元件庫是由VS的"新建"->"控制元件庫"生成的)

namespace MyEventTEST
{
 public class LoginEventArgs : System.EventArgs
  // 上面程式碼定義了在主程式中引發事件時需要傳遞給主程式的所有資訊,並且注意,
  // 該類必須派生於System.EventArgs類
 {
 public LoginEventArgs(string sUserID, string sPass, bool bValid)
 {
 UserID = sUserID;
 Password = sPassword;
 Valid = bValid;
 }

 public string UserID;
 public string Password;
 public bool Valid;
 }

 public delegate void GoodLoginEventHandler( sender, LoginEventArgs e);
 public delegate void FailedThreeTimesEventHandler(object sender, LoginEventArgs e);
  // 上面兩行程式碼定義了兩個多路委託(因此返回型別必須為void),每個委託對應一種型別的事件;
  // 因為是多路委託,所以每個委託中可以含有多個方法.
  // 請注意,引數是(object sender, LoginEventArgs e),所以新增到多路委託的方法必須符合這種簽名方式.
  // 此外,為什麼這裡不使用已經定義的多路委託"System.EventHandler(object sender, EventArgs e)",
  // 而要自己定義新的委託"?????EventHandler()"呢?這是因為我們這裡傳遞給程式集的引數不是
  // "System.EventArgs"型別,而是自己定義的"LoginEventArgs"型別,所以有必要重新定義自己的委託型別.


 public class ActiveLogin : System.Windows.Forms.UserControl
 {
   private System.Windows.Forms.Label label1;
 private System.Windows.Forms.Label label2;
 private System.Windows.Forms.TextBox txtUserID;
 private System.Windows.Forms.TextBox txtPass;
 private System.Windows.Forms.Button btnLogin;
 private System.Windows.Forms.Button btnCancel;
 private System.ComponentModel.Container components = null;
  // 上面程式碼是組成這個控制元件的一些定義,由VS.NET自動生成

 public event GoodLoginEventHandler GoodLogin;
 public event FailedThreeTimesEventHandler FailedThreeTimes;
 public event EventHandler Cancel;
  // 上面三行程式碼非常之重要,定義了三個事件(event),分別是"GoodLogin","FailedThreeTimes"
  // 和"Cancel"
  // 它們的型別分別是"GoodLoginEventHandler","FailedThreeTimesEventHandler"
  // 和"EventHandler",也就是說新增到這三個事件中的方法必須符合對應的多路委託定義!
  // 而且注意,因為事件"Cancel"的型別是系統已經定義的多路委託"EventHandler"型別,
  // 所以上面的多路委託中沒有定義類似"CancelEventHandler"的新委託,因為是不需要的.
 
 public ActiveLogin()
 {
 InitializeComponent();
 }
  // 上面程式碼是控制元件中類"ActiveLogin"的構造方法,該方法中呼叫了初始方法InitializeComponent()
  // 上面程式碼由VS.NET自動生成

 protected overr void Dispose( bool disposing )
 {
 if( disposing )
 {
 if(components != null)
 {
 components.Dispose();
 }
 }
 base.Dispose( disposing );
 }
  // 上面程式碼是自定義控制元件中類"ActiveLogin"的析構方法,由VS.NET自動生成.

 private void InitializeComponent()
 {
  ....  // 這裡是對所有引用控制元件(元件)的初始化程式碼
  }
  // 上面程式碼是自定義控制元件中類"ActiveLogin"的初始方法,其中內容由VS.NET自動生成.

 protected virtual void OnGoodLogin(LoginEventArgs e)
  // 上面一行程式碼定義了激發"GoodLogin"事件的方法;
  // 注意簽名型別,定義方法是protected virtual,也就是說只能在這個類及它的
  // 繼承類中訪問此方法,而且可以重寫.
  // 引數型別是"LoginEventArgs",注意只有這一個引數,因為在本方法中含有對
  // this的引用,所以這裡不需要傳遞this.
  // 一般地說,這個方法使用場合只有兩種:
  //  <1>在本控制元件內被呼叫,因為本方法不被呼叫,就無法激發使用者程式碼在事件"GoodLogin"
  //  中新增的方法;
  //  <2>在使用者的繼承程式碼中重寫本方法,雖然重寫本方法可能會帶來的提高,但
  //  倘若使用者程式碼中忘記呼叫此方法,那麼在使用者程式碼先前在事件"GoodLogin"中
  //  新增的方法將無法得到啟用!!! (避免此問題的方法就是在重寫方法中必須含有
  //  一行"base.GoogLogin(e)",這行將負責呼叫本方法)
  //  對於第<2>點需要提出的是,在使用者的繼承程式碼中重寫本方法的作用相當與在
  //  事件"GoodLogin"中新增一個方法,此方法的程式碼內容和重寫方法內容相同.
  //  (但是應該絕對沒有"base.GoogLogin(e)"這一行)
 {
 if (GoodLogin != null)  // 如果在事件"GoogLogin"中含有方法,則激發這些方法
 {
 GoodLogin(this, e);  // 把this物件和引數e傳遞給所有在事件"GoogLogin"
  // 中新增的方法,並順序這些方法 (注意,由多路
  // 委託特性決定:在使用者程式碼中先新增的方法先執行.
 }
 }
  // 上面對OnGoogLogin方法解釋已經十分詳細了,下面兩個ON方法均與上述ON方法同出一轍.

 protected virtual void OnFailedThreeTimes(LoginEventArgs e)
 {
 if (FailedThreeTimes != null)
 {
 FailedThreeTimes(this, e);
 }
 }

 protected virtual void OnCancel(System.EventArgs e)
 {
 if (Cancel != null)
 {
 Cancel(this, e);
 }
 }
 
 private void btnLogin_Click(object sender, System.EventArgs e)
  // 上面的定義是由VS.NET自動生成,是當按下控制元件的"btnLogin"按鈕時呼叫的方法.
 {
  if(...)
  OnGoodLogin(new LoginEventArgs(txtUserID.Text, txtPass.Text, true));
  // 上面一行程式碼呼叫了OnGoodLogin方法,作用是"當控制元件中的按鈕btnLogin被按下時,
  // 並且符合上面的if條件時:
  // 將透過呼叫OnGoodLogin方法把在使用者程式碼中新增到事件"GoogLogin"中的所有方法
  // 全部順序執行一遍.
  // 為什麼不在這裡把OnGoodLogin()方法中的程式碼執行一遍,而還要再單獨呼叫OnGoodLogin
  // 方法呢? 這是因為有時候使用者程式碼需要重寫OnGoodLogin()方法!
  // 下面呼叫的OnFailedThreeTimes()方法和OnCancel()方法解釋同上.
  else
  OnFailedThreeTimes(new LoginEventArgs(txtUserID.Text, txtPass.Text, false));
 }

 private void btnCancel_Click(object sender, System.EventArgs e)
 {
 OnCancel(new EventArgs());
 }
 }
}

二.呼叫此控制元件的程式集(注意,此程式集是由VS.NET的"新建"->"Windows應用程式"生成的),也就是"使用者程式碼"部分

namespace HostApp
{
 public class Form1 : System.Windows.Forms.Form
 {
 private MyEventTEST.ActiveLogin activeLogin1;
  // 上面一行程式碼引用了自定義控制元件庫的類"ActiveLogin",並用它定義了一個物件"activeLogin1".
  //  這裡的"MyEventTEST"是在自定義的控制元件庫中定義的名稱空間,如果在這個程式集中沒有出現
  //  "using MyEventTEST"語句,則該名稱必須出現在對自定義控制元件引用的任何程式碼中!

 private System.ComponentModel.Container components = null;

 public Form1()
 {
 InitializeComponent();
 }

 protected override void Dispose( bool disposing )
 {
 if( disposing )
 {
 if (components != null)
 {
 components.Dispose();
 }
 }
 base.Dispose( disposing );
 }
  // 上面兩個方法都是又VS.NET自動生成,不做另外解釋.

 private void InitializeComponent()
 {
 this.activeLogin1 = new MyEventTEST.ActiveLogin();
  // 上面一行程式碼用自定義控制元件庫中的類"ActiveLogin"例項化物件"activeLogin1"
 this.SuspendLayout();

  //
 // activeLogin1
 //
 this.activeLogin1.Location = new System.Drawing.Point(144, 8);
 this.activeLogin1.Name = "activeLogin1";
 this.activeLogin1.Size = new System.Drawing.Size(280, 184);
 this.activeLogin1.TabIndex = 0;

 this.activeLogin1.GoodLogin += new MyEventTEST.GoodLoginEventHandler(this.activeLogin1_GoodLogin);
 this.activeLogin1.Cancel += new System.EventHandler(this.activeLogin1_Cancel);
 this.activeLogin1.FailedThreeTimes += new MyEventTEST.FailedThreeTimesEventHandler(this.activeLogin1_FailedThreeTimes);
  // !!! 請注意上面的三行程式碼,這是使用者程式碼接受自定義控制元件庫中事件的程式碼 !!!
  // 上面三行程式碼分別把使用者定義的方法"activeLogin1_GoodLogin","activeLogin1_Cancel"
  // 和"activeLogin1_FailedThreeTimes"分別新增到自定義控制元件中的事件"GoogLogin","Cancel"
  // 和"FailedThreeTimes"中; 這樣一來只要自定義控制元件中的對應事件一被激發,這些
  // 新增使用者自定義方法就會被執行.
  // 要注意的是,使用者自定義方法簽名必須符合對應的多路委託的定義(因為事件是由多路委託
  // 定義的,所以要新增到什麼事件,定義就必須符合該事件對應的多路委託的定義)
  // 而且,這裡的Cancel事件型別是系統已經定義的多路委託"EventHandler"型別,所以它的例項化
  // 與其它兩個事件的例項化有所不同,是"System.EventHandler",而不是"MyEventTEST.EventHandler"!
  // 這些使用者自定義方法將在下面列出.
  // 不過請注意,上面的三行程式碼雖然在方法InitializeComponent()中,但卻是我們自己手工新增的!

 //
 // Form1
 //
 this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
 this.ClientSize = new System.Drawing.Size(440, 357);
 this.Controls.AddRange(new System.Windows.Forms.Control[] {
  this.activeLogin1,
  });
 this.Name = "Form1";
 this.Text = "Form1";
 this.ResumeLayout(false);
 }

 [STAThread]
 static void Main()
 {
 Application.Run(new Form1());
 }
  // 上面的方法分別是Windows Forms程式的入口.

 private void activeLogin1_GoodLogin(object sender, MyEventTEST.LoginEventArgs e)
 {
 MessageBox.Show("Good Login! " + e.UserID);
 }

 private void activeLogin1_FailedThreeTimes(object sender, MyEventTEST.LoginEventArgs e)
 {
 MessageBox.Show("Failed to login three times.");
 }

 private void activeLogin1_Cancel(object sender, System.EventArgs e)
 {
 MessageBox.Show("Cancel");
 }
  // 上面的三個方法(activeLogin1_GoogLogin,activeLogin1_Cancel和activeLogin1_FailedThreeTimes)
  // 就是當自定義控制元件中對應的事件被激發時在當前程式集中對應的處理方法.
  // 值得注意的是,簽名應該完全符合對應的多路委託的定義!
 }
}


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-993811/,如需轉載,請註明出處,否則將追究法律責任。

相關文章