初步了解事件
订阅关系:关注的对象,我关注着自己的闹钟,当自己闹钟响了,执行起床的方法,而非别人的闹钟。
事件的订阅者:
事件参数:
设计模式:
MVC、MVP、MVVM等模式,是事件模式更高级、更有效的“玩法”。
事件的应用
事件模型的五个组成部分
1.事件的拥有者/事件的Source(event source,对象/类)
事件不会主动发生,一定是被拥有者某些内部逻辑触发后才能够发生,才能够发挥通知的作用。
2.事件成员(event ,成员)
3.事件的响应者(event subscriber,对象) 被通知了,事件响应者是订阅了事件的对象或者类
4.事件处理器(event handler,方法成员)—本质上是一个回调方法
5.事件订阅—把事件处理器与事件关联在一起,本质上是一种以委托类型为基础的“约定”
解决三个问题:谁被通知/拿什么事件处理器来处理事件/遵守同一个“约定”
Examples:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Timers;
/// <summary>
/// 事件拥有者(事件Source)是timer对象,事件响应者boy,该响应者需要一个事件处理器Action方法
/// 事件订阅 timer.Elapsed+=boy.Action
/// </summary>
namespace EventModel
{
class Program
{
static void Main(string[] args)
{
Timer timer = new Timer();
timer.Interval = 1000;//时间间隔,每隔1000ms就会触发Elapsed事件
Boy boy = new Boy();
Girl girl = new Girl();
timer.Elapsed += boy.Action;
timer.Elapsed += girl.Action;//先订阅先执行
timer.Start();
Console.ReadLine();
}
}
class Boy
{
internal void Action(object sender, ElapsedEventArgs e)
{
Console.WriteLine("Jump");
}
}
class Girl
{
internal void Action(object sender, ElapsedEventArgs e)
{
Console.WriteLine("Dance");
}
}
}
标准事件模型
事件的拥有者和事件的响应者是完全不同的两个对象
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace EventModelNormal
{
class Program
{
static void Main(string[] args)
{
Form form = new Form(); //事件拥有者是form
Controller controller = new Controller(form);//事件响应者是Controller实例
form.ShowDialog();
}
}
class Controller
{
private Form form;
public Controller(Form form)
{
if(form!=null)
{
this.form = form;
this.form.Click += this.FormClicked; //事件响应者是Controller实例,订阅关系
}
}
private void FormClicked(object sender, EventArgs e)
{
this.form.Text = DateTime.Now.ToString();
}
}
}
其他事件模型
二星实例:事件的拥有者和事件的响应者是同一个对象
方法一:两次Tab,事件处理器和事件响应者为其本身
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace EventModelTwo
{
//派生:在原有类的基础上拓展功能
class Program
{
static void Main(string[] args)
{
Form form = new Form();//事件的拥有者
form.Click += Form_Click;
form.ShowDialog();
}
private static void Form_Click(object sender, EventArgs e)
{
Form form = (Form)sender;
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 EventModelTwo
{
//派生(继承):在原有类的基础上拓展功能
class Program
{
static void Main(string[] args)
{
MyForm myForm = new MyForm();//事件的拥有者MyForm对象,Click事件,事件响应者MyForm
//事件处理器 Form_Clicked 事件订阅+=
myForm.Click += myForm.Form_Clicked;
myForm.ShowDialog();
}
}
class MyForm : Form
{
internal void Form_Clicked(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 EventModelImportant
{
class Program
{
static void Main(string[] args)
{
MyForm myForm = new MyForm();//事件响应者myForm,事件拥有者是类成员Button,事件:Click
myForm.ShowDialog();
}
}
class MyForm :Form
{
private TextBox textBox;
private Button button;
private FlowLayoutPanel layoutPanel;
public MyForm()
{
this.textBox = new TextBox();
this.button = new Button();
this.layoutPanel = new FlowLayoutPanel();
this.Controls.Add(this.textBox);
this.Controls.Add(this.button);
this.button.Top = this.textBox.Height;
this.button.Text = "Say Hello";
this.button.Click += this.Button_Click;
}
private void Button_Click(object sender, EventArgs e)
{
string text = "Hello AlbertZhaohongyong";
this.textBox.Text = text;
this.textBox.Width = text.Length*8;
}
}
}
小知识点:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Event小知识点
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
//Lmd表达式
//写法一:
this.button1.Click += (object sender, EventArgs e) =>
{
this.textBox1.Text = "Hello AlbertZhaohongyong";
};
//写法二:在一的基础上简化,不需要写委托约束
this.button1.Click += (sender,e) =>
{
this.textBox1.Text = "Hello AlbertZhaohongyong";
};
//写法三:已过时,匿名方法
this.button1.Click += delegate (object sender, EventArgs e)
{
this.textBox1.Text = "AlbertZhaohongyong";
};
}
}
}
类的三大成员:属性(类的状态)、方法(类能够做什么)、事件(通知谁)
深入理解事件
事件的声明
自定义事件的产生的原因
非产商为我们准备好直接可用的事件(例如Click,Elapse等)
事件是基于委托的:(表层约束,底层实现)
1.事件需要使用委托类型做一个约束,这个约束既规定了事件能够发送什么样消息给事件响应者,也规定了事件响应者能收到什么样的消息。(匹配)
2.当事件的响应者向事件的拥有者提供了事件处理器之后,能够记录和引用方法的任务只有委托类型的实例能够做到。
总结:委托是事件的底层基础,事件是委托的上层建筑。
事件声明的完整格式
//using EventFullDeclare;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace EventFullDeclare
{
class Program
{
static void Main(string[] args)
{
Customer customer = new Customer();
Waiter waiter = new Waiter();
customer.Order += waiter.Action;
customer.Action();
customer.PayTheBill();
}
}
public class OrderEventArgs:EventArgs //派生自EventArgs基类
{
public string DishName { get; set; }
public string Size { get; set; }
}
public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);//准备好了委托类型
//基于事件的委托,以事件名+EventHandler为后缀(委托名),第一个参数为事件Source,第二个参数为事件名+EventArgs为后缀
public class Customer
{
private OrderEventHandler orderEventHandler;
public event OrderEventHandler Order
{
//事件处理器的添加器
add
{
this.orderEventHandler += value;
}
//事件处理器的移除器
remove
{
this.orderEventHandler -= value;
}
}
public double Bill { get; set; }
public void PayTheBill()
{
Console.WriteLine("I will pay ${0}", Bill);
}
public void WalkIn()
{
Console.WriteLine("Walk in the restaurant.");
}
public void SitDown()
{
Console.WriteLine("SitDown");
}
public void Think()
{
Thread.Sleep(1000);
if(this.orderEventHandler!=null)
{
OrderEventArgs e = new OrderEventArgs();
e.DishName = "Kongpao Chicken";
e.Size = "large";
this.orderEventHandler.Invoke(this, e);
}
}
public void Action()
{
Console.ReadLine();
this.WalkIn();
this.SitDown();
this.Think();
}
}
public class 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;
}
}
}
Winform中自定义事件封装声明
//子窗体
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace EventHandler
{
public partial class SubForm : Form
{
//自定义事件
public event controlClickEventHandler controlClick;
//事件拥有者内部逻辑触发事件方法
protected virtual void TrigEvent(EventMessageEventArgs e)
{
if(controlClick!=null)
{
controlClick.Invoke(this, e);
}
}
//引发事件
private void RaiseEvent(string raiseEventMessage)
{
EventMessageEventArgs e = new EventMessageEventArgs(raiseEventMessage);
TrigEvent(e);
}
public SubForm()
{
InitializeComponent();
}
private void SubForm_Load(object sender, EventArgs e)
{
}
private void button1_Click(object sender, EventArgs e)
{
this.textBox1.Text = "this is subform";
RaiseEvent("ButtonClick");
}
}
#region 自定义事件
public delegate void controlClickEventHandler(object sender, EventMessageEventArgs e);
public class EventMessageEventArgs : EventArgs
{
public string Message { get; set; }
public EventMessageEventArgs(string messageInfo)
{
Message = messageInfo;
}
}
#endregion
}
//主窗体
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace EventHandler
{
public partial class Form1 : Form
{
public SubForm subForm = new SubForm();
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
subForm.TopLevel = false;
this.panel2.Controls.Add(subForm);
subForm.Show();
subForm.controlClick += this.Action;
}
private void Action(object sender, EventMessageEventArgs e)
{
if (e.Message == "ButtonClick")
{
this.textBox1.Text = "This is text form subform";
}
}
}
}
事件简化声明
field-like(像字段的声明格式)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EventSimply
{
public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);
class Program
{
static void Main(string[] args)
{
Customer customer = new Customer();
Waiter waiter = new Waiter();
customer.order += waiter.Action;
customer.Aciton();
}
}
public class OrderEventArgs:EventArgs
{
public string DishName { get; set; }
public string Size { get; set; }
}
public class Customer
{
public event OrderEventHandler order;//事件的本质是委托字段的一个包装器
public double Bill { get; set; }
public void PayBill()
{
Console.WriteLine("I will pay {0}",Bill);
}
public void Aciton() //事件永远是拥有者内部逻辑触发
{
if(order!=null)
{
OrderEventArgs e = new OrderEventArgs();
e.DishName = "Kongpao Chicken";
e.Size = "large";
order.Invoke(this, e);
}
}
}
public class Waiter
{
internal void Action(Customer customer, OrderEventArgs e)
{
double prize = 10;
switch(e.Size)
{
case "large":prize = prize * 1.5;
break;
default:
break;
}
customer.Bill = prize;
Console.WriteLine("You need pay {0} for {1}",customer.Bill,e.DishName);
}
}
}
调用x64 Native Tools Command Prompt for VS 2019
输入ildasm
水蓝色字段Order,事件的本质是委托字段的一个包装器
问题辨析
我们既然可以声明委托类型的字段,为什么还要事件这种成员呢?
更加有逻辑,更加有道理,防止“借刀杀人”,事件可以保证不能在事件拥有者的外部随随便便触发事件,只能在外部+=或者-=。
封装(encapsulation)的一个重要功能就是隐藏,事件**对外界隐藏了委托实例的大部分功能,仅暴露添加/移除事件处理器的功能。
触发事件方法一般命名为OnFoo,即“因何引发”、“事出有因”,访问级别为protected,不能为public 不然又成了可以“借刀杀人”了。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EventSimply
{
public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);
class Program
{
static void Main(string[] args)
{
Customer customer = new Customer();
Waiter waiter = new Waiter();
customer.order += waiter.Action;
//customer.Aciton();
Customer badGuy = new Customer();
OrderEventArgs e = new OrderEventArgs();
e.DishName = "Manhanquanxi";
e.Size = "large";
OrderEventArgs e2 = new OrderEventArgs();
e2.DishName = "beer";
e2.Size = "large";
badGuy.order += waiter.Action;
badGuy.order.Invoke(customer, e2);//借刀杀人,将e2给到第一个客人头上
customer.Aciton();
}
}
public class OrderEventArgs:EventArgs
{
public string DishName { get; set; }
public string Size { get; set; }
}
public class Customer
{
public OrderEventHandler order;//直接使用委托,外部也能进行访问
public double Bill { get; set; }
public void PayBill()
{
Console.WriteLine("I will pay {0}",Bill);
}
public void Aciton() //事件永远是拥有者内部逻辑触发,触发事件方法一般命名为OnFoo
{
if(order!=null)
{
OrderEventArgs e = new OrderEventArgs();
e.DishName = "Kongpao Chicken";
e.Size = "large";
order.Invoke(this, e);
}
}
}
public class Waiter
{
internal void Action(Customer customer, OrderEventArgs e)
{
double prize = 10;
switch(e.Size)
{
case "large":prize = prize * 1.5;
break;
default:
break;
}
customer.Bill = prize;
Console.WriteLine("You need pay {0} for {1}",customer.Bill,e.DishName);
}
}
}
利用微软提供好的EventHandler委托来实现,同时将触发方法封装在内部
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EventSimply
{
class Program
{
static void Main(string[] args)
{
Customer customer = new Customer();
Waiter waiter = new Waiter();
customer.order += waiter.Action;
customer.Aciton();
}
}
public class OrderEventArgs:EventArgs
{
public string DishName { get; set; }
public string Size { get; set; }
}
public class Customer
{
public event EventHandler order;//直接使用委托,外部也能进行访问
public double Bill { get; set; }
public void PayBill()
{
Console.WriteLine("I will pay {0}",Bill);
}
public void Aciton() //事件永远是拥有者内部逻辑触发
{
this.OnOrder();
}
//protected访问级别保护触发方法
protected void OnOrder()
{
if (order != null)
{
OrderEventArgs e = new OrderEventArgs();
e.DishName = "Kongpao Chicken";
e.Size = "large";
order.Invoke(this, e);
}
}
}
public class Waiter
{
internal void Action(object sender,EventArgs e)
{
Customer customer = sender as Customer;
OrderEventArgs messInfo = e as OrderEventArgs;
double prize = 10;
switch(messInfo.Size)
{
case "large":prize = prize * 1.5;
break;
default:
break;
}
customer.Bill = prize;
Console.WriteLine("You need pay {0} for {1}",customer.Bill, messInfo.DishName);
}
}
}