Bubble Event Handler of the User Control for the parent control

Bubble Event Handler of the User Control for the parent control

最近剛好投入在開發Win Form程式的開發,雖然離開發Win Form程式已經好久時段了,

不過還好有些觀念的東西沒有忘記,所以最近剛好被問到一個問題:

如何把User Control裡某物件觸發的事件通知上層使用它的人,讓上層的Control可以擷取到裡面的內容?

 

關於這個問題,我想對於.NET Framework中的事件模型運作的流程比較熟悉的話,不難解決這個問題,

其實我自己在撰寫Transport Hub Lite時,也有透過Silverlight實作相似的功能,因此,以下先解釋一下Event Handler。

 

Event Handler

要了解Event Handler在整個事件處理過程扮演的角色為何,一定要先了解整個.NET Framework在事件模型(Event Model)的運作機制。

 

在事件運作裡,主要有二個角色:事件發行者(Publisher)與事件訂閱者(Subscriber)。事件發行者,通常識為引發(Raise)事件的類別,

而負責處理(Handle)事件的類別識為事件訂閱者。舉例來說:通常在Form裡針對TextBox註冊一個TextChanged的事件時,如果沒有特

別針對該事件指定其他Event Handler來處理,則該Form則具有二者身份。

 

根據MSDN針對事件(Event)的概觀定義,如下:

  • 發行者會判斷引發事件的時間,而訂閱者則決定要採取什麼動作來回應該事件

  • 一個事件可以有多個訂閱者,訂閱者可以處理來自多個發行者的多個事件

  • 沒有訂閱者的事件永遠不會被呼叫。

  • 事件通常用於對使用者的動作 (例如在圖形化使用者介面內按一下按鈕或選取功能表) 發出信號。

  • 當某事件擁有多個訂閱者時,便會在事件引發的同時叫用事件處理常式。若要以非同步方式叫用事件,請參閱以非同步的方式呼叫同步方法

  • 事件可用來讓執行緒同步。

  • 在 .NET Framework 類別庫 (Class Library) 中,事件會根據 EventHandler 委派 (Delegate) 和 EventArgs 基底類別 (Base Class)。

至於怎麼訂閱想要處理的事件呢,可以使用如下的方式:

‧訂閱事件 (使用加法指派運算子 (+=),將事件處理常式附加到事件上)

   1: publisher.RaiseCustomEvent += HandleCustomEvent;
   2: //舉例
   3: button1.Click += new EventHandler(MyEventHandler);

‧取消事件 (使用減法指派運算子 (-=) 取消訂閱事件)

   1: publisher.RaiseCustomEvent -= HandleCustomEvent;
   2: //舉例
   3: button1.Click -= MyEventHandler;

 

從上述的說明,可以了解事件整個運作的裡需要的二個角色與如何掛上更多的Handler來進行事件處理。

然而,事件是一種機制,提供讓應用程式可以針對特定的事件配合指定的程式碼來進行處理,當然,隨著事件的種類不同,

也有包括事件發生前或事件發生後的處理事件等。因此,事件模型依照著事件發行者被引發(raise)的時機,引動對應的訂閱者,

讓註冊的事件處理方法可以完。

 

那事件被引發時,事件方法是怎麼被啟動的呢?在事件被引發時,事件發行者會通知事件處理常式(Event Handler),並且事件處理常式

透過委派(delegate)的方式,將事件轉向註冊的執行方法上,完成指定的任務。然而,事件處理常式會被加入到事件中,讓它在引發事件時,

可以依據上述說明的方式呼叫指定的方法。

 

那要怎麼宣告一個事件在自訂的類別裡呢?那要先了解一個關鍵字:event:

‧event關鍵字

event關鍵字用於發行者(Publisher)類別中宣告事件。定義該發行者類別具有可對外的事件。

事件是一種特殊種類的多點傳委派(所以允許註冊多個event handler來處理指定的事件),只能從它們宣告的類別(publisher)或結構(struct)內叫用。

因此,當其他類別向該publisher類別註冊事件處理常式時,event定義的事件會將其處理常式標記起來,等到事件引入時再呼叫處理常式來運作。

另外,event關鍵字可以配合修飾詞的使用(例如:static、virtual、sealed與abstract),讓事件呈現不同的使用功能,也支援透過override的方式,

讓事件的行動更多元。

   1: //使用範例
   2: public event EventHandler gCustomEventHandler;

 

〉範例

舉個例子來說明實作的效果。假設目前有實作一個User Control(叫MyPanel),並且裡面放了一個TextBox與具有TextChanged的事件,

最後該User Control被放置於主Form中,當用戶輸入在TextBox輸入內容時,外層Form要能擷取出來。

實作說明:

(a) 在User Control宣告一個 用event關鍵字定義的Event Handler,用於定義該User Control是整個Event的發行者(Publisher);

(b) 在TextChange事件裡,透過Event Handler進行Delegate,轉換事件給由Form向User Control註冊的Event Handler;

 

