一、事件的基本概念
事件是什么呢?他是一个东西,得能够发生,发生了就会给其他的东西通知。
事件是类的成员。它使类或对象具有通知别的类或对象的能力。
而当然,不仅仅有通知。
有些事件,例如说手机响铃,会伴随着其他的消息,比如谁谁谁领导通知你开会,家人跟你聊天什么的。这种伴随着事件传达的信息称为事件参数。
手机响铃会有很多种可能,而有些事件,其发生与通知本身就蕴含了一些信息,没有必要再给你其他的事件参数。比如红灯停绿灯行。
因而,事件的功能=通知+可选的事件参数。它用与类或对象之间的动作协调与信息传递。
/*感觉在C语言中,这个功能是通过使用flag啊isXX等等标志的布尔值或者对数值的判断功能来实现的?*/
事件通知到的那个东西,同时也是接受事件通知、关心事件通知、行动,的那个东西,叫作事件的订阅者。它们接收到事件的信息后采取行动称为响应事件/处理事件。它们处理事件时所进行的行动称为事件处理器。
将现实中的具体实物抽象化为事件,我们具有一个“事件模型”的原理。它由两个“5”组成:
其一,发生-->响应的五个部分:如闹钟响了我起床,不只有“闹钟”、“响了”、“我”、“起床”这五个元素,它还包含了“我”关心着“闹钟”这个订阅关系。
其二,发生-->响应的五个动作:(1)我有一个事件(2)有一个人或一群人关心着我这个事件(3)我发生了(4)我依次通知他们并且说明该做的事情(5)他们根据事件参数处理事件
注:
为了防止事件模型逻辑混乱的场面,对于事件的逻辑设置,人类总结出了MVP.MVC、MVVM等设计模式,是事件模式更高级有效的方法。
在日常生活中,声明事件机会较少,更多是使用已有的事件,故更重要学会怎么用。
二、事件的应用
1.事件模型的五个组成部分
(1)事件的拥有者/事件的source(event source,对象)
手机响铃的手机
(2)事件成员(event)
事件本身。
它使类或对象具有通知别的类或对象的能力。
是被动的工具
只有在事件的拥有者完成内部逻辑后触发才能发挥通知的作用
(3)事件的订阅者(event subscriber,对象)
被通知到的类或对象
(4)事件处理器(event handler,成员)————本质上是一个回调方法
(5)事件订阅——把事件处理器与事件关联在一起,本质上是一种以委托类型为基础的约定
解决了三个问题:
有谁被通知到,拿什么样的事件处理器处理这个事件,事件的响应者具体拿哪个方法处理这个事件
C#规定,用于订阅事件的事件处理器必须与事件遵守同一个约定,约定约束了事件能够发送什么样的消息给处理器,也约束处理器能够处理什么样的消息。
2.常用的事件订阅者和拥有者的组合
第一种,事件的拥有者和事件的订阅者处在两个不同的类。这是最基础的一个组合。
第二种,事件的响应者和拥有者是同一个对象。
第三种是最广泛的,事件的拥有者是事件的响应者的一个字段成员
比如说WPF的button。button是事件的拥有者,窗口是事件的响应者,button是窗口的一个字段成员。
3.使用事件
namespace ConsoleAppPractice
{
class Program
{
static void Main()
{
Boy boy = new Boy();//事件响应者
Girl girl = new Girl();
Timer timer = new Timer();//事件拥有者
timer.Interval = 500;
//事件订阅、事件成员
timer.Elapsed += boy.Action;//Elapsed是timer的一个事件。+=操作符用于让对象订阅一个事件
timer.Elapsed += girl.Action;
timer.Start();
Console.ReadLine();
}
}
class Boy
{
//可以使用alt+enter让系统帮你创造一个这个事件处理器
//事件处理器
internal void Action(object sender, ElapsedEventArgs e)
{
Console.WriteLine("I'm running happily!");
}
}
class Girl
{
internal void Action(object sender, ElapsedEventArgs e)
{
Console.WriteLine("I'm drawing happily!");
}
}
}
感觉很像是多播委托
接下来,我们看看具体的三种模式的代码实例。
事件的拥有者跟事件的响应者是两个不同的对象
/*
代码功能:让form显示出来,点击一下后关闭
*/
namespace ConsoleAppPractice
{
class Program
{
static void Main()
{
Form form = new Form();//事件的拥有者
Controller controller = new Controller(form);//事件的响应者
form.ShowDialog();
}
}
class Controller
{
private Form form;
public Controller(Form form)
{
this.form = form;
this.form.Click += this.Action;//订阅事件
}
private void Action(object sender, EventArgs e)//事件处理器。注意此处事件参数不同于上一例
{
this.form.Close();
}
}
}
注意,此处如果进行如下修改:
Form form = new Form();
Controller controller = new Controller();
form.ShowDialog();
public Controller()
{
this.form = new Form();
this.form.Click += this.Action;//订阅事件
}
是不能完成任何功能的。这也体现了第一种模式和第三种模式的区别。虽然都是在构造器里进行初始化和事件订阅,但是由于第一个模式,是用一个对象去响应另一个对象,故必须传递事件拥有者的地址,才能让事件被正确订阅;第三个模式只有自己一个对象,所以只需创建自己的实例即可。
第三个模式例子中的私有字段成员button和textBox之所以能被外界访问,我初步猜想是由于:
this.Controls.Add(this.button);
this.Controls.Add(this.textBox);
这两条语句。
我们仍希望使用Form这个类,因而我们需要对Form这个类添加事件处理器。
但是Form这个类是微软自带的类,不能被修改。所以,我们可以使用拓展方法或者派生类,来为其添加方法。
具体代码如下:
//使用派生类
namespace ConsoleAppPractice
{
class Program
{
static void Main()
{
MyForm form = new MyForm();//事件的拥有者、订阅者
form.Click += form.Action;//订阅事件
form.ShowDialog();
}
}
class MyForm : Form
{
internal void Action(object sender, EventArgs e)//事件处理器。事件参数于上一例相同
{
this.Text = "Hello World!";
}
}
}
//使用扩展方法
namespace ConsoleAppPractice
{
class Program
{
static void Main()
{
Form form = new Form();
form.Click += form.Action;
form.ShowDialog();
}
}
static class FormExtension
{
internal static void Action(this Form form,object sender, EventArgs e)
{
form.Text = "You clicked me just now.>///<";
}
}
}
事件的拥有者是事件的响应者的一个字段成员。
namespace ConsoleAppPractice
{
class Program
{
static void Main()
{
MyForm form = new MyForm();
form.ShowDialog();
}
}
class MyForm : Form
{
private Button button;
private TextBox textBox;
public MyForm()
{
this.button = new Button();
this.textBox = new TextBox();
this.Controls.Add(this.button);
this.Controls.Add(this.textBox);
button.Top = 50;
button.Click += this.ClickEvent;
}
private void ClickEvent(object sender, EventArgs e)
{
this.textBox.Text = "Hello!You cliked me just now.";
}
}
}
下面我们来看例子,通过其看事件的自定义声明。
这例子的实际背景:一个顾客走进来,服务员迎接,并问要点什么菜。顾客说明菜名和尺寸后,服务员接受订单并计算账单价格,随后顾客坐下来吃饭,最后付款。
运行效果:
由之可见,需要两个事件。一个是顾客走进来,服务员迎接;一个是顾客点菜,服务员计算账单。这两个事件当然C#没有提供,所以需要我们自己声明。
具体代码如下:
using System;
using System.Windows.Forms;
using System.Threading;
namespace ConsoleAppPractice
{
public delegate void WalkInEventHandler(Customer customer, EventArgs e);
public delegate void OrderEventHandler(Customer customer, OrderEventArgs dish);
class Program
{
static void Main()
{
Thread.Sleep(2000);
Sever sever = new Sever();
Customer customer = new Customer();
customer.Action(sever);
}
}
public class Customer
{
public int Bill { get; set; }
public void Action(Sever sever)//实现代码基本逻辑
{
this.WalkIn += sever.WelcomeAction;
this.Walking();
Thread.Sleep(3000);
this.Order += sever.OrderAction;
this.OrderDish();
this.Eating();
this.PayTheBill(this.Bill);
}
private WalkInEventHandler walkInEventHandler;//声明事件1:走进来了
public event WalkInEventHandler WalkIn
{
add
{
this.walkInEventHandler += value;
}
remove
{
this.walkInEventHandler -= value;
}
}
protected void Walking()//事件触发的内部逻辑,使用protected保护
{
Console.WriteLine("I am walking in!");
Thread.Sleep(2000);
this.Sit();
EventArgs e = new EventArgs();
this.walkInEventHandler.Invoke(this,e);//触发事件
}
public void Sit()
{
Console.WriteLine("I sat.");
}
private OrderEventHandler orderEventHandler;//声明事件2:顾客点单
public event OrderEventHandler Order
{
add
{
this.orderEventHandler += value;
}
remove
{
this.orderEventHandler -= value;
}
}
protected void OrderDish()//事件触发内部逻辑,得用protected保护
{
Console.WriteLine("I will make an order.");
OrderEventArgs dish = new OrderEventArgs();
this.orderEventHandler.Invoke(this, dish);
Sever.CountAction(this, dish);
}
public void Eating()
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine("I am eating......");
Thread.Sleep(1000);
}
}
public void PayTheBill(int bill)
{
Console.WriteLine("I will pay ${0}",bill);
}
}
public class Sever
{
public static void CountAction(Customer customer, OrderEventArgs dish)
{
int price = 0;
switch (dish.DishName)
{
case "FriedFish":
price = 10;
break;
case "ChickenSoup":
price = 19;
break;
default:
break;
}
switch (dish.DishSize)
{
case "Small":
price *= 1;
break;
case "Big":
price *= 2;
break;
default:
break;
}
customer.Bill += price;
Console.WriteLine("We received your order successfully.");
Thread.Sleep(5000);
Console.WriteLine("The dish is finished.May you have a good eating!");
}
internal void OrderAction(Customer customer, OrderEventArgs dish)
{
Console.WriteLine("What do you want to eat?");
dish.DishName = Console.ReadLine();
Console.WriteLine("What size do you want to eat?");
dish.DishSize = Console.ReadLine();
}
internal void WelcomeAction(Customer customer, EventArgs e)//事件1的处理器
{
Console.WriteLine("Welcome to our restaurant!");
Console.WriteLine("Please see the menu.The Big size is twice as the Small size:");
Console.WriteLine("\tDishName\tPrice");
Console.WriteLine("\tFriedFish\t10");
Console.WriteLine("\tChickenSoup\t19");
}
}
public class OrderEventArgs:EventArgs
{
public string DishName { get; set; }
public string DishSize { get; set; }
}
}
高亮一下事件声明方法:
public delegate void OrderEventHandler(Customer customer, OrderEventArgs dish);
//声明事件处理器的委托,注意命名方式
public class Customer
{
private OrderEventHandler orderEventHandler;//声明事件处理器的委托字段
public event OrderEventHandler Order
{
add
{
this.orderEventHandler += value;
}
remove
{
this.orderEventHandler -= value;
}
}
protected void OrderDish()//事件触发内部逻辑,使用protected保护
{
……
OrderEventArgs dish = new OrderEventArgs();//声明事件参数,事件要通知的东西
this.orderEventHandler.Invoke(this, dish);//触发事件
……
}
public void Action(Sever sever)//实现代码基本逻辑
{
this.Order += sever.OrderAction;//订阅
this.OrderDish();//开始触发事件
}
}
public class Sever
{
internal void OrderAction(Customer customer, OrderEventArgs dish)
{
……
}//事件处理器
}
public class OrderEventArgs:EventArgs//注意命名方式和父类
{
……//事件参数的类
}
事件有简要的声明,此时连委托也可以懒得命名。
namespace ConsoleAppPractice
{
class Program
{
static void Main()
{
Console.ReadLine();
Phone myPhone = new Phone();
Me me = new Me();
MessageEventArgs message = new MessageEventArgs();
message.CreateMessage();
myPhone.m = message;
me.Sleeping();
Thread.Sleep(1000);
myPhone.Ring += me.Surprised;
myPhone.Ringing();
Thread.Sleep(3000);
myPhone.GetMessage += me.Action;
myPhone.Action();
Console.ReadLine();
}
}
public class Phone
{
public event EventHandler GetMessage;//使用自带的事件委托类型
public event EventHandler Ring;
public MessageEventArgs m;
protected void OnGetMessage()
{
Console.WriteLine("My Phone:You have just received a message!");
this.GetMessage.Invoke(this,this.m);
}
protected void OnRing()
{
Console.WriteLine("My Phone:dingdong~");
Thread.Sleep(3000);
this.Ring.Invoke(this, new EventArgs());//直接使用事件名。不过当然仅限于此
}
public void Ringing()
{
this.OnRing();
}
public void Action()
{
this.OnGetMessage();
}
}
public class MessageEventArgs:EventArgs
{
public string senderName { get; set; }
public string content { get; set; }
public void CreateMessage()
{
this.senderName = "Mom";
this.content = "What did you eat this morning?You should be healthy!";
}
}
class Me
{
internal void Action(object sender, EventArgs e)
{
MessageEventArgs m = e as MessageEventArgs;//注意此处的类型转换
Phone phone = sender as Phone;
Console.WriteLine("Me:I received a message from {0}.",m.senderName);
Console.WriteLine("Me:The content is:{0}",m.content);
}
public void Sleeping()
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine("Me:I am sleeping......");
Thread.Sleep(1000);
}
}
internal void Surprised(object sender, EventArgs e)
{
Console.WriteLine("Me:woc!?");
}
}
}
如果去掉event,成为完全的委托字段,会使其在类外也能被访问,安全性受损。故事件实质上是委托字段的包装器,集结了有逻辑关系的委托组,并将其封装,保护它的安全性,仅向外界暴露了添加/移除事件处理器的办法。
命名规范:
注:关于此处的protected:
如果只想让其被自己的类及自己的类派生出的成员访问,则用protected。private不能被派生子类访问。
可以用如下的代码修改试试。
namespace ConsoleAppPractice
{
class Program
{
static void Main()
{
B b = new B();
b.haha();
}
}
class A
{
protected void HAHA()
{
Console.WriteLine("Hh");
}
}
class B : A
{
public void haha()
{
this.HAHA();
}
}
}