一、事件的原理
事件(Event)
定义:
类 或对象可以通过事件向其他类或对象通知发生的相关事情。 发送(或引发)事件的类称为“发布者”,接收(或处理)事件的类称为“订阅者”。
在典型的 C# Windows 窗体或 Web 应用程序中,可订阅由按钮和列表框等控件引发的事件。
可以使用 Visual C# 集成开发环境 (IDE) 来浏览控件发布的事件,并选择想要处理的事件。
简单的说事件本质是对委托类型的封装,类似于类的属性对字段的封装。
目的是构成一个发布订阅的模型的同时保证委托类型的变量在类外部不能被直接调用。
事件的使用:
代码:
namespace ConsoleApp1 { internal class Program { /// <summary> /// 声明委托类型 /// </summary> /// <param name="num"></param> public delegate void NumCountChange(int num); static void Main(string[] args) { //实例化一个发布者 Publisher pub = new Publisher(); //实例化一个订阅者 Subscriber sub = new Subscriber(); // 1、通过事件发布器来触发事件,然后订阅器接收该事件 pub.NumChange += new NumCountChange(sub.OnNumChange); pub.InvokeEvent(); //发布者触发事件,订阅者接收该触发事件 // 2、通过委托则可以在外部直接触发该事件,为可行但不合理的方式 pub.NumChangeDelegate = new NumCountChange(sub.OnNumChange); pub.NumChangeDelegate(200);//委托变量直接调用了订阅者的OnNumChange函数 Console.ReadKey(); } /// <summary> /// 事件发布者类 /// </summary> class Publisher { public NumCountChange NumChangeDelegate; //声明委托类型变量 public event NumCountChange NumChange; //声明事件 public int count { set; get; } = 99; //当调用该函数时,由发布者来触发对应的事件 public void InvokeEvent() { if (NumChange != null) //当委托中有函数注册时(即为非空),就会触发该事件 { ++count; NumChange(count);//触发该事件 } } } /// <summary> /// 事件订阅者类 /// </summary> class Subscriber { //该函数会注册到委托事件中,一旦事件发布者触发该事件时,订阅者就会接收到该事件进行执行 public void OnNumChange(int count) { Console.WriteLine("Subscriber the changed number is {0}", count); } } } }
当然在winform程序中,事件被使用的最多,当我们用鼠标左键单击按钮对象、在文本框对象中输入字符或用鼠标选择组合框中的元素之一时,就会发生一个事件。熟悉这些事件之后,我们可以使用这些事件完成我们想要的操作:
public MainForm() { InitializeComponent(); //背景色设置 this.TransparencyKey = this.BackColor; //边框设置 this.FormBorderStyle = FormBorderStyle.None; //打开默认位置 this.StartPosition = FormStartPosition.CenterScreen; SetStyle(ControlStyles.UserPaint, true);//使用自定义绘制方式 SetStyle(ControlStyles.AllPaintingInWmPaint, true); // 禁止擦除背景. SetStyle(ControlStyles.DoubleBuffer, true); // 双缓冲 //为主窗体注册一个鼠标点击事件 this.MouseClick += MainForm_MouseClick; } /// <summary> /// 鼠标点击事件执行方法 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void MainForm_MouseClick(object sender, MouseEventArgs e) { if(e.Button == MouseButtons.Right) { Point p = new Point(); p.X = this.Location.X + e.X + 10; p.Y = this.Location.Y + e.Y + 10; contextMenuStrip1.Show(p); } }
事件中最值得讨论的地方是他的设计模式,观察者模式,在现实世界中,许多对象并不是独立存在的,
其中一个对象的行为发生改变可能会导致一个或者多个其他对象的行为也发生改变。比如,当我们开车到交叉路口时,遇到红灯会停,遇到绿灯会行。
观察者(Observer)模式的定义:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。
在事件中事件的触发和反应就是一种观察者模式,主要分为两部分:
被监查主体:事件发布者,只管触发事件,而不管是否有人订阅了该事件。
观察者:事件的订阅者,只根据自身的需要,来决定是否需要注册订阅该事件,以对该事件产生响应,而不管是谁触发了该事件。
下面就以汽车等红灯为例子,制作一个简单的控制台程序:
namespace ConsoleApp1 { internal class Program { static void Main(string[] args) { //实例化三个对象 CountDown countDown = new CountDown(); Car car = new Car(); Display display = new Display(); //注册事件 countDown.curTemperature += car.MakeRun; //取消事件的注册 //countDown.curTemperature -= car.MakeRun; countDown.curTemperature += display.ShowSecondCount; //开始倒计时 countDown.CountDowning(); Console.ReadKey(); } /// <summary> /// 注册一个委托类型 /// </summary> /// <param name="num"></param> public delegate void SendSecondCountEvent(int num); /// <summary> /// 倒计时,负责显示当前倒计时 /// </summary> class CountDown { //将委托封装成事件 public event SendSecondCountEvent curTemperature; //私有属性 private int secondcount; //倒计时的方法 public void CountDowning() { //倒计时从100开始 for(int i = 100; i >= 0; i--) { secondcount = i; if (curTemperature != null) { curTemperature(secondcount); } //模拟时间过去一秒 Thread.Sleep(10); } } } /// <summary> /// 汽车:当倒计时为0时,汽车松开刹车 /// </summary> class Car { public void MakeRun (int num) { if(num<=0) { Console.WriteLine("绿灯已亮,松开刹车,踩下油门!!!"); } } } /// <summary> /// 显示器:显示当前倒计时 /// </summary> class Display { public void ShowSecondCount(int num) { if (num <= 0) { Console.WriteLine("现在是绿灯,请快速通过!!!"); } else { Console.WriteLine("现在是红灯,倒计时={0}", num); } } } } }
二、事件和委托的区别
事件真的是“以特殊方式声明的委托字段/实例吗”?不是!只是声明的时候“看起来像”
事件声明的时候使用了委托类型,简化声明造成事件看上去像一个委托的字段(实例),
而event关键字则更像是一个修饰符——这就是错觉的来源之一
订阅事件的时候+=操作符后面可以是一个委托实例,
这与委托实例的赋值方法语法相同,这也让事件看起来像是一个委托字段——这是错觉的又一来源
重申:事件的本质是加装在委托字段上的一个“蒙板”(mask),是个起掩蔽作用的包装器,
这个用于阻挡非法操作的“蒙板”绝不是委托字段本身
namespace EventExample { class Program { static void Main(string[] args) { Customer customer = new Customer(); Waiter waiter = new Waiter(); customer.Order += waiter.Action;//挂接事件。waiter的Action订阅着customer的Order customer.Action(); customer.PayTheBill(); } } public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);//声明一个委托类型,专门用来声明事件,约束事件处理器 public class OrderEventArgs : EventArgs//声明用来传递消息的类,派生自EventArgs { public string DishName { get; set; } public string Size { get; set; } } public class Customer //事件发起者 { public event OrderEventHandler Order;//声明事件Order。用OrderEventHandler来约束事件 public double Bill { get; set; } public void Walkin() { Console.WriteLine("Walk in the restaurant"); } public void Sitdown() { Console.WriteLine("Sit down"); } public void Think() { for (int i = 0; i < 5; i++) { Console.WriteLine("Let me think......"); Thread.Sleep(1000); } if (this.Order != null)//等于空说明没有人订阅这个事件,会报异常 { OrderEventArgs e = new OrderEventArgs(); e.DishName = "Kongpao Chicken"; e.Size = "large"; this.Order.Invoke(this, e); } } public void Action() { Console.ReadLine(); Walkin(); Sitdown(); Think(); } public void PayTheBill() { Console.WriteLine("I Will pay ${0}.",this.Bill); } } public class Waiter //事件的响应者Waiter { public void Action(Customer customer, OrderEventArgs e)//事件处理器 { Console.WriteLine("I will serve you the dish - {0}", e.DishName); double price = 10; switch (e.Size) { case "small": price = price * 0.5; break; case "large": price = price * 1.5; break; default: break; } customer.Bill += price; } } }
三、常见的事件
1、定时器事件Timer
- 要包含对应的命名空间:
using System.Timers;
- 指定时间间隔,然后每到指定的间隔就响应一次,代码如下
namespace Pr01_Basic { class Program { static void Main(string[] args) { Test te = new Test(); Timer timer = new Timer(); timer.Interval = 1000; //C#中事件和行为是通过 += 来进行连接。 //timer.Elapsed表示时间到,进行触发 //te.Response为对该触发事件做出的响应 timer.Elapsed += te.Response; timer.Start(); Console.ReadKey(); } } class Test { //事件的响应函数 internal void Response(object sender, ElapsedEventArgs e) { Console.WriteLine("Hello is a response for Timer!"); } } }
2、事件触发和响应分别为不同对象
namespace Pr01_Basic { class Program { static void Main(string[] args) { Form fm = new Form(); //事件触发对象 Test te = new Test(fm); //事件响应对象 fm.ShowDialog(); Console.ReadKey(); } } class Test { public Form form { get; set; } public Test(Form form) { if(form!=null) { this.form = form; this.form.Click += this.formClick; //点击时触发事件与响应 } } private void formClick(object sender, EventArgs e) { this.form.Text = "Just!!"; } } }
3、事件的触发和响应都是同一个对象
namespace Pr01_Basic { class Program { static void Main(string[] args) { MyForm myfm = new MyForm(); //事件的触发者和响应者皆为myfm myfm.Click += myfm.Response; myfm.ShowDialog(); Console.ReadKey(); } } class MyForm : Form { internal void Response(object sender, EventArgs e) { this.Text = "Test My Form"; } } }
最后事件一般不用自己声明,Microsoft给的一般都够用了。事件的应用范围真的很广,就像你双击文件夹就会打开,这就是一个事件。事件本事建立在委托的基础上,实现一系列的功能,没有委托就没有事件。当然,没有类就也没有委托,没有面向对象思想就没有类,以此推下去,对象(Object)真是一个神奇的东西