前言
偶然间,在公司调试机器看代码时,发现自动动作代码中定义了许多自定义的事件,这才猛然醒悟,事件这东西原来是这么的重要;于是,这两天就在研究自定义事件是如何使用的,现在把这两天研究到的结果记录下来。
学习事件,我主要是通过B站刘铁猛老师的视频和网上的博客进行学习,其中,下面所展示的代码都是在刘铁猛老师的课堂中敲打的。
事件的组成部分
- 事件拥有者;
- 事件;
- 事件响应者;
- 事件处理器;
- 事件订阅.
事件是什么?
参考百度:
事件是可以被控件识别的操作,如按下确定按钮,选择某个单选按钮或者复选框。每一种控件有自己可以识别的事件,如窗体的加载、单击、双击等事件,编辑框(文本框)的文本改变事件,等等。
触发事件的对象称为事件发送者;接收事件的对象称为事件接收者。
C#中事件机制的工作过程如下:
(1)将实际应用中需通过事件机制解决的问题对象注册到相应的事件处理程序上,表示今后当该对象的状态发生变化时,该对象有权使用它注册的事件处理程序。
(2)当事件发生时,触发事件的对象就会调用该对象所有已注册的事件处理程序。
自定义事件
对于自定义事件,本人还是很懵懂的,对该知识点的认值并不是很熟悉,下面就写下本人定义自定义事件的全过程。
以一个例子展开:
假如你去饭店吃饭,坐下凳子后,你就要开始想你要吃什么,想了五秒钟,想好了,你就得叫服务员给你下单,这时候服务员就来订阅你点菜的这个事件了,当你离开时还得付账。
-
首先定义用于传递事件消息的类,也叫事件参数
事件参数一般我们命名习惯都要以
EventArgs
作为结尾。DishName 你点的菜名
Size 分量大小// 用于传递事件消息(事件参数) public class OrderEventAgrs : EventArgs { public string DishName { get; set; } public string Size { get; set; } }
注意:这里需要继承自EventArgs,不用管他为什么要继承EventArgs,就把它当作一种写代码的习惯就好。
-
定义事件拥有者类
刚开始可以先定义一个空类public class Customer { }
-
声明一个定义事件的委托
注意:如果该委托仅仅只是用来定义事件的,那么一般都要以
EventHandler
结尾。public delegate void OrderEventHandler(Customer customer, OrderEventAgrs e); // 参数一:拥有事件的类(事件源) 参数二:传递事件消息的类(事件参数)
此委托用于定义事件与在时间内部订阅方法
-
可以在事件拥有者类中定义事件了
事件命名也得要以
EventHandler
结尾/* 一、事件拥有者 */ public class Customer { // 声明一个委托类型字段 private OrderEventHandler orderEventHandler; /* 二、事件 */ public event OrderEventHandler Order { // 定义事件 // 事件内部在使用委托取订阅方法 add { this.orderEventHandler += value; } remove { this.orderEventHandler -= value; } } //public event OrderEventHandler Order; // 简单定义事件的方法 }
可以看到,先定义了一个委托类型的字段,然后在定义事件的内部中添加add和remove方法,分别用于委托订阅方法和删减方法,间接就是事件订阅方法了。
这点很重要,这是区别 - 简单定义事件 - 的门栏。
也顺便把下面的方法都加到Customer类中吧:
public double Bill { get; set; } public void PayTheBill() { Console.WriteLine("I will pay ${0}", this.Bill); } public void Walkln() { Console.WriteLine("Walk into the restaurant."); } public void SitDown() { Console.WriteLine("Sit down"); }
-
定义 被事件订阅的方法类
// 被事件订阅的方法类 public class Waiter { /* 四、事件处理器 */ public void Action(Customer customer, OrderEventAgrs e) { Console.WriteLine("I will serve your the dish - {0}.", e.DishName); double price = 10; // 假设所有东西都十元钱 switch (e.Size) { case "small": // 小份价格是原价*0.5 price = price * 0.5; break; case "large": // 大份价格是原价*1.5 price = price * 1.5; break; default: price = 0; break; } // 统计价格 customer.Bill += price; } }
该类中的Action方法,是用来被事件订阅的,也叫事件处理器。
也就是当事件促发时,所要处理的事情。注意函数的参数和返回值类型必须要和委托定义的一样。
当然也可以如下图,使用系统自动成成函数的方法,自动生成,这样比较保险。
-
接着在Customer类中添加 促发事件的方法
// 促发事件 public void Think() { for (int i = 0; i < 5; i++) { Console.WriteLine("Let me think..."); Thread.Sleep(1000); } if (this.orderEventHandler != null) { OrderEventAgrs e = new OrderEventAgrs(); e.DishName = "egg"; // 鸡蛋 e.Size = "large"; // 大份 this.orderEventHandler.Invoke(this, e); // 促发 } } public void Action() { Console.ReadLine(); this.Walkln(); this.SitDown(); this.Think(); }
需要特别注意,得判断委托是否为空,才进行下面的操作,避免不必要的报错。
-
在main函数中进行事件的订阅和应用
static void Main(string[] args) { Customer customer = new Customer(); /* 三、事件响应者 */ Waiter waiter = new Waiter(); /* 五、事件订阅 */ customer.Order += waiter.Action; customer.Action(); // 执行事件响应后的方法 customer.PayTheBill(); // 付钱 Console.ReadKey(); }
到了这里,代码也就写完了,可以运行看看效果:
效果也就是这样了。
自定义事件也就码完了,下面是全部代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace 复习自定义事件 {
class Program {
static void Main(string[] args) {
Customer customer = new Customer();
/* 三、事件响应者 */
Waiter waiter = new Waiter();
/* 五、事件订阅 */
customer.Order += waiter.Action;
customer.Action();
customer.PayTheBill();
Console.ReadKey();
}
}
// 用于传递事件消息(事件参数)
public class OrderEventAgrs : EventArgs {
public string DishName { get; set; }
public string Size { get; set; }
}
// 声明一个定义事件的委托
public delegate void OrderEventHandler(Customer customer, OrderEventAgrs e); // 参数一:拥有事件的类(事件源) 参数二:传递事件消息的类(事件参数)
/* 一、事件拥有者 */
public class Customer {
// 声明一个委托类型字段
private OrderEventHandler orderEventHandler;
/* 二、事件 */
public event OrderEventHandler Order {
// 事件内部在使用委托取订阅方法
add {
this.orderEventHandler += value;
}
remove {
this.orderEventHandler -= value;
}
}
//public event OrderEventHandler Order; // 简单定义事件的方法
public double Bill { get; set; }
public void PayTheBill() {
Console.WriteLine("I will pay ${0}", this.Bill);
}
public void Walkln() {
Console.WriteLine("Walk into 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.orderEventHandler != null) {
OrderEventAgrs e = new OrderEventAgrs();
e.DishName = "egg"; // 鸡蛋
e.Size = "large"; // 大份
this.orderEventHandler.Invoke(this, e); // 促发
}
}
public void Action() {
Console.ReadLine();
this.Walkln();
this.SitDown();
this.Think();
}
}
// 被事件订阅的方法类
public class Waiter {
/* 四、事件处理器 */
public void Action(Customer customer, OrderEventAgrs e) {
Console.WriteLine("I will serve your 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:
price = 0;
break;
}
customer.Bill += price;
}
}
}
下面是一些系统自带的事件代码积累,有兴趣的可以看看。
系统自带事件
Timer事件
例子一:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Timers;
namespace 例子一 {
class Program {
static void Main(string[] args) {
// 1. 事件拥有者
Timer timer = new Timer();
timer.Interval = 1000;
// 3. 事件的响应者
Box box = new Box();
Girl girl = new Girl();
// 2. 事件 5. 事件订阅
timer.Elapsed += box.Action;
timer.Elapsed += girl.Action;
timer.Start();
Console.ReadKey();
}
}
class Box {
static int time = 0;
// 4. 事件的处理器
internal void Action(object sender, ElapsedEventArgs e) {
Console.WriteLine("第" + time + "盒子");
time += 2;
}
}
class Girl {
static int time = 1;
internal void Action(object sender, ElapsedEventArgs e) {
Console.WriteLine("第" + time + "女孩");
time += 2;
}
}
}
他会无限的一直这样输出下去,直到你按下任意键。
事件的拥有者和事件的响应者是分开的不同的两个类
例子二:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
// 事件的拥有者和事件的响应者是分开的不同的两个类
namespace 例子二 {
class Program {
static void Main(string[] args) {
// 1. 事件拥有者
Form form = new Form();
// 3. 事件响应者
Controls controls = new Controls(form);
form.ShowDialog();
}
class Controls {
private Form form;
public Controls(Form _form) {
if (_form != null) {
this.form = _form;
// 2. 事件 5. 事件订阅
this.form.Click += this.FormClicked; // 窗体的点击事件
}
}
// 4. 事件处理器
private void FormClicked(object sender, EventArgs e) {
this.form.Text = DateTime.Now.ToString(); // 获取当前系统事件更新窗体标题
}
}
}
}
当鼠标点击窗体里的任意地方都会更新标题
事件的拥有者同时也是事件的响应者
例子三:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
// 事件的拥有者同时也是事件的响应者
namespace 例子三 {
class Program {
static void Main(string[] args) {
// 1. 事件拥有者 3. 事件响应者
MyForm myForm = new MyForm();
// 2. 事件 5. 事件订阅
myForm.Click += myForm.FormClicked;
myForm.ShowDialog();
}
}
class MyForm : Form {
// 4. 事件处理器
internal void FormClicked(object sender, EventArgs e) {
this.Text = DateTime.Now.ToString();
}
}
}
还是一样,鼠标点击窗体后更新标题时间。
自定义类继承自系统的类,就可以添加自己的方法去实现了。
事件响应者也是事件拥有者
例子四:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace 例子四 {
class Program {
static void Main(string[] args) {
// 3. 事件响应者
MyForm myForm = new MyForm();
myForm.ShowDialog();
}
}
class MyForm : Form {
private TextBox textBox;
private Button button;
public MyForm() {
textBox = new TextBox();
// 1. 事件拥有者
button = new Button();
this.Controls.Add(textBox);
this.Controls.Add(button);
// 2. 事件 5. 事件订阅
button.Click += this.ButtonClicked;
this.button.Text = "Say Hello";
this.button.Top = 30;
}
// 4. 事件处理器
private void ButtonClicked(object sender, EventArgs e) {
this.textBox.Text = "Hello Wrold";
}
}
}
总结:
事件本人懂的也就这些了,具体事件有什么用,我还真是不知道,既然有这样一个东西,肯定有他的用处的;日后对事件有更进一步的理解后再过来更新更新这篇博客吧。