博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

事件

Posted on 2008-06-06 10:41  郭子  阅读(197)  评论(0编辑  收藏  举报
 对于“事件”这个词,C#的帮助从各个角度进行了描述,比如“事件是类在发生其关注的事情时用来提供通知的一种方式”,“事件是可以通过代码响应或“处理”的操作”,“事件是操作发生时允许执行特定于应用程序的代码的机制”,“事件是对象发送的消息,以发信号通知操作的发生”,“事件是当对象发生用户关心的情况时,类将此对象通知用户的方法”等等,概括起来就是一句话“事件是软件或硬件发生的某些事情,它要求应用程序的响应”。

======================
一 不包含附加信息的事件
======================
1 定义事件委托

要使用事件首先要定义一个与事件相关的委托,因为事件发生后一定要有某个类中的方法来处理它,而委托正是由对象引用以及该对象的方法引用组成的,所以委托能够将事件绑定到用来处理它们的方法上。

★ 事件委托准则:

.NET Framework 指南指示用于事件的委托类型应采用两个参数:“对象源”参数(用于指示事件源)和特定于事件的参数(它封装有关事件的其他任何信息)。特定于事件的参数应从 EventArgs 类派生。对于不使用任何附加信息的事件,.NET Framework 提供了 EventHandler 类。

准则告诉我们用于事件的委托一定要有两个参数,第一个参数用于指示事件源,第二个参数用于指示特定事件,所谓特定事件指的是具有附加信息的事件和没有附加信息的事件,如果使用没有附加信息的事件,委托参数就使用内置的EventHandler类的形式。

★ EventHandler

EventHandler 是命名空间System中的一个预定义的委托,是专用于表示不生成数据的事件的事件处理程序方法。

语法

public delegate void EventHandler (
 Object sender,
 EventArgs e
);

参数
sender:事件源,类型为 Object,用于引发事件的实例。
e:不包含任何事件数据的 EventArgs。

解读:

语法给出了事件委托的基本形式,其中EventHandler是一个委托的名称,可以根据习惯任意更改,第一个参数中的Object表示数据类型,sender是使用该类型创建的实例名,第二个参数EventArgs是一个内置类,e 是该类的一个实例。

通过上面的学习,我们懂得了如何为事件定义一个委托,下面就定义这样一个委托。

//定义委托
public delegate void TestEventDelegate(object sender, System.EventArgs e);

2 声明事件

事件和方法一样具有签名,签名包括名称和参数列表。事件的签名使用委托的签名来表示。

声明事件需要使用 event 关键字。

语法

修饰符 event 委托名称 事件名称

public event TestEventDelegate TestEvent;

事件要放在引发事件的类中,定义一个事件引发类。

//定义事件引发类
public class EventSource
{
    public event TestEventDelegate TestEvent;
    private void RaiseTestEvent() { /* ... */ }
}

3 引发事件

若要引发事件,类可以调用委托,并传递所有与事件有关的参数。

private void RaiseTestEvent()
{
    TestEventDelegate temp = TestEvent;

    if (temp != null)
    {
        temp(this, new System.EventArgs());
    }
}

4 订阅事件

要接收某个事件的类可以创建一个方法来接收该事件,然后向类事件自身添加该方法的一个委托。这个过程称为“订阅事件”。

首先,接收类必须具有与事件自身具有相同签名(如委托签名)的方法。然后,该方法(称为事件处理程序)可以采取适当的操作来响应该事件。

//定义事件接收类
public class EventReceiver
{
    //定义事件处理方法
    public void ReceiveTestEvent(object sender, System.EventArgs e)
    {
        System.Console.Write("Event received from ");
        System.Console.WriteLine(sender.ToString());
    }
}

若要订阅事件,接收器必须创建一个与事件具有相同类型的委托,并使用事件处理程序作为委托目标。然后,接收器必须使用加法赋值运算符 (+=) 将该委托添加到源对象的事件中。

//事件订阅方法
public void Subscribe(EventSource source)
{
    TestEventDelegate temp = ReceiveTestEvent;
    source.TestEvent += temp;
}

若要取消订阅事件,接收器可以使用减法赋值运算符 (-=) 从源对象的事件中移除事件处理程序的委托。例如:

//取消订阅方法 
public void UnSubscribe(EventSource source)
{
    TestEventDelegate temp = ReceiveTestEvent;
    source.TestEvent -= temp;
}

5 整合

前面的代码不会运行,必需在入口方法中将他们整合。

 static void Main()
    {
        EventReceiver receiver = new EventReceiver();
        EventSource source = new EventSource();
        receiver.Subscribe(source);
        source.RaiseTestEvent();
    }

完整代码

//定义委托
public delegate void TestEventDelegate(object sender, System.EventArgs e);
//事件引发类
public class EventSource
{
    //声明事件
    public event TestEventDelegate TestEvent;
    //事件引发方法
    private void RaiseTestEvent()
    {
        TestEventDelegate temp = TestEvent;

        if (temp != null)
        {
            temp(this, new System.EventArgs());
        }
    }

    static void Main()
    {
        EventReceiver receiver = new EventReceiver();
        EventSource source = new EventSource();
        receiver.Subscribe(source);
        source.RaiseTestEvent();
    }

}

//事件接收类
public class EventReceiver
{
    //事件处理方法
    public void ReceiveTestEvent(object sender, System.EventArgs e)
    {
        System.Console.WriteLine("Event received from");
        System.Console.WriteLine(sender.ToString());
    }
    //事件订阅方法
    public void Subscribe(EventSource source)
    {
        TestEventDelegate temp = ReceiveTestEvent;
        source.TestEvent += temp;
    }
}

事件使用过程小结:

1 定义委托
2 定义事件引发类
(1)定义事件
(2)定义引发方法,在这个方法中,将事件分配给一个委托实例,然后调用委托。
      
3 定义事件接收类
(1)定义事件处理方法
(2)定义事件订阅方法,在这个方法中,将事件处理方法分配给委托的一个实例,然后将委托绑定到事件。      

=======================
二 包含附加信息的事件
=======================

如果使用包含附加信息的事件,委托的第二个参数应从 EventArgs 类派生。vs2005对EventArgs 类有如下说明:

★ EventArgs

EventArgs 是包含事件数据的类的基类。此类不包含事件数据,在事件引发时不向事件处理程序传递状态信息的事件会使用此类。如果事件处理程序需要状态信息,则应用程序必须从此类派生一个类来保存数据。

下面我们以台风登陆事件为例来说明,06年第8号超强台风“桑美”,中心风力17级,以每小时20公里的速度向我国东部沿海袭来,登陆地点是福建和浙江。
 
★ 定义信息保存类

台风登陆时需要说明登陆地点和风力,因此设计一个类用来保存这两项数据。

using System;
public class Typhoonclass : EventArgs
{
    public string place;
    public int level;
    public Typhoonclass(string place, int level)
    {
        this.place = place;
        this.level = level;
    }
}

★ 事件引发类

public delegate void TyphoonDelegate
(object sender,Typhoonclass e);
public class EventSource
{  
    public event TyphoonDelegate TyphoonEvent;
    public void Raise(string place, int level)
   {
      TyphoonDelegate temp = TyphoonEvent;

       if (temp != null)
     {
      temp(this, new Typhoonclass(place,level));
     }
   }
}

★ 事件接收类

public class EventReceiver
{
      public void EventHandling(object sender,Typhoonclass e)
    {
        Console.WriteLine("订阅方法被" +
               sender.ToString() + "调用。");
        if (e.level < 5)
            Console.WriteLine("登陆" + e.level +
                "的风力一般,要稍加防范");
        else if (e.level < 10)
            Console.WriteLine("登陆" + e.level +
                "的风力较大,船只回港避风");
        else
            Console.WriteLine("登陆" + e.level +
                "的风力很大,船只回港,人员疏散避风");
    }

   public void Subscribe(EventSource source)
    {
        TyphoonDelegate temp  = EventHandling;
        source.TyphoonEvent += temp;
    }
}

public class Mainclass
{
    public static void Main()
    {
    EventSource source = new EventSource();
    EventReceiver receiver = new EventReceiver();
    receiver.Subscribe(source);
    source.Raise("浙江",7);

    }
}

//=============完整代码=====================
using System;
public delegate void TyphoonDelegate(object sender, Typhoonclass e);
public class Typhoonclass : EventArgs
{
    public string place;
    public int level;
    public Typhoonclass(string place, int level)
    {
        this.place = place;
        this.level = level;
    }
}

public class EventSource
{  
    public event TyphoonDelegate TyphoonEvent;
    public void Raise(string place, int level)
   {
      TyphoonDelegate temp = TyphoonEvent;
       if (temp != null)
     {
      temp(this, new Typhoonclass(place,level));
     }
   }
}

public class EventReceiver
{
    public void EventHandling(object sender, Typhoonclass e)
    {
        if (e.level < 5)
            Console.WriteLine("登陆‘" +e.place+"’的风力为:" +e.level +"级,要稍加防范");
        else if (e.level < 10)
            Console.WriteLine("登陆‘" +e.place+"’的风力为:" +e.level +"级,船只要回港避风");
        else
            Console.WriteLine("登陆‘" +e.place+"’的风力为:" +e.level +"级,船只回港人员紧急疏散");
    }

   public void Subscribe(EventSource source)
    {
        TyphoonDelegate temp  = EventHandling;
        source.TyphoonEvent += temp;
    }
}

public class Mainclass
{
    public static void Main()
    {
    EventSource source = new EventSource();
    EventReceiver receiver = new EventReceiver();
    receiver.Subscribe(source);
    source.Raise("上海", 3);
    source.Raise("浙江",8);
    source.Raise("福建", 12);
    }
}

======================
三 匿名方法
======================

在下面的示例中,类 TestButton 包含事件 OnClick。派生自 TestButton 的类可以选择响应 OnClick 事件,并且定义了处理事件要调用的方法。可以以委托和匿名方法的形式指定多个处理程序,匿名方法仅适用于C#2.0语法。

//--------------匿名方法1------------------
public delegate void ButtonEventHandler();
class TestButton
{
    public event ButtonEventHandler OnClick;
    public void Click()
    {
        OnClick();
    }
}

class myclass:TestButton
{
static void Main()
    {
        TestButton mb = new TestButton();
        mb.OnClick += delegate { System.Console.WriteLine("Hello, World!"); };
        mb.Click();  
    }
}

//--------------匿名方法2------------------
public delegate void ButtonEventHandler();
class TestButton
{
    public event ButtonEventHandler OnClick;
    public void Click()
    {
        ButtonEventHandler mb = OnClick;
        mb();
    }
}

class myclass : TestButton
{
    static void Main()
    {
        TestButton sb = new TestButton();
        ButtonEventHandler mb = delegate{System.Console.WriteLine("Hello, World!");};
        sb.OnClick += mb;
        sb.Click();
    }
}

======================
四 在接口中声明事件
======================
此示例说明可以在接口中声明一个事件,然后在类中实现它。

public delegate void TestDelegate();
public interface ITestInterface
{
    event TestDelegate TestEvent;
    void FireAway();
}

public class TestClass : ITestInterface
{
    public event TestDelegate TestEvent;
    public void FireAway()
    {
        if (TestEvent != null)
        {
            TestEvent();
        }
    }
}

public class MainClass
{
    static private void F()
    {
        System.Console.WriteLine("This is called when the event fires.");
    }
    static void Main()
    {
        ITestInterface i = new TestClass();
        i.TestEvent += F;
        i.FireAway();
    }
}

======================
五 事件访问器
======================

可以使用事件访问器声明事件。事件访问器使用的语法非常类似于属性访问器,它使用 add 关键字和代码块添加事件的事件处理程序,使用 remove 关键字和代码块移除事件的事件处理程序。

public delegate void TestEventDelegate();
public class EventSource
{
    private TestEventDelegate TestEventHandlers;
    public event TestEventDelegate TestEvent
    {
        add
        {   
                TestEventHandlers += value; 
        }
        remove
        {
                TestEventHandlers -= value;   
        }
    }
    public void RaiseTestEvent()
    {
        TestEventDelegate temp = TestEventHandlers;
        if (temp != null)
        {
            temp();
        }

    }
}

public class testMain
{
    public void DelegateMethod()
    {
        System.Console.WriteLine("事件访问器");
    }
    static void Main()
    {
        testMain a = new testMain();
        EventSource b = new EventSource();
        b.TestEvent += a.DelegateMethod;
        b.RaiseTestEvent();
    }
}

引发事件的类必须具有存储和检索处理程序的机制,才能使用事件访问器。前面的示例使用一个私有委托字段 TestEventHandlers,以及加法和减法赋值运算符在列表中添加和移除处理程序。这与没有使用访问器声明的事件的工作方式极为类似。如果事件接收器使用加法赋值运算符 (+=) 添加事件处理程序,则调用 add 访问器,并且新的处理程序在访问器中可作为局部变量命名值使用。如果使用减法赋值运算符 (-=),则调用 remove 访问器,并且要移除的处理程序可作为局部变量命名值使用。两个访问器都返回 void,因此所有返回语句都不能返回值。无论类是否声明了事件访问器,事件的订阅和取消订阅都使用相同的语法。