事件說明
什麼是事件
事件是物件的一種機制,當物件在某種條件狀況下,該事件就會被引發。若客戶端程式有訂閱這個事件,該物件就會送出事件引發訊息。
傳送事件的類別稱為發行者(Publisher)-> Raise Event
接收事件的類別稱為訂閱者(Subscriber)-> Handle Event
事件是物件的一種機制,比較學術的說法則是,事件是一種特殊的委派方法。通常可由二個方面來看事件:發行者和訂閱者。
- 發行者(Publisher):引發事件的類別 (Raise Event Class)
- 訂閱者(Subscriber):接收事件的類別 (Handle Event Class)
委派與事件的關係
當設計一個事件時,我們並不知道訂閱者會用哪一段程式碼來處理事件。 所以在設計事件時,就必須透過委派的協助,利用委派幫我們指向事件要處理的方法(函式)。 所以:
- 發行者在宣告事件的時候,其回傳型別就必須是一個委派型別。
- 訂閱者在訂閱事件的時候,其處理函式就必須與該委派具有相同的簽名(Signature:也就是要有相同的回傳值和參數列)。
事件的宣告
[存取修飾詞] delegate 回傳型別 委派名稱 ([參數列]); [存取修飾詞] event 委派名稱 事件名稱;
//宣告一個委派 public delegate void SampleEventHandler(object sender, SampleEventArgs e); //宣告一個事件 public event SampleEventHandler SampleEvent; //指明以 SampleEventHandler 這個委派型別為基礎
觸發事件
觸發事件是發行者要做的事,要注意的是,當程式碼要引發事件的時候,必須先判斷該事件值是否為 null ,因為訂閱者不見得有訂閱這個事件,所以必須用 null 判斷。
if (SampleEventHandler != null) SampleEventHandler (this, e);
訂閱事件
訂閱事件是訂閱者要做的事,主要可分成二個部份:
Step1:撰寫事件處理函式:這個函式的簽章 (Signature) 必須與事件的委派簽章相符 (相同的回傳值,相同的參數列)。
void HandleSampleEvent(object sender, CustomEventArgs a) { // Do something useful here. }
Step2:訂閱事件:在適當的地方,撰寫訂閱事件程式碼。
// 1) 訂閱事件 // 使用加法指派運算子 (+=) 將事件處理常式附加到事件上 publisher.SampleEvent += HandleSampleEvent; // 2) 取消訂閱事件 // 使用減法指派運算子 (-=) 移除附加在事件上的事件處理常式 publisher.SampleEvent -= HandleSampleEvent;
實例演練一
下面範例我們定義了一個類別,這個類別有一個事件 MyWarningEvent ,一個屬性值 Count 。 透過 Increment 和 Decrement 方法可以增減 Count 值。 當 Count 值大於 10 或小於 0 時,則引發 MyWarningEvent 事件。
傳送事件的類別(Publisher)
public class MyEventArgs : EventArgs { public int Count = 0; public MyEventArgs(int _count) { Count = _count; } } public class MyClass { private int Count = 0; public delegate void MyEventHandle(object sender, MyEventArgs e); public event MyEventHandle MyWarningEvent; //==================================================================== // 將事件引發包裹在一個 protected virtual method 中 // 這樣子它的衍生類別才能覆寫這個方法中的行為 // 否則像下方的 Decrement 方法中,直接在該方法中將事件引發即可 //==================================================================== protected virtual void RaiseMyEvent(int _count) { if (MyWarningEvent != null) { MyWarningEvent.Invoke(this, new MyEventArgs(Count)); } } public void Increment() { Count++; if ((Count > 10) || (Count < 0)) //引發事件 { RaiseMyEvent(Count); } } public void Decrement() { Count--; if ((Count > 10) || (Count < 0)) //引發事件 { if (MyWarningEvent != null) { MyWarningEvent.Invoke(this, new MyEventArgs(Count)); } } } }
接收事件的類別(Subscriber)
MyClass obj = new MyClass(); //事件處理方法 private void On_MyEvent(object sender, MyEventArgs e) { Console.WriteLine(e.Count); } //訂閱事件 private void button1_Click(object sender, EventArgs e) { obj.MyWarningEvent += On_MyEvent; } //取消訂閱事件 private void button2_Click(object sender, EventArgs e) { obj.MyWarningEvent -= On_MyEvent; } private void btnIncrement_Click(object sender, EventArgs e) { obj.Increment(); } private void btnDecrement_Click(object sender, EventArgs e) { obj.Decrement(); }
實例演練二
下面範例我們設計了一個使用者自訂控制項,它具有二個事件:小偷入侵、發生火災。同時我們用二個按鈕,分別來摸擬當這二個事件的發生。
傳送事件的類別(Publisher)
public delegate void Alarm_Handler(); //宣告事件的 Delegate 型別 public partial class ucAlarm : UserControl { public event Alarm_Handler ThiefBreakInto; //宣告事件 ThiefBreakInto 小偷入侵 public event Alarm_Handler Fire; //宣告事件 FireOn 火燒 public ucAlarm() { InitializeComponent(); btnThief.Click += new System.EventHandler(this.On_ThiefBreakInto); //摸擬發生小偷入侵事件 btnFire.Click += new System.EventHandler(this.On_Fire); //摸擬發生火災事件 } private void On_Fire(object sender, EventArgs e) { if (Fire != null) //當事件發生時,觸發"火災"事件 Fire.Invoke(); } private void On_ThiefBreakInto(object sender, EventArgs e) { if (ThiefBreakInto != null) //當事件發生時,觸發"小偷入侵"事件 ThiefBreakInto.Invoke(); } }
接收事件的類別(Subscriber)
訂閱者使用加法指派運算子 (+=) 訂閱事件,一個事件是可以同時委派多個處理方法的。
public partial class Form2 : Form { public Form2() { InitializeComponent(); //1.1) 訂閱ThiefBreakInto事件 alarm1.ThiefBreakInto += alarm1_ThiefBreakInto; //使用加法指派運算子 (+=) 將事件處理常式附加到事件上 //1.2) 訂閱Fire事件 alarm1.Fire += alarm1_Fire; // 再訂閱Fire事件,執行 Call_Boss (一個事件是可以委派多個方法的) alarm1.Fire += Call_Boss; } //定義事件處理常式方法,其簽章 (Signature) 與事件的委派簽章相符。 private void alarm1_ThiefBreakInto(DateTime dt) { Console.WriteLine("Call 110 : " + System.DateTime.Now.ToString()); } private void alarm1_Fire(DateTime dt) { Console.WriteLine("Call 119 : " + System.DateTime.Now.ToString()); } private void Call_Boss(DateTime dt) { Console.WriteLine("Call Boss : " + System.DateTime.Now.ToString()); } } //結果顯示 //Call 110 : 2012/8/8 上午 09:51:12 //Call 119 : 2012/8/8 上午 09:51:15 //Call Boss : 2012/8/8 上午 09:51:15
上例補充說明:
我們在上面例子中,針對 ThiefBreakInto 這個事件,總共加入了2個處理方法。
當委派的方法被 invoke 時,會依方法加入的先後順序執行,如上例的結果顯示。
如果被呼叫的方法有回傳值的話,那麼上面的做法就只能取到最後一個方法的回傳值。
若需求狀況有必要取得所有方法的回傳值的話,就必須利用 GetInvocationList 方法,取得委派的引動清單後,再分開 invoke 每一個方法,這樣就可以取得每一次的回傳值。
//利用 GetInvocationList ,分開invoke private void On_ThiefBreakInto(object sender, EventArgs e) { if (ThiefBreakInto != null) { foreach (Alarm_Handler handler in ThiefBreakInto.GetInvocationList()) { handler(); } } }
沒有留言:
張貼留言