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在整個事件處理過程扮演的角色為何,一定要先了解整個.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# 程式設計手冊)