初步了解事件

image.png
订阅关系:关注的对象,我关注着自己的闹钟,当自己闹钟响了,执行起床的方法,而非别人的闹钟。
事件的订阅者:
image.png
事件参数:
image.png
设计模式:
MVC、MVP、MVVM等模式,是事件模式更高级、更有效的“玩法”。

事件的应用

事件模型的五个组成部分

1.事件的拥有者/事件的Source(event source,对象/类)
事件不会主动发生,一定是被拥有者某些内部逻辑触发后才能够发生,才能够发挥通知的作用。
2.事件成员(event ,成员)
3.事件的响应者(event subscriber,对象) 被通知了,事件响应者是订阅了事件的对象或者类
4.事件处理器(event handler,方法成员)—本质上是一个回调方法
5.事件订阅—把事件处理器与事件关联在一起,本质上是一种以委托类型为基础的“约定”
解决三个问题:谁被通知/拿什么事件处理器来处理事件/遵守同一个“约定”

Examples:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. using System.Timers;
  7. /// <summary>
  8. /// 事件拥有者(事件Source)是timer对象,事件响应者boy,该响应者需要一个事件处理器Action方法
  9. /// 事件订阅 timer.Elapsed+=boy.Action
  10. /// </summary>
  11. namespace EventModel
  12. {
  13. class Program
  14. {
  15. static void Main(string[] args)
  16. {
  17. Timer timer = new Timer();
  18. timer.Interval = 1000;//时间间隔,每隔1000ms就会触发Elapsed事件
  19. Boy boy = new Boy();
  20. Girl girl = new Girl();
  21. timer.Elapsed += boy.Action;
  22. timer.Elapsed += girl.Action;//先订阅先执行
  23. timer.Start();
  24. Console.ReadLine();
  25. }
  26. }
  27. class Boy
  28. {
  29. internal void Action(object sender, ElapsedEventArgs e)
  30. {
  31. Console.WriteLine("Jump");
  32. }
  33. }
  34. class Girl
  35. {
  36. internal void Action(object sender, ElapsedEventArgs e)
  37. {
  38. Console.WriteLine("Dance");
  39. }
  40. }
  41. }

标准事件模型

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

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. using System.Windows.Forms;
  7. namespace EventModelNormal
  8. {
  9. class Program
  10. {
  11. static void Main(string[] args)
  12. {
  13. Form form = new Form(); //事件拥有者是form
  14. Controller controller = new Controller(form);//事件响应者是Controller实例
  15. form.ShowDialog();
  16. }
  17. }
  18. class Controller
  19. {
  20. private Form form;
  21. public Controller(Form form)
  22. {
  23. if(form!=null)
  24. {
  25. this.form = form;
  26. this.form.Click += this.FormClicked; //事件响应者是Controller实例,订阅关系
  27. }
  28. }
  29. private void FormClicked(object sender, EventArgs e)
  30. {
  31. this.form.Text = DateTime.Now.ToString();
  32. }
  33. }
  34. }

其他事件模型
image.png
二星实例:事件的拥有者和事件的响应者是同一个对象
方法一:两次Tab,事件处理器和事件响应者为其本身

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. using System.Windows.Forms;
  7. namespace EventModelTwo
  8. {
  9. //派生:在原有类的基础上拓展功能
  10. class Program
  11. {
  12. static void Main(string[] args)
  13. {
  14. Form form = new Form();//事件的拥有者
  15. form.Click += Form_Click;
  16. form.ShowDialog();
  17. }
  18. private static void Form_Click(object sender, EventArgs e)
  19. {
  20. Form form = (Form)sender;
  21. form.Text = DateTime.Now.ToString();
  22. }
  23. }
  24. }

方法二:继承基类

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. using System.Windows.Forms;
  7. namespace EventModelTwo
  8. {
  9. //派生(继承):在原有类的基础上拓展功能
  10. class Program
  11. {
  12. static void Main(string[] args)
  13. {
  14. MyForm myForm = new MyForm();//事件的拥有者MyForm对象,Click事件,事件响应者MyForm
  15. //事件处理器 Form_Clicked 事件订阅+=
  16. myForm.Click += myForm.Form_Clicked;
  17. myForm.ShowDialog();
  18. }
  19. }
  20. class MyForm : Form
  21. {
  22. internal void Form_Clicked(object sender, EventArgs e)
  23. {
  24. this.Text = DateTime.Now.ToString();
  25. }
  26. }
  27. }

