(1)事件的拥有者(event source,对象),也称:事件的source,事件的主体,事件消息的发送者
(2)事件(event,成员)
(3)事件的响应者(event subscriber,对象),是订阅了事件的对象或类,当一个事件发生时,被通知到的类或对象就是事件的响应者
(4)事件的处理器(event handler,成员),是事件响应者的方法成员,本质上是一个回调方法
(5)事件订阅,把事件处理器与事件关联在一起,本质上是一种以委托类型为基础的约定
事件是能够让类或对象具备通知能力的成员,站在事件的拥有者的角度,事件就是用来通知别人的工具;
事件不会主动发生,它一定是被事件拥有者的某些内部逻辑所触发,才能够发生,并且发挥通知作用
答:实际上并不是这样的,这里涉及到了Windows操作系统的常识
【用户的操作通过Windows操作系统调用了按钮的内部逻辑,所以最终是按钮的内部逻辑触发了Click事件】
过程:之所以说图形界面的程序比较友好,是因为它把一些底层的,复杂的逻辑全部隐藏起来了;
(1)当一个事件发生时,事件的拥有者都会通知谁?
会通知订阅该事件的类或对象
(2)拿什么样的事件处理器(方法)才能够处理该事件?
当拿着一个事件处理器去订阅一个事件时,C#编译器会做非常严格的类型检查,C#规定:用于订阅事件的事件处理器必须与事件遵守同一个约定;这个约定,既约束了事件能够把什么样的消息发送给事件处理器,也约束了事件处理器能够处理什么样的消息
如果事件是使用某个约定定义的,而且事件处理器也遵循同样的预定,那么【事件处理器与事件就是匹配的】,说明事件处理器可以订阅事件;如果不匹配,编译器就会报错
这个约定,就是委托——事件是基于委托的
(3)事件的响应者具体拿哪个方法来处理该事件?
如果类或对象的多个方法都与事件匹配,那么在订阅事件时,就会告诉事件:未来会用哪个具体方法来处理事件
这种模型是标准的事件机制模型,是MVC,MVP等设计模式的雏形
特点是:事件拥有者和事件响应者是完全不同的两个对象;事件响应者用自己的事件处理器订阅着这个事件,当事件发生时,事件处理器开始执行
事件模型的5个组成部分
(1)事件的拥有者:form
(2)事件:form的Click事件
(3)事件的响应者:controller
(4)事件处理器:类Controller的实例方法FormClicked()
(5)订阅事件
为什么FormClicked()与Action()的第二个参数不同?
因为Click事件与它的事件处理器FormClicked()共同遵守着约定A,而Elapsed事件与它的事件处理器Action()共同遵守着约定B,约定A和约定B不同,也就是遵循的约束不同;所以,不能拿响应Elapsed事件的事件处理器去响应Click事件,它们之间是不通用的
为什么要给类Controller声明一个Form类的实例字段?
为了架起一个桥梁
首先明确:事件是不会主动发生的,它一定是被事件拥有者的某些内部逻辑所触发,而在这个例子当中,事件拥有者和事件响应者是完全不同的两个对象,那么事件响应者如何知道事件已被触发?换句话说,事件的响应者如何被通知,从而响应事件?
所以,事件的响应者 controller 需要将事件的拥有者,也就是Form类的实例form,吸收为自己的实例字段,因为实例字段可表示该实例当前的状态,那么当form的Click事件触发后,controller就会被通知到,这就相当于架起了事件拥有者与事件响应者之间的一个桥梁
事件的响应者 controller 如何将事件的拥有者 form 吸收为自己的实例字段?
通过自定义类Controller的构造器
定义该构造器时,规定未来创建类Controller的实例时,必须传进一个数据类型为Form的实参,显然该实参就是事件的拥有者 form,如果 form 不为空,就把这个form赋值给类Controller的实例字段【form】(注意区分两个form:this.form = form;赋值符号左边是实例字段,右边是实参),并为实例字段form的Click事件挂接事件处理器
事件模型的5个组成部分
(1)事件的拥有者:form
(2)事件:Click事件
(3)事件的响应着:form
(4)事件处理器:FormClick()
(5)订阅事件
为什么要声明一个派生于Form类的子类MyForm?
因为如果直接去创建一个Form类的实例,是无法为该实例的Click事件去挂接事件处理器的,因为Form类早就已经写好了,不能修改;但如果要为了能够写事件处理器而去创建一个全新的类,则有点小题大做了,是不太现实的;所以声明一个类MyForm,它既继承了Form类的所有成员,也可以自定义事件处理器
事件的响应者用自己的方法订阅着自己的字段成员的某个事件
这种情况,意义重大,应用非常广泛;因为它是Windows平台上默认的事件订阅和处理结构
举例:
【按钮是窗口的字段成员】
按钮是Click事件的拥有者,而窗口则是Click事件的响应者;
当为这个窗口编程时,会为其准备一个方法(事件处理器),该方法订阅着按钮的Click事件,一旦用户点击按钮,按钮就会通过Click事件通知窗口自己被点击了,窗口就会应用自己的事件处理器去响应该事件
代码示例:
实现:在窗口中有一个文本框,一个按钮;当点击按钮时,文本框中就会显示 “Hello,world!” 字符串
事件模型的5个组成部分
(1)事件的拥有者:button
(2)事件:Click事件
(3)事件的响应者:form
(4)事件处理器:ButtonClick()方法
(5)订阅事件
事件的响应者到底是谁?
不要以为在textbox中显示了字符串事件的响应者就是textbox,因为TextBox是微软早就准备好的类,它是不会拥有自定义的事件处理器的;唯一能修改的只有MyForm这个类,事件的响应者应是它的实例form
从非可视化编程到可视化编程
在上述代码中将button的Top属性值设置为100,显然100并不是最合适的值,但是由于我们在编写代码时是非可视化的,所以也没办法,只能乱猜,这样就会把大量时间浪费在设计窗体上,所以我们需要可视化编程,也就是:所见即所得
打开WinForms
(1)添加文本框,按钮控件后,在设计区右键鼠标,点击 “view code”,进入代码填写完整逻辑
(2)也可以自动生成事件处理器,方法是添加控件后,选中事件的拥有者button,在右侧属性面板中找到它的Click事件,填写事件处理器名称 “ButtonClicked” 后敲击回车,就会看到已经生成的好的事件处理器框架,往里添加所需逻辑即可
问题:事件订阅在哪呢?
右击事件处理器ButtonClicked,选择 “Find All Reference”,高亮的部分就是订阅事件的表达式(使用窗体设计器自动完成)
但要注意:重用的前提是这个事件处理器必须与所要处理的事件保持约束上的一致
举例:
如果为窗口再添加一个button2控件,那么由于button1的事件处理器ButtonClicked与button2的Click事件遵循的是同一个约定,所以button2的Click事件也可以挂接上该事件处理器(可以在button2的属性面板中找到它的Click事件,在下拉菜单中直接选择ButtonClicked事件处理器)
注意ButtonClicked事件处理器的第一个参数:【Sender】——event source(也就是事件的拥有者,事件的source,事件消息的发送者;这也是为什么这个参数叫作 “sender” 的原因:sender的意思是:事件消息的发送者)
由于ButtonClicked可以处理两个click事件,那么就可以根据事件sender(事件拥有者)的不同来决定逻辑的不同
(1)界面编辑器会采用更传统的挂接方式;visual studio会自动判断出EventHandler是事件处理器和事件所共同遵循的约定
this.button1.Click += new System.EventHandler(this.ButtonClicked);
把鼠标放在EventHandler上也可看到ButtonClicked()是完全符合它约束的目标方法
(2)用匿名方法进行挂接(已经废弃了)
(3)用lambda表达式进行挂接
当使用这种写法时,编译器可以通过委托约束来推断出参数的数据类型,所以只需写参数名即可