在事件(消息)通讯中,负责事件发起的类对象并不知道哪个对象或方法会接收和处理(handle)这一事件。这就需要一个中介者(类似指针处理的方式),在事件发起者与接收者之间建立关联。在.NET Framework中,定义了一个特殊的类型(delegate),来提供类似C++的函数指针的功能。本文通过一个定义和使用简单自定义事件的例子,对.NET的事件处理机制加以分析,以加深对事件处理原理的理解。
如图所示,使用自定义事件,需要完成以下步骤:
1、声明(定义)一个委托类(型),或使用.NET程序集提供的委托类(型);
2、在一个类(事件定义和触发类,即事件发起者sender)中声明(定义)一个事件绑定到该委托,并定义一个用于触发自定义事件的方法;
3、在事件响应类(当然发起和响应者也可以是同一个类,不过一般不会这样处理)中定义与委托类型匹配的事件处理方法;
4、在主程序中订阅事件(创建委托实例,在事件发起者与响应者之间建立关联)。
5、在主程序中触发事件。
如按钮点击事件,就是用户在程序界面点击按钮控件时由按钮对象发出的消息,我们可以在界面程序中定义按钮点击事件处理方法来响应这一消息。这里就使用了委托处理机制。
一、委托的定义和使用
委托(委派)的声明(定义)格式如下所示:
public delegate void MyDelegateClass(string message);
其中delegate为委托类型关键字,MyDelegateClass是我们所定义的委托类的名称。委托类型类似C++的函数指针,而且是类型安全的函数指针,如同C++的回调函数(CALLBACK)。委托(委派)类型有一个签名(或称识别标志,signature),只有与签名特征匹配的方法才可以通过委托类型进行委派。
从上面的定义中,可以看出我们定义的MyDelegateClass类的签名特征,即只要是输入参数为string,返回类型为void的方法都可以通过MyDelegateClass类进行指派。有了这一行定义语句,不需要我们再干什么,.NET编译环境就会自动为我们生成委托类MyDelegateClass,并允许我们通过类似MyDelegateClass delegateObj = new MyDelegateClass(对象名.方法名)的方式创建委托实例,添加与该实例关联的方法引用。.NET是如何做的呢?
实际上,.NET在编译时,是根据我们的委托声明语句,为我们创建继承自System.MulticastDelegate(抽象类,其根类为System.Delegate)的委托类。Delegate类具有Target和Method两个类似指针的(引用)属性,分别指向所引用的对象及其方法的地址,这样,我们在使用委托类实例时实际上就是在调用对应的对象方法。而且,Delegate类可以引用多个对象方法,利用其“+=”操作符,通过类似delegateObj += new MyDelegateClass(对象名.方法名)的语句,可以为委托类对象实例delegateObj添加多个方法引用,这些方法引用被保存在委托类的委托列表中,在使用委托类实例时,这些方法都会被调用。
如果需要,我们可以通过Delegate类的GetInvocationList()取出这些委托,并查看其Target和Method属性,获取所引用的方法名等信息。
下面以一个简单例子来演示一下委托类型的定义和使用。
1、创建一个目标类极其方法,提供给委托类型使用。
//TargetClass.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace Delegatete_EventTest
{
//也可以创建单独的类文件
public class TargetClass
{
public static void Method1(string message1)
{
Console.WriteLine("调用了目标方法1,参数:" + message1);
}
public void Method2(string message2)
{
Console.WriteLine("调用了目标方法2,参数:" + message2);
}
}
}
2、在主程序中定义并使用委托类型。如图所示为程序中定义的委托类(包括其基类)的类视图:
//DeleGateExample.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace Delegatete_EventTest
{
class DeleGateExample
{
//定义委托类型
public delegate void MyDelegateClass(string message);
//主程序方法
static void Main(string[] args)
{
//Test1();
//Test2();
Test3();
}
//测试1(仅为委托实例指派了一个目标方法)
static void Test1()
{
//定义委托实例,并指派(关联)目标方法(注意是目标类的静态方法)
MyDelegateClass delegateObj = new MyDelegateClass(TargetClass.Method1);
//运行委托实例(调用目标方法)
delegateObj("just a test");
//显示委托实例所关联的目标类极其方法
Console.WriteLine("目标对象及方法:" + delegateObj.Target + ","
+ delegateObj.Method);
}
//测试2(如果不通过+=操作符而指派第二个目标方法,会覆盖掉第一个目标方法关联)
static void Test2()
{
//定义委托类对象实例,并指派第一个目标方法(目标类静态方法)
MyDelegateClass delegateObj = new MyDelegateClass(TargetClass.Method1);
//为委托实例指派第二个目标方法(目标类对象方法)
TargetClass targetobj = new TargetClass();
delegateObj = new MyDelegateClass(targetobj.Method2);
//运行委托实例(调用目标方法)
delegateObj("just a test");
//显示委托列表包含的目标方法个数
Console.WriteLine("该委托实例的目标方法个数:"
+ delegateObj.GetInvocationList().Length);
//显示委托实例的目标类极其方法名称
Console.WriteLine("目标对象及方法:" + delegateObj.Target + ","
+ delegateObj.Method);
}
//测试3(委托调用及委托列表显示)
static void Test3()
{
//定义委托对象实例,并关联第一个目标方法(目标类的静态方法)
MyDelegateClass delegateObj = new MyDelegateClass(TargetClass.Method1);
//使用+=操作符为委托实例添加第二个目标方法(目标类对象方法)
TargetClass targetobj = new TargetClass();
delegateObj += new MyDelegateClass(targetobj.Method2);
//运行委托实例(调用目标方法)
//delegateObj.Invoke("just a tets");
delegateObj("just a tets");
//调用委托列表显示方法
DisplayDeObjList(delegateObj);
}
//委托列表的显示方法(逐一显示委托列表所包含的目标类极其方法名称)
static void DisplayDeObjList(MyDelegateClass delegateObj)
{
//显示委托列表包含的目标方法个数
Console.WriteLine("该委托实例的目标方法列表中存在" +
delegateObj.GetInvocationList().Length+"个目标方法,分别是:");
//逐一显示委托列表中所指派的目标类极其方法名称
for (int i = 0; i < delegateObj.GetInvocationList().Length; i++)
{
MyDelegateClass deObj = (MyDelegateClass)delegateObj.GetInvocationList()[i];
Console.WriteLine("目标对象及方法:" + deObj.Target + "," + deObj.Method);
}
}
}
}
二、自定义事件的定义与处理
1、在事件发起者类中定义事件:
//EventSenderClass.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace Self_DefinedEvent
{
//声明一个委托类(定义为公共类型,以便外部代码使用)
public delegate void MyEventDelegate(string aMessage);//参数为提示信息
class EventSenderClass
{
//定义一个事件属性
public event MyEventDelegate selfEvent;
//定义一个激发自定义事件的方法
public void RaiseSelfDefinedEvent()
{
//事件是否被订阅(被实例化),如果未订阅,MessageArrived就是null,不会引发事件
if (selfEvent != null)
selfEvent("Self-Defined event is raised.");
}
}
}
2、在事件接收与处理类中定义事件处理方法
//EventHandlerClass.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace Self_DefinedEvent
{
public class EventHandlerClass
{
//定义接收消息的公共属性
public string receivedMessage;
//自定义事件的处理方法
public void ReceiveAndDisplayMessage(string message)
{
receivedMessage = "自定义事件被响应,事件消息为:" + message;
}
}
}
3、本例基于窗口应用,把窗口(Form)类作为自定义事件处理的主程序。在初始化窗口对象时执行自定义事件的订阅,即为自定义事件添加负责事件接收和处理的对象方法(语法与前面例子中添加委托实例的目标方法相同);在窗口类中添加了一个按钮和一个标签控件,并把自定义事件的触发放在了按钮点击处理方法中。点击按钮,自定义事件被触发,并使用标签控件输出事件响应信息。
//Form1.cs
using System;
......
using System.Windows.Forms;
namespace Self_DefinedEvent
{
public partial class Form1 : Form
{
EventSenderClass myEventSender;
EventHandlerClass myEventHandler;
public Form1()
{
InitializeComponent();
myEventSender = new EventSenderClass();
myEventHandler = new EventHandlerClass();
//订阅(实例化)自定义事件
myEventSender.selfEvent +=
new MyEventDelegate(myEventHandler.ReceiveAndDisplayMessage);
}
//按钮点击处理方法
private void button1_Click(object sender, EventArgs e)
{
//触发自定义事件
myEventSender.RaiseSelfDefinedEvent();
label1.Text = label1.Text + myEventHandler.receivedMessage;
}
}
}
4、以按钮为例,理解.NET的事件处理方式
实际上,.NET的控件事件处理方式正是采用了前面所讲的自定义事件的处理机制。以上例中的按钮事件处理为例,打开Form1.Designer.cs,可以找到按钮事件的订阅语句:
this.button1.Click += new System.EventHandler(this.button1_Click);
解析一下这个语句,“Click”是System.Windows.Forms.Button按钮类的事件属性,button1_Click是处理按钮事件的目标方法名,System.EventHandler则是.NET已定义好的用于事件处理的委托类型。这是.NET事件订阅的典型语法。
5、动态控件的定义和使用
在实际项目中有时事先并不知道程序界面中需要哪些控件,需要几个,这时就需要根据不同的条件动态生成不同的控件并使用。这里我们仅以一个简单例子加以说明。
在上面的Windows界面应用程序中添加一个界面类Form2.cs:
......
namespace Self_DefinedEvent
{
public partial class Form2 : Form
{
public Form2()
{
InitializeComponent();
Button but1 = new Button();
but1.Text = "动态按钮";
but1.Click += new EventHandler(this.but1_Click);
this.Controls.Add(but1);
}
//动态按钮处理方法
private void but1_Click(object sender, EventArgs e)
{
Label lb = new Label();
//设置标签位置,实际应用中要涉及到界面布局,如利用动态表格设置控件位置等。
lb.Location = new System.Drawing.Point(0, 30);
lb.Size = new System.Drawing.Size(200,10);
this.Controls.Add(lb);
lb.Text = "Button is clicked.";
请发表评论