三星例子:事件拥有者是事件响应者的一个字段成员,事件响应者用自己的一个方法订阅者自己字段成员的某个事件

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. using System.Windows.Forms;
  7. namespace EventModelImportant
  8. {
  9. class Program
  10. {
  11. static void Main(string[] args)
  12. {
  13. MyForm myForm = new MyForm();//事件响应者myForm,事件拥有者是类成员Button,事件:Click
  14. myForm.ShowDialog();
  15. }
  16. }
  17. class MyForm :Form
  18. {
  19. private TextBox textBox;
  20. private Button button;
  21. private FlowLayoutPanel layoutPanel;
  22. public MyForm()
  23. {
  24. this.textBox = new TextBox();
  25. this.button = new Button();
  26. this.layoutPanel = new FlowLayoutPanel();
  27. this.Controls.Add(this.textBox);
  28. this.Controls.Add(this.button);
  29. this.button.Top = this.textBox.Height;
  30. this.button.Text = "Say Hello";
  31. this.button.Click += this.Button_Click;
  32. }
  33. private void Button_Click(object sender, EventArgs e)
  34. {
  35. string text = "Hello AlbertZhaohongyong";
  36. this.textBox.Text = text;
  37. this.textBox.Width = text.Length*8;
  38. }
  39. }
  40. }

小知识点:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Data;
  5. using System.Drawing;
  6. using System.Linq;
  7. using System.Text;
  8. using System.Threading.Tasks;
  9. using System.Windows.Forms;
  10. namespace Event小知识点
  11. {
  12. public partial class Form1 : Form
  13. {
  14. public Form1()
  15. {
  16. InitializeComponent();
  17. //Lmd表达式
  18. //写法一:
  19. this.button1.Click += (object sender, EventArgs e) =>
  20. {
  21. this.textBox1.Text = "Hello AlbertZhaohongyong";
  22. };
  23. //写法二:在一的基础上简化,不需要写委托约束
  24. this.button1.Click += (sender,e) =>
  25. {
  26. this.textBox1.Text = "Hello AlbertZhaohongyong";
  27. };
  28. //写法三:已过时,匿名方法
  29. this.button1.Click += delegate (object sender, EventArgs e)
  30. {
  31. this.textBox1.Text = "AlbertZhaohongyong";
  32. };
  33. }
  34. }
  35. }

类的三大成员:属性(类的状态)、方法(类能够做什么)、事件(通知谁)

深入理解事件

winform和wpf(路由事件)

事件的声明

image.png

自定义事件的产生的原因

非产商为我们准备好直接可用的事件(例如Click,Elapse等)
事件是基于委托的:(表层约束,底层实现)
1.事件需要使用委托类型做一个约束,这个约束既规定了事件能够发送什么样消息给事件响应者,也规定了事件响应者能收到什么样的消息。(匹配)
2.当事件的响应者向事件的拥有者提供了事件处理器之后,能够记录和引用方法的任务只有委托类型的实例能够做到。
总结:委托是事件的底层基础,事件是委托的上层建筑。

事件声明的完整格式

  1. //using EventFullDeclare;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading;
  7. using System.Threading.Tasks;
  8. namespace EventFullDeclare
  9. {
  10. class Program
  11. {
  12. static void Main(string[] args)
  13. {
  14. Customer customer = new Customer();
  15. Waiter waiter = new Waiter();
  16. customer.Order += waiter.Action;
  17. customer.Action();
  18. customer.PayTheBill();
  19. }
  20. }
  21. public class OrderEventArgs:EventArgs //派生自EventArgs基类
  22. {
  23. public string DishName { get; set; }
  24. public string Size { get; set; }
  25. }
  26. public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);//准备好了委托类型
  27. //基于事件的委托,以事件名+EventHandler为后缀(委托名),第一个参数为事件Source,第二个参数为事件名+EventArgs为后缀
  28. public class Customer
  29. {
  30. private OrderEventHandler orderEventHandler;
  31. public event OrderEventHandler Order
  32. {
  33. //事件处理器的添加器
  34. add
  35. {
  36. this.orderEventHandler += value;
  37. }
  38. //事件处理器的移除器
  39. remove
  40. {
  41. this.orderEventHandler -= value;
  42. }
  43. }
  44. public double Bill { get; set; }
  45. public void PayTheBill()
  46. {
  47. Console.WriteLine("I will pay ${0}", Bill);
  48. }
  49. public void WalkIn()
  50. {
  51. Console.WriteLine("Walk in the restaurant.");
  52. }
  53. public void SitDown()
  54. {
  55. Console.WriteLine("SitDown");
  56. }
  57. public void Think()
  58. {
  59. Thread.Sleep(1000);
  60. if(this.orderEventHandler!=null)
  61. {
  62. OrderEventArgs e = new OrderEventArgs();
  63. e.DishName = "Kongpao Chicken";
  64. e.Size = "large";
  65. this.orderEventHandler.Invoke(this, e);
  66. }
  67. }
  68. public void Action()
  69. {
  70. Console.ReadLine();
  71. this.WalkIn();
  72. this.SitDown();
  73. this.Think();
  74. }
  75. }
  76. public class Waiter
  77. {
  78. public void Action(Customer customer, OrderEventArgs e)
  79. {
  80. Console.WriteLine("I will serve you the dish - {0}",e.DishName);
  81. double price = 10;
  82. switch(e.Size)
  83. {
  84. case "small":
  85. price = price * 0.5;
  86. break;
  87. case "large":
  88. price = price * 1.5;
  89. break;
  90. default:
  91. break;
  92. }
  93. customer.Bill += price;
  94. }
  95. }
  96. }

Winform中自定义事件封装声明

  1. //子窗体
  2. using System;
  3. using System.Collections.Generic;
  4. using System.ComponentModel;
  5. using System.Data;
  6. using System.Drawing;
  7. using System.Linq;
  8. using System.Text;
  9. using System.Threading.Tasks;
  10. using System.Windows.Forms;
  11. namespace EventHandler
  12. {
  13. public partial class SubForm : Form
  14. {
  15. //自定义事件
  16. public event controlClickEventHandler controlClick;
  17. //事件拥有者内部逻辑触发事件方法
  18. protected virtual void TrigEvent(EventMessageEventArgs e)
  19. {
  20. if(controlClick!=null)
  21. {
  22. controlClick.Invoke(this, e);
  23. }
  24. }
  25. //引发事件
  26. private void RaiseEvent(string raiseEventMessage)
  27. {
  28. EventMessageEventArgs e = new EventMessageEventArgs(raiseEventMessage);
  29. TrigEvent(e);
  30. }
  31. public SubForm()
  32. {
  33. InitializeComponent();
  34. }
  35. private void SubForm_Load(object sender, EventArgs e)
  36. {
  37. }
  38. private void button1_Click(object sender, EventArgs e)
  39. {
  40. this.textBox1.Text = "this is subform";
  41. RaiseEvent("ButtonClick");
  42. }
  43. }
  44. #region 自定义事件
  45. public delegate void controlClickEventHandler(object sender, EventMessageEventArgs e);
  46. public class EventMessageEventArgs : EventArgs
  47. {
  48. public string Message { get; set; }
  49. public EventMessageEventArgs(string messageInfo)
  50. {
  51. Message = messageInfo;
  52. }
  53. }
  54. #endregion
  55. }
  56. //主窗体
  57. using System;
  58. using System.Collections.Generic;
  59. using System.ComponentModel;
  60. using System.Data;
  61. using System.Drawing;
  62. using System.Linq;
  63. using System.Text;
  64. using System.Threading.Tasks;
  65. using System.Windows.Forms;
  66. namespace EventHandler
  67. {
  68. public partial class Form1 : Form
  69. {
  70. public SubForm subForm = new SubForm();
  71. public Form1()
  72. {
  73. InitializeComponent();
  74. }
  75. private void Form1_Load(object sender, EventArgs e)
  76. {
  77. subForm.TopLevel = false;
  78. this.panel2.Controls.Add(subForm);
  79. subForm.Show();
  80. subForm.controlClick += this.Action;
  81. }
  82. private void Action(object sender, EventMessageEventArgs e)
  83. {
  84. if (e.Message == "ButtonClick")
  85. {
  86. this.textBox1.Text = "This is text form subform";
  87. }
  88. }
  89. }
  90. }

事件简化声明

field-like(像字段的声明格式)

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. namespace EventSimply
  7. {
  8. public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);
  9. class Program
  10. {
  11. static void Main(string[] args)
  12. {
  13. Customer customer = new Customer();
  14. Waiter waiter = new Waiter();
  15. customer.order += waiter.Action;
  16. customer.Aciton();
  17. }
  18. }
  19. public class OrderEventArgs:EventArgs
  20. {
  21. public string DishName { get; set; }
  22. public string Size { get; set; }
  23. }
  24. public class Customer
  25. {
  26. public event OrderEventHandler order;//事件的本质是委托字段的一个包装器
  27. public double Bill { get; set; }
  28. public void PayBill()
  29. {
  30. Console.WriteLine("I will pay {0}",Bill);
  31. }
  32. public void Aciton() //事件永远是拥有者内部逻辑触发
  33. {
  34. if(order!=null)
  35. {
  36. OrderEventArgs e = new OrderEventArgs();
  37. e.DishName = "Kongpao Chicken";
  38. e.Size = "large";
  39. order.Invoke(this, e);
  40. }
  41. }
  42. }
  43. public class Waiter
  44. {
  45. internal void Action(Customer customer, OrderEventArgs e)
  46. {
  47. double prize = 10;
  48. switch(e.Size)
  49. {
  50. case "large":prize = prize * 1.5;
  51. break;
  52. default:
  53. break;
  54. }
  55. customer.Bill = prize;
  56. Console.WriteLine("You need pay {0} for {1}",customer.Bill,e.DishName);
  57. }
  58. }
  59. }

调用x64 Native Tools Command Prompt for VS 2019
输入ildasm
image.pngimage.png
水蓝色字段Order,事件的本质是委托字段的一个包装器

问题辨析

我们既然可以声明委托类型的字段,为什么还要事件这种成员呢?

更加有逻辑,更加有道理,防止“借刀杀人”,事件可以保证不能在事件拥有者的外部随随便便触发事件,只能在外部+=或者-=。

封装(encapsulation)的一个重要功能就是隐藏,事件**对外界隐藏了委托实例的大部分功能,仅暴露添加/移除事件处理器的功能。

触发事件方法一般命名为OnFoo,即“因何引发”、“事出有因”,访问级别为protected,不能为public 不然又成了可以“借刀杀人”了。

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. namespace EventSimply
  7. {
  8. public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);
  9. class Program
  10. {
  11. static void Main(string[] args)
  12. {
  13. Customer customer = new Customer();
  14. Waiter waiter = new Waiter();
  15. customer.order += waiter.Action;
  16. //customer.Aciton();
  17. Customer badGuy = new Customer();
  18. OrderEventArgs e = new OrderEventArgs();
  19. e.DishName = "Manhanquanxi";
  20. e.Size = "large";
  21. OrderEventArgs e2 = new OrderEventArgs();
  22. e2.DishName = "beer";
  23. e2.Size = "large";
  24. badGuy.order += waiter.Action;
  25. badGuy.order.Invoke(customer, e2);//借刀杀人,将e2给到第一个客人头上
  26. customer.Aciton();
  27. }
  28. }
  29. public class OrderEventArgs:EventArgs
  30. {
  31. public string DishName { get; set; }
  32. public string Size { get; set; }
  33. }
  34. public class Customer
  35. {
  36. public OrderEventHandler order;//直接使用委托,外部也能进行访问
  37. public double Bill { get; set; }
  38. public void PayBill()
  39. {
  40. Console.WriteLine("I will pay {0}",Bill);
  41. }
  42. public void Aciton() //事件永远是拥有者内部逻辑触发,触发事件方法一般命名为OnFoo
  43. {
  44. if(order!=null)
  45. {
  46. OrderEventArgs e = new OrderEventArgs();
  47. e.DishName = "Kongpao Chicken";
  48. e.Size = "large";
  49. order.Invoke(this, e);
  50. }
  51. }
  52. }
  53. public class Waiter
  54. {
  55. internal void Action(Customer customer, OrderEventArgs e)
  56. {
  57. double prize = 10;
  58. switch(e.Size)
  59. {
  60. case "large":prize = prize * 1.5;
  61. break;
  62. default:
  63. break;
  64. }
  65. customer.Bill = prize;
  66. Console.WriteLine("You need pay {0} for {1}",customer.Bill,e.DishName);
  67. }
  68. }
  69. }

利用微软提供好的EventHandler委托来实现,同时将触发方法封装在内部

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. namespace EventSimply
  7. {
  8. class Program
  9. {
  10. static void Main(string[] args)
  11. {
  12. Customer customer = new Customer();
  13. Waiter waiter = new Waiter();
  14. customer.order += waiter.Action;
  15. customer.Aciton();
  16. }
  17. }
  18. public class OrderEventArgs:EventArgs
  19. {
  20. public string DishName { get; set; }
  21. public string Size { get; set; }
  22. }
  23. public class Customer
  24. {
  25. public event EventHandler order;//直接使用委托,外部也能进行访问
  26. public double Bill { get; set; }
  27. public void PayBill()
  28. {
  29. Console.WriteLine("I will pay {0}",Bill);
  30. }
  31. public void Aciton() //事件永远是拥有者内部逻辑触发
  32. {
  33. this.OnOrder();
  34. }
  35. //protected访问级别保护触发方法
  36. protected void OnOrder()
  37. {
  38. if (order != null)
  39. {
  40. OrderEventArgs e = new OrderEventArgs();
  41. e.DishName = "Kongpao Chicken";
  42. e.Size = "large";
  43. order.Invoke(this, e);
  44. }
  45. }
  46. }
  47. public class Waiter
  48. {
  49. internal void Action(object sender,EventArgs e)
  50. {
  51. Customer customer = sender as Customer;
  52. OrderEventArgs messInfo = e as OrderEventArgs;
  53. double prize = 10;
  54. switch(messInfo.Size)
  55. {
  56. case "large":prize = prize * 1.5;
  57. break;
  58. default:
  59. break;
  60. }
  61. customer.Bill = prize;
  62. Console.WriteLine("You need pay {0} for {1}",customer.Bill, messInfo.DishName);
  63. }
  64. }
  65. }

事件的命名约定

  • 带有时态的动词或者动词短语
  • 事件拥有者“正在做”用进行时,事件拥有者“做完了”,用完成时。

    市面上的误传和误会

    image.png