定义:能够发生什么的事情
事件担任的角色:使对象或者类具备通知能力的成员
对象A拥有一个事件B,表达的思想是:当事件B发生的时候,A有能力通知别的对象
使用:用于对象或类间的动作协调与信息传递(消息推送)
原理:事件模型(event model)的两个5
发生->响应中的5个部分——闹钟响了你起床、孩子饿了你做饭等等,隐含着“订阅”关系
闹钟,响了,我,做饭这是四个部分,第五个部分是订阅关系,指的是我只订阅着我闹钟响铃这个事件
发生->响应中的5个动作——1. 我有一个事件;2. 一个人或者一群人关心我这个事件 3. 我这个事件发生了;4. 关心这个事件的人会被依次(先订阅先通知,后订阅后通知)通知;5. 被通知到的人根据拿到的事件信息(又称“事件数据”,“事件参数”,“通知”)对事件进行响应(又称“处理事件”)
提示:
1. 事件多用于桌面、手机等开发的客户端编程,因为这些程序经常是用户通过事件来“驱动”的
2. 各种编程语言对这个机制的实现方法不尽相同
3. Java语言里没有事件这种成员,也没有委托这种数据类型。Java的“事件”是用接口来实现的
4. MVC、MVP、MVVM等模式,是事件模式更高级、更有效地方法
5. 日常开发的时候,使用已有事件的机会比较多,自己声明事件的机会比较少,所以先学使用
事件的功能 = 通知 + 可选的事件参数(即详细信息)
名词解释:
事件的订阅者=事件消息的接收者=事件的响应者=事件的处理者=被事件所通知的对象
事件参数=事件信息=事件消息=时间数据
如果说某某对象拥有一个某某事件,解释如下:这个对象可以通过他的事件来通知别的对象,关心这个事件的对象纷纷做出响应
事件模型的五个组成部分
①. 事件的拥有者(event source,一定是一个对象或者一个类)
②. 事件成员(event,成员)——事件不会主动发生
③. 事件的响应者(event subscriber,对象)
④. 事件处理器(event handler,成员)——本质上是一个回调方法
⑤. 事件订阅——把事件处理器与事件关联在一起,本质上是一种以委托类型为基础的“约定”
注意事项:
①. 事件处理器是方法成员
②. 挂接事件处理器的时候,可以使用委托实例,也可以直接使用方法名,这是个“语法糖”
③. 事件处理器对事件的订阅不是随意的,匹配与否由声明事件时所使用的委托类型来检测
④. 事件可以同步调用也可以异步调用
来个简单的例子:
using System;
using System.Timers;
//using System.Windows.Forms;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace _6.事件的定义
{
internal class Program
{
static void Main(string[] args)
{
//1. 创建Timer对象
Timer timer = new Timer();//timer就是事件的拥有者,其拥有事件elapsed
//timer.此时出现的候选词有三种符号,分别是扳手,方块,闪电,分别对应属性、方法、事件
//对于一个类或者一个对象来说,最重要的三个功能就是属性、方法、事件
//属性表示这个类当前处于的状态;方法表示他能做什么;事件表示它能在什么情况下通知谁
timer.Interval = 1000;//每过一秒触发elapsed的事件
//4. 创建事件响应者对象
Boy boy = new Boy();//boy对象就是事件响应者
Girl girl = new Girl();//同上
//5. 订阅事件,此时Boy类中并没有方法,我们写一个Action方法,用Action方法订阅Elap事件
//6. 这个时候Action下面会标红并出现提示,按下Alt+enter选择生成方法
timer.Elapsed += boy.Action;//事件订阅的操作符为+=
timer.Elapsed += girl.Action;//同上
timer.Start();
Console.ReadLine();
}
}
//2. 创建事件响应者,其应该有一个方法叫事件处理器去订阅事件
class Boy
{
//3. 注意,此时不需要手写方法,等会可以自动生成
internal void Action(object sender, ElapsedEventArgs e)//7. VS就会按照“约定”自动生成这个方法(事件处理器)
{
//throw new NotImplementedException();默认生成的这行不要
//8. 当Elapsed事件发生,boy就拿着Action去响应事件
Console.WriteLine("Jump!");//每秒打印一个Jump
}
}
//同上
class Girl
{
internal void Action(object sender, ElapsedEventArgs e)
{
//throw new NotImplementedException();
Console.WriteLine("Sing!");
}
}
}
结果如下,每秒打印一次Jump!和Sing!:
当事件的拥有者和响应者为不同的对象时:
代码如下:
using System;
using System.Timers;
using System.Windows.Forms;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace _6.事件的定义
{
//在这个例子程序中,事件的拥有者和事件的响应者是完全不同的两个对象,
//响应者用自己的事件处理器订阅着事件,事件发生的时候事件处理器就执行了
//本例子中在引用中增加了对Systemm.Windows.Forms的引用
internal 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)//添加构造器,可以接受一个Form 类型的参数
{
//判断为空是因为如果一个对象是空的,无法访问其事件
if (form != null)
{
this.form = form;//字段的form等于我传进来的参数
//当this.form获得了对form对象的引用之后,为form的Click添加一个事件处理器
this.form.Click += this.FormClicked;//this代表Controller的实例
//Click为form的事件
}
}
//注意下面第二个参数是EventArgs,与例一的ElapsedEventArgs不一样,说明他们的约定不一样
private void FormClicked(object sender, EventArgs e)//事件处理器
{
//throw new NotImplementedException();
this.form.Text = DateTime.Now.ToString();//输出当前时间
}
}
}
结果如下,点击窗体中间左上角标题显示时间:
当事件拥有者和响应者为同一个对象
代码如下:
using System;
using System.Timers;
using System.Windows.Forms;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace _6.事件的定义
{
//一个对象拿着自己的方法订阅自己的事件
internal class Program
{
static void Main(string[] args)
{
//如果使用Form类来自己订阅自己,在生成事件处理器的时候会报错,因为Form类已经被官方写死
//Form form = new Form();
//form.Click += form.Action;
//如果自己定义一个Form类的话又无法使用Click等事件,故需要用派生类
//事件拥有者同时也是事件响应者
MyForm form = new MyForm();//子类可以使用父类的成员
form.Click += form.FormClicked;//事件Click,订阅关系+=
form.ShowDialog();
}
}
class MyForm : Form
{
//事件处理器
internal void FormClicked(object sender, EventArgs e)
{
//throw new NotImplementedException();
this.Text = DateTime.Now.ToString();
}
}
}
结果如下,效果同上一个:
当事件拥有者是事件响应者的一个字段成员(使用最多!!!)
代码如下:
using System;
using System.Timers;
using System.Windows.Forms;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace _6.事件的定义
{
//事件响应者用自己的方法订阅自己字段成员的某个事件
//做一个小页面,包括一个按钮一个文本框,按下按钮显示事件
internal class Program
{
static void Main(string[] args)
{
MyForm myForm = new MyForm();
myForm.ShowDialog();
}
}
//事件响应者是MyForm对象
class MyForm : Form
{
private TextBox _textBox;
private Button _button;//事件拥有者,且为Form的一个字段成员
public MyForm()
{
this._textBox = new TextBox();
this._button = new Button();
this.Controls.Add(this._textBox);//把textbox加入到显示页面
this.Controls.Add(this._button);//同上
this._button.Click += this.ButtonClicked;//事件,订阅
this._button.Text = "Time";
this._button.Top = 100;
this._textBox.Width = 150;
}
//事件处理器
private void ButtonClicked(object sender, EventArgs e)
{
//throw new NotImplementedException();
this._textBox.Text = DateTime.Now.ToString();
}
}
}
结果如下:
在WinForm 中的事件的挂接方式
1. 在同一个事件处理器中响应三个事件
private void ButtonCliked(object sender, EventArgs e)//一个事件可以挂接多个事件处理器,一个事件处理器也可以被多个事件挂接
{
//我们可以根据事件Source的不同处理不同的事件
if (sender == this.button1)
{
this.textBox1.Text = DateTime.Now.ToString();
}
if (sender == this.button2)
{
this.textBox1.Text = "Hello World!";
}
if (sender == this.button3)
{
this.textBox1.Text = "1234";
}
}
2. 普通的挂接事件
//this.button3.Click += this.ButtonCliked;//我们可以这样挂接事件处理器,也可以在界面上控件属性中选择ButtonCliked
3. 利用委托完成
this.button3.Click += new EventHandler(this.ButtonCliked);
4. Lambda表达式
this.button3.Click += (sender, e) =>
{
this.textBox1.Text = "3333";
};//参数类型object,EventArgs可以由委托的约束自行推断出来
完成代码如下:
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 _7.事件详解_01
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
//this.button3.Click += this.ButtonCliked;//我们可以这样挂接事件处理器,也可以在界面上控件属性中选择ButtonCliked
//当然也可以像这样挂接
//this.button3.Click += new EventHandler(this.ButtonCliked);
//当打出一个new再按下空格的时候,VS自动判断定义这个事件的时候用的委托是谁并变蓝,委托即EventHandler
//当你创建委托实例的时候,构造器会问你要方法明,鼠标挪过去可以知道他需要一个object和一个EventArgs类型的参数
//还有一种很少见的挂接事件方式(基本废弃了)
//this.button3.Click += delegate (object sender, EventArgs e)
//{
// this.textBox1.Text = "2222";
//};
//此时Button3.Click的事件处理器就不是下面的方法了,而是上面这个匿名方法
//代替上面的是下面这种方式
this.button3.Click += (sender, e) =>
{
this.textBox1.Text = "3333";
};//参数类型object,EventArgs可以由委托的约束自行推断出来
}
//当事件处理器和被处理的事件保持约束关系上的一致时,这个事件处理器可以被重复使用
//即Button2也可以用Button1的Click方法
private void ButtonCliked(object sender, EventArgs e)//一个事件可以挂接多个事件处理器,一个事件处理器也可以被多个事件挂接
{
//我们可以根据事件Source的不同处理不同的事件
if (sender == this.button1)
{
this.textBox1.Text = DateTime.Now.ToString();
}
if (sender == this.button2)
{
this.textBox1.Text = "Hello World!";
}
if (sender == this.button3)
{
this.textBox1.Text = "1234";
}
}
}
}
结果如下: