【C#学习】21事件详解(中)深入理解事件模型的5个组成部分;使用已经准备好的事件


在这里插入图片描述

事件模型的基本理论

1.事件模型的5个组成部分

(1)事件的拥有者(event source,对象),也称:事件的source,事件的主体,事件消息的发送者

(2)事件(event,成员)

(3)事件的响应者(event subscriber,对象),是订阅了事件的对象或类,当一个事件发生时,被通知到的类或对象就是事件的响应者

(4)事件的处理器(event handler,成员),是事件响应者的方法成员,本质上是一个回调方法

(5)事件订阅,把事件处理器与事件关联在一起,本质上是一种以委托类型为基础的约定

2.事件是不会主动发生的

事件是能够让类或对象具备通知能力的成员,站在事件的拥有者的角度,事件就是用来通知别人的工具;

事件不会主动发生,它一定是被事件拥有者的某些内部逻辑所触发,才能够发生,并且发挥通知作用

  • 问题:事件是被拥有者的某些【内部逻辑】所触发,但按钮的Click事件明明是被用户的点击这个【外部逻辑】所触发的啊?

答:实际上并不是这样的,这里涉及到了Windows操作系统的常识
用户的操作通过Windows操作系统调用了按钮的内部逻辑,所以最终是按钮的内部逻辑触发了Click事件

过程:之所以说图形界面的程序比较友好,是因为它把一些底层的,复杂的逻辑全部隐藏起来了;

  • 当用鼠标去点击一个按钮,其实并不是用户自己在点击这个按钮,实际上是用户的鼠标向计算机硬件发送了一个电讯号;硬件之所以能够工作,全靠计算机的操作系统协调管理;
  • 现在计算机中运行的是Windows操作系统,当Windows侦听到由鼠标发送过来的电讯号之后,它就会查看一下它所记录的鼠标指针在屏幕上的当前位置;实际上,Windows操作系统一启动,它就一直在追踪鼠标指针在屏幕上的位置,这个也是所有图形用户界面操作系统最基本的功能之一;
  • 当Windows发现当前鼠标指针所在的位置有一个按钮,而且包含这个按钮的窗口处于激活状态时,它就会通知这个按钮:【你被用户按下了】,然后按钮的内部逻辑就开始执行,典型的内部逻辑是:按钮会快速地把自己绘制一遍,绘制成自己被按下的样子,(平时不按按钮时,按钮的样子看起来是凸起的),而且按钮还会记录自己当前的状态是被按下的;
  • 紧接着如果用户松开鼠标,Windows操作系统也会把这个消息传递给按钮:【你被用户松开了】,然后按钮的内部逻辑又开始执行了,典型的内部逻辑是:按钮会快速把自己绘制成弹起的样子,同时把自己的状态记录为没有被按下;
  • 这时重要的时刻来了:按钮的内部逻辑会检测到自己是被连续地执行了按下并松开的操作,也就意味着【按钮被点击了】,所以按钮会马上使用自己的Click事件通知外界自己被点击了,也就是说Click事件发生了;此时如果有别的对象订阅着这个按钮的Click事件,那么这些事件的响应者就可以开始工作了,也就是说:【这个程序在事件的驱动下开始运行了】;
  • 所以说:【是用户的操作通过Windows操作系统调用了按钮的内部逻辑,是按钮的内部逻辑触发了Click事件,而不是用户的点击这个外部逻辑触发了该事件 】

3.事件订阅解决的3个问题

(1)当一个事件发生时,事件的拥有者都会通知谁?
会通知订阅该事件的类或对象

(2)拿什么样的事件处理器(方法)才能够处理该事件?
当拿着一个事件处理器去订阅一个事件时,C#编译器会做非常严格的类型检查,C#规定:用于订阅事件的事件处理器必须与事件遵守同一个约定;这个约定,既约束了事件能够把什么样的消息发送给事件处理器,也约束了事件处理器能够处理什么样的消息

如果事件是使用某个约定定义的,而且事件处理器也遵循同样的预定,那么【事件处理器与事件就是匹配的】,说明事件处理器可以订阅事件;如果不匹配,编译器就会报错

这个约定,就是委托——事件是基于委托的

(3)事件的响应者具体拿哪个方法来处理该事件?
如果类或对象的多个方法都与事件匹配,那么在订阅事件时,就会告诉事件:未来会用哪个具体方法来处理事件

4.什么是事件,如何使用事件?

在这里插入图片描述

  • 事件模型的5个组成部分
    (1)事件的拥有者:timer
    (2)事件:timer的Elapsed事件(timer每隔一段时间就会触发一次该事件,时间间隔由我们存储在timer的Interval属性中)
    (3)事件的响应者:实例boy,gilr(当事件被触发时,它们的实例方法,也就是事件处理器会响应事件)
    (4)事件处理器:实例方法Action,Action2(编译器根据Elapsed事件按照约定【委托类型】自动生成)
    (5)订阅事件:也就是为事件挂接事件处理器,运用 “+=” 操作符
  • 对于一个类或对象来说,最重要的三种成员:属性,方法,事件;对应着三个最重要的功能:存储数据,做事情,通知别人;
  • 图标:属性(小扳手),方法(小方块),事件(小闪电)

事件组成部分的几种组合方式

5.事件的拥有者和事件的响应者是完全不同的两个对象

在这里插入图片描述
这种模型是标准的事件机制模型,是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事件挂接事件处理器

6.事件的拥有者同时也是事件的响应者

在这里插入图片描述
相当于一个对象拿着自己的事件处理器去订阅和处理自己的事件

代码示例:
在这里插入图片描述

  • 事件模型的5个组成部分
    (1)事件的拥有者:form
    (2)事件:Click事件
    (3)事件的响应着:form
    (4)事件处理器:FormClick()
    (5)订阅事件

  • 为什么要声明一个派生于Form类的子类MyForm?
    因为如果直接去创建一个Form类的实例,是无法为该实例的Click事件去挂接事件处理器的,因为Form类早就已经写好了,不能修改;但如果要为了能够写事件处理器而去创建一个全新的类,则有点小题大做了,是不太现实的;所以声明一个类MyForm,它既继承了Form类的所有成员,也可以自定义事件处理器

7.事件拥有者是事件响应者的一个字段成员

在这里插入图片描述
事件的响应者用自己的方法订阅着自己的字段成员的某个事件
这种情况,意义重大,应用非常广泛;因为它是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”,高亮的部分就是订阅事件的表达式(使用窗体设计器自动完成)
    在这里插入图片描述

知识补充

8.一个事件处理器是可以被重用的

但要注意:重用的前提是这个事件处理器必须与所要处理的事件保持约束上的一致

举例:
如果为窗口再添加一个button2控件,那么由于button1的事件处理器ButtonClicked与button2的Click事件遵循的是同一个约定,所以button2的Click事件也可以挂接上该事件处理器(可以在button2的属性面板中找到它的Click事件,在下拉菜单中直接选择ButtonClicked事件处理器)

注意ButtonClicked事件处理器的第一个参数:【Sender】——event source(也就是事件的拥有者,事件的source,事件消息的发送者;这也是为什么这个参数叫作 “sender” 的原因:sender的意思是:事件消息的发送者)
由于ButtonClicked可以处理两个click事件,那么就可以根据事件sender(事件拥有者)的不同来决定逻辑的不同
在这里插入图片描述

9.挂接事件处理器的其他方式

(1)界面编辑器会采用更传统的挂接方式;visual studio会自动判断出EventHandler是事件处理器和事件所共同遵循的约定

this.button1.Click += new System.EventHandler(this.ButtonClicked);

把鼠标放在EventHandler上也可看到ButtonClicked()是完全符合它约束的目标方法

(2)用匿名方法进行挂接(已经废弃了)

(3)用lambda表达式进行挂接
当使用这种写法时,编译器可以通过委托约束来推断出参数的数据类型,所以只需写参数名即可
在这里插入图片描述

10.如何用WPF应用程序使用事件?

  • WPF与WinForms中绝大部分是相同的,两者使用事件的方法几乎是一致的,只是WPF有种新的使用方法
  • WPF发明的XAML与html是同一个家族的语言,以便设计师轻松参与到程序开发中的窗体设计部分
  • 可以在XAML中直接用 : click = “…” 的格式声明,双引号中是事件处理器的名字,注意该语句要写对位置,是谁的click事件就写在谁中,不要写错
  • 如果用传统的委托的方式挂接事件处理器,会发现WPF中button的Click事件与WinForms中button的Click事件的所用的约束(委托类型)是不一样的,这是因为WPF是一种新技术,它的事件成为路由事件
发布了29 篇原创文章 · 获赞 3 · 访问量 937

猜你喜欢

转载自blog.csdn.net/weixin_44813932/article/details/104014758