‧MyPanel.cs(實作User Control,增加event關鍵字宣告的Event Handler)

   1: public partial class MyPanel : UserControl, IUserControl
   2: {
   3:     //利用event關鍵字宣告一個Event Handler,提供給外部控件註冊該Event Handler
   4:     public event EventHandler TextBox_Change;
   5:  
   6:     public MyPanel()
   7:     {
   8:         InitializeComponent();
   9:         
  10:         //為textBox1註冊TextChanged事件
  11:         this.textBox1.TextChanged += new EventHandler(this.textBox1_TextChanged);
  12:     }
  13:  
  14:     private void textBox1_TextChanged(object sender, EventArgs e)
  15:     {
  16:         //取得由event關鍵字宣告的Event Handler
  17:         EventHandler tChangeHandle = this.TextBox_Change;
  18:         if (tChangeHandle != null)
  19:         {
  20:             //利用Delegate轉出該Event給註冊於TextBox_Change的Event Handler
  21:             tChangeHandle .Invoke(sender, e);
  22:         }
  23:     }
  24: }

 

‧Form1.cs(使用MyPanel.cs,並且向MyPanel.cs提供的Event Handler進行註冊)

   1: public partial class Form1 : Form
   2: {
   3:     public Form1()
   4:     {
   5:         InitializeComponent();
   6:         Init();
   7:     }
   8:     
   9:     private void Init()
  10:     {
  11:         //實作MyPanel
  12:         MyPanel tMyPanel = new MyPanel();
  13:         tMyPanel.TextBox_Change += new EventHandler(this.ChangeTextBox);
  14:         this.Controls.Add(tMyPanel);
  15:         tMyPanel.Show();
  16:     }
  17:  
  18:     protected void ChangeTextBox(object sender, EventArgs e)
  19:     {
  20:         //取得由MyPanel中TextBox輸入的內容
  21:         string tContent = ((TextBox)sender).Text;
  22:     }
  23: }

 

在使用MyPanel時,一定要記得透過「+=」的方式,把Event Handler註冊到宣告的event中,這樣才有辦法做對應,

在執行時,當程式進入Invoke時,Event Handler即將Event內容轉發給原先註冊的Handler,在Form1裡就可以取得嘍!

---

以上是針對這次我被詢問到的問題,做一個完整的說明,剛好可以把這篇做為筆記,分享給大家。

其實在學習撰寫程式的過程中,Event Model一直是很重要的部分,因為.NET Framework針對事件的處理都會按照Event Model來進行,

到了WPF之後針對Event Handler還有RoutedEventArgs的參數可以使用,這也是蠻有趣的部分喔。

希望對大家有所幫助。

 

[補充]

〉事件設計

補充一下針對如果需要自訂事件設計時,可以參考一下MSDN上針對事件設計的一些重要觀念建議::

‧要為事件使用引發 (Raise) 用語,而不能使用引發 (Fire) 或觸發 (Trigger)。

‧要使用 System.EventHandler<T>,而不要手動建立新的委派來當做事件處理常式使用。

‧請考慮使用 System.EventArgs 的衍生類別做為事件引數,除非您絕對確信此事件絕對不需要夾帶任何資料到事件處理方法

   (這種情況下,您可直接使用 System.EventArgs 型別)。

   如果自訂事件時,需要夾帶一些特定的參數讓事件處理常式可以運作的話,可以實作EventArgs成為衍生類別來夾帶訂定的資料

‧要使用受保護的虛擬方法來引發每一個事件,但是,這只適用於非密封類別上的非靜態事件,而不適用於結構、密封類別或靜態事件。

‧要使用型別為事件引數類別的參數 (此類別是引發事件的受保護方法之類別),此參數應該命名為 e。

‧當引發非靜態事件時,請勿將 null (Visual Basic 中為 Nothing) 傳遞為傳送者參數。

‧當引發事件時,請勿將 null (Visual Basic 中為 Nothing) 傳遞為事件資料參數。

‧一定要為執行於事件處理方法中的任意程式碼做好準備。

‧請考慮引發使用者可以取消的事件,這只適用於預期事件。

 

〉多點傳送委派(組合委派)

delegates物件本身可以透過「+」或「-」運算子來將要註冊的物件(或方法)指派給一個委派執行個體,達成多點傳送委派。

意思就是說,由於事件處理常式是透過委派的方式啟動註冊於事件的處理方法,因此,當透過「+」或「-」向事件註冊處理

常式時,等到事件被引入時,會依照符合宣告的delegate型態相符的處理常式,來進行呼叫與啟動。

 

References:

C# WinForms UserControl Mouse Event Help

The .NET Event Model - Part 1 (必讀)

EventHandler 委派 & event (C# 參考) & 事件 (C# 程式設計手冊)

Delegates and Events in C#/.NET (必讀)

ASP.NET Web Server Control Event Model

Implementing Delegation-Event Model in C#, similar to that of Delegation-Event model of Java

HOW TO:發行符合 .NET Framework 方針的事件 (C# 程式設計手冊)

HOW TO:訂閱及取消訂閱事件 (C# 程式設計手冊)

 

Dotblogs Tags: