簡介
任何編寫過圖形化使用者介面(GUI)軟體的開發人員都熟悉事件處理編程,當使用者與GUI控制進行互動時(例如點擊表格上的按鈕),作為上述事件的反應,就會執行一個或多個方法。沒有使用者的參與,事件也可能執行。事件處理常式是對象的方法,是根據應用程式中發生的事件而執行的。為了理解.Net架構下的事件處理模式,我們需要理解代理的概念。
C#中的代理
C#中的代理允許我們將一個類中的方法傳遞給其他類的對象。我們能夠將類A中的方法m封裝為一個代理,傳遞給類B,類B能夠調用類A中的方法m,靜態和執行個體方法都可以傳送。C++軟體開發人員應該對這一概念非常熟悉,在C++中,開發人員能夠以參數的形式使用函數指標將函數傳遞給同理個類或其他類中的方法。代理的概念是在Visulal J++中引入的,然後又被帶到了C#中。在.Net架構中C#的代理是以從System.Delegate中繼承的類的形式實現的。使用代理需要4個步驟:
1、定義一個輸入參數與要進行封裝的方法完全相同的代理對象。
2、定義所有輸入參數與在第1步中定義的代理對象相同的方法。
3、建立代理對象,並與希望封裝的方法進行串連。
4、通過代理對象調用封裝的方法。
下面的C#代碼通過實現一個代理、4個類舉例說明了上面的4個步驟:
using System;
//步驟1:定義一個具有被封裝方法輸入參數的代理對象
public delegate void MyDelegate(string input);
//步驟2:定義與定義的代理對象具有相同輸入參數的方法
class MyClass1{
public void delegateMethod1(string input){
Console.WriteLine("This is delegateMethod1 and the input to the method is {0}",input);
}
public void delegateMethod2(string input){
Console.WriteLine("This is delegateMethod2 and the input to the method is {0}",input);
}
}
//步驟3:建立代理對象,插入方法
class MyClass2{
public MyDelegate createDelegate(){
MyClass1 c2=new MyClass1();
MyDelegate d1 = new MyDelegate(c2.delegateMethod1);
MyDelegate d2 = new MyDelegate(c2.delegateMethod2);
MyDelegate d3 = d1 + d2;
return d3;
}
}
//步驟4:通過代理調用被封裝的方法。
class MyClass3{
public void callDelegate(MyDelegate d,string input){
d(input);
}
}
class Driver{
static void Main(string[] args){
MyClass2 c2 = new MyClass2();
MyDelegate d = c2.createDelegate();
MyClass3 c3 = new MyClass3();
c3.callDelegate(d,"Calling the delegate");
}
}
C#中的事件處理常式
C#中的事件處理常式是一個帶有特定輸入參數的代理,如下所示:
public delegate void MyEventHandler(object sender, MyEventArgs e);
上面定義中的第一個參數(sender)指定了發生事件的對象,第二個參數(e)儲存著在事件處理常式中要用到的資料。MyEventArgs類是繼承EventArgs類得來的,EventArgs類是MouseEventArgs、ListChangedEventArgs等更專業化的類的基礎類。對於GUI事件,我們可以使用這些特定的EventArgs類的對象,而無需自己建立特定的EventArgs類。然而,對於非GUI事件而言,我們仍然需要建立自己的特定的EventArgs類,儲存希望向代理對象傳遞的資料。我們可以通過繼承EventArgs類建立自己特定的EventArgs類:
public class MyEventArgs EventArgs{
public string m_myEventArgumentdata;
}
在事件處理常式中,代理對象的調用需要用到event關健字,如下所示:
public event MyEventHandler MyEvent;
下面我們將建立二個類,體會.Net架構中事件處理機制的工作原理。在對代理的討論的第二個步驟中,要求我們定義與定義的代理有完全相同的輸入參數的方法。在我們的例子中,類A將提供事件處理常式(與代理對象具有相同輸入參數的方法。),它將建立代理對象(對代理討論中的第三步)並安裝事件處理常式。類A然後會將代理對象傳遞給類B。當類B中有事件出現時,它就會執行類A中的事件處理常式方法。
using System;
//步驟1:建立代理對象
public delegate void MyHandler1(object sender,MyEventArgs e);
public delegate void MyHandler2(object sender,MyEventArgs e);
//步驟2:建立事件處理常式方法
class A{
public const string m_id="Class A";
public void OnHandler1(object sender,MyEventArgs e){
Console.WriteLine("I am in OnHandler1 and MyEventArgs is {0}", e.m_id);
}
public void OnHandler2(object sender,MyEventArgs e){
Console.WriteLine("I am in OnHandler2 and MyEventArgs is {0}", e.m_id);
}
//步驟3:建立代理,安裝事件處理常式,並向啟動事件的對象註冊。
public A(B b){
MyHandler1 d1=new MyHandler1(OnHandler1);
MyHandler2 d2=new MyHandler2(OnHandler2);
b.Event1 +=d1;
b.Event2 +=d2;
}
}
//步驟4:通過代理調用封裝的方法。
class B{
public event MyHandler1 Event1;
public event MyHandler2 Event2;
public void FireEvent1(MyEventArgs e){
if(Event1 != null){
Event1(this,e);
}
}
public void FireEvent2(MyEventArgs e){
if(Event2 != null){
Event2(this,e);
}
}
}
public class MyEventArgs EventArgs{
public string m_id;
}
public class Driver{
public static void Main(){
B b= new B();
A a= new A(b);
MyEventArgs e1=new MyEventArgs();
MyEventArgs e2=new MyEventArgs();
e1.m_id ="Event args for event 1";
e2.m_id ="Event args for event 2";
b.FireEvent1(e1);
b.FireEvent2(e2);
}
}
C#中的GUI事件處理
Windows Forms(支援GUI應用程式的.NET架構)中的事件處理使用.NET事件處理模式。我們下面將應用這種模式編寫一個簡單的應用程式,該應用程式有一個繼承自System.Windows.Forms.Form類的MyForm類。如果仔細地研究一下代碼和其中的三行注釋,就會發現其實我們無須定義代理和使用event關健字調用這些代理,因為我們已經可以使用GUI控制(表格、按鈕等)的事件(滑鼠點擊等),代理就是System.EventHandler。當然了,我們仍然需要定義方法,建立代理對象(System.EventHandler),並在代理對象中安裝方法,一旦有事件發生,方法就會開始執行。
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
public class MyForm Form{
private Button m_nameButton;
private Button m_clearButton;
private Label m_nameLabel;
private Container m_components = null;
public MyForm(){
initializeComponents();
}
private void initializeComponents(){
m_nameLabel=new Label();
m_nameButton = new Button();
m_clearButton = new Button();
SuspendLayout();
m_nameLabel.Location=new Point(16,16);
m_nameLabel.Text="Click NAME button, please";
m_nameLabel.Size=new Size(300,23);
m_clearButton.Location=new Point(16,152);
m_clearButton.Size=new Size(176,23);
m_clearButton.Text="CLEAR";
//建立代理,並安裝方法,將代理捆綁在按鈕的Click事件上
m_clearButton.Click += new System.EventHandler(ClearButtonClicked);
this.ClientSize = new Size(292, 271);
this.Controls.AddRange(new Control[] {m_nameLabel,m_nameButton,m_clearButton});
this.ResumeLayout(false);
}
//定義輸入參數與代理完全相同的方法
private void NameButtonClicked(object sender, EventArgs e){
m_nameLabel.Text="My name is john, please click CLEAR button to clear it";
}
private void ClearButtonClicked(object sender,EventArgs e){
m_nameLabel.Text="Click NAME button, please";
}
public static void Main(){
Application.Run(new MyForm());
}
}
結束語
Java和Smalltalk等其他的物件導向的程式設計語言中都沒有代理的概念,這一概念是在C#中新引進的,它源自於C++和J++。我希望上面的討論使第一次使用物件導向的程式設計語言時就使用C#語言的編程人員能夠弄明白代理的概念。如果使用Visual Studio IDE作為C# GUI的開發環境,無需編寫代碼就能夠將代理中的方法與由GUI控制產生的事件(例如用滑鼠點擊按鈕等)串連在一起,當然了,我們還是應當知道這是怎麼回事