zoukankan      html  css  js  c++  java
  • [.net 面向对象编程基础] (22) 事件

    [.net 面向对象编程基础] (22)  事件

       事件(Event)是学习.net面向对象编程很重要的一部分,在学习事件之前,我们实际上已经在很多地方使用了事件,比如控件的click事件等,这些都是.net设计控件的时候已经定义好的事件。除此之外,我们同样可以自己定义事件。

       事件实际上是一种消息机制,当然点击控件时,click就通知处理他的方法去处理,实际上就是前面说的委托。因此我们可以说:事件是一种具有特殊签名的委托。而事件/消息机制是windows的核心,因此我们必须掌握他。

       为了更加容易理解事件,我们还是使用前面的动物的示例来说明,有两三只动物,猫(名叫Tom),还有两只老鼠(JerryJack),当猫叫的时候,触发事件(CatShout),然后两只老鼠开始逃跑(MouseRun)。在使用代码实现这个示例之前,我们先看一下事件的书面定义. 

    1.什么是事件(Event)? 

     事件(Event)是类或对象向其他类或对象通知发生的事情的一种特殊签名的委托. 

    2.事件的声明 

    public event 委托类型 事件名; 

    事件使用event关键词来声明,他的返回类值是一个委托类型。 

    通常事件的命名,以名字+Event 作为他的名称,在编码中尽量使用规范命名,增加代码可读性。 

    3.事件示例 

    下面我们实现本篇开始的猫捉老鼠的示例 

    首先看一下UML 

      

    如上UML类图,有猫(Cat)和老鼠(Mouse)两个类,里面包含其成员.当猫叫(CatShout),触发事件(CatShoutEvent),事件通知老鼠,然后老鼠跑路(MouseRun). 

     两个类的代码如下:

     1     class Cat
     2     {
     3         string catName;
     4         string catColor { get; set; }
     5         public Cat(string name, string color)
     6         {
     7             this.catName = name;
     8             catColor = color;
     9         }
    10         
    11         public void CatShout()
    12         {
    13             Console.WriteLine(catColor+" 的猫 "+catName+" 过来了,喵!喵!喵!
    ");
    14 
    15             //猫叫时触发事件
    16             //猫叫时,如果CatShoutEvent中有登记事件,则执行该事件
    17             if (CatShoutEvent != null)
    18                 CatShoutEvent();
    19         }
    20 
    21         public delegate void CatShoutEventHandler();
    22 
    23         public event CatShoutEventHandler CatShoutEvent;
    24 
    25     }
    26     class Mouse
    27     {
    28         string mouseName;
    29         string mouseColor { get; set; }
    30         public Mouse(string name, string color)
    31         {
    32             this.mouseName = name;
    33             this.mouseColor = color;
    34         }
    35 
    36         public void MouseRun()
    37         {
    38             Console.WriteLine(mouseColor + " 的老鼠 " + mouseName + " 说:"老猫来了,快跑!"  
    我跑!!
    我使劲跑!!
    我加速使劲跑!!!
    ");
    39         }
    40     }

    调用如下:

     1             Console.WriteLine("[场景说明]: 一个月明星稀的午夜,有两只老鼠在偷油吃
    ");
     2             Mouse Jerry = new Mouse("Jerry", "白色");
     3             Mouse Jack = new Mouse("Jack", "黄色");
     4 
     5 
     6             Console.WriteLine("[场景说明]: 一只黑猫蹑手蹑脚的走了过来
    ");
     7             Cat Tom = new Cat("Tom", "黑色");
     8 
     9             Console.WriteLine("[场景说明]: 为了安全的偷油,登记了一个猫叫的事件
    ");
    10             Tom.CatShoutEvent += new Cat.CatShoutEventHandler(Jerry.MouseRun);
    11             Tom.CatShoutEvent += new Cat.CatShoutEventHandler(Jack.MouseRun);
    12 
    13             Console.WriteLine("[场景说明]: 猫叫了三声
    ");
    14             Tom.CatShout();
    15 
    16 
    17             Console.ReadKey();

    运行结果如下:

    4.事件参数

     上面的事件是最简单的事件,通过我们看到的事件,都会带两个参数,比如c# winform中的button点击事件的委托方法如下:

     private void button1_Click(object sender, EventArgs e) 

    带有两个参数,不熟悉事件参数的小伙伴肯定要问,这两个参数sendere到底有什么用呢?
    第一个参数 sender,其中object类型的参数 sender表示的是发送消息的对象,为什么要使用object类型呢,这是因为不同类型的对象调用时使用object能很好的兼容。 

    第二个参数 e,他的类型为EventArgs.EventArgs这个类没有实际的用途,只是作为一个基类让其他对象继承。很多对象不需要传递额外的信息,例如按钮事件,只是调用一个回调方法就够了。当我们定义的事件不需要传递额外的信息时,这时调用EventArgs.Empty就行了,不需要重新构建一个EventArgs对象。

     

    我们可以看到在Button事件登记时,只传了一个参数sender

    为了更好的理解带参数的事件,我们改写一下上面猫捉老鼠的示例: 

    先看UML图:

    实现代码如下:

     1   class Cat
     2     {
     3         string catName;
     4         string catColor { get; set; }
     5         public Cat(string name, string color)
     6         {
     7             this.catName = name;
     8             catColor = color;
     9         }
    10         
    11         public void CatShout()
    12         {
    13             Console.WriteLine(catColor+" 的猫 "+catName+" 过来了,喵!喵!喵!
    ");
    14 
    15             //猫叫时触发事件
    16             //猫叫时,如果CatShoutEvent中有登记事件,则执行该事件
    17             if (CatShoutEvent != null)
    18                 CatShoutEvent(this, new CatShoutEventArgs() {catName=this.catName, catColor=this.catColor});
    19         }
    20 
    21         public delegate void CatShoutEventHandler(object sender,CatShoutEventArgs e);
    22 
    23         public event CatShoutEventHandler CatShoutEvent;
    24 
    25     }
    26 
    27     /// <summary>
    28     /// EventArgs类的作用就是让事件传递参数用的
    29     /// 我们定义一个类CatShout包含两个成员属性,以方便传递
    30     /// </summary>
    31     class CatShoutEventArgs:EventArgs
    32     {
    33        public  string catColor { get; set; }
    34        public string catName { get; set; }
    35     }
    36 
    37     class Mouse
    38     {
    39         string mouseName;
    40         string mouseColor { get; set; }
    41         public Mouse(string name, string color)
    42         {
    43             this.mouseName = name;
    44             this.mouseColor = color;
    45         }
    46 
    47         public void MouseRun(object sender, CatShoutEventArgs e)
    48         {
    49             if (e.catColor == "黑色")
    50                 Console.WriteLine(mouseColor + " 的老鼠 " + mouseName + " 说:" " + e.catColor + "" + e.catName + " 来了,快跑!"  
    我跑!!
    我使劲跑!!
    我加速使劲跑!!!
    ");
    51             else
    52                 Console.WriteLine(mouseColor + " 的老鼠 " + mouseName + " 说:" " + e.catColor + "" + e.catName + " 来了,慢跑!"  
    我跑!!
    我慢慢跑!!
    我慢慢悠悠跑!!!
    ");
    53 
    54         }
    55     }

    调用如下:

     1 Console.WriteLine("[场景说明]: 一个月明星稀的午夜,有两只老鼠在偷油吃
    
    
    ");
     2 Mouse Jerry = new Mouse("Jerry", "白色");
     3 Mouse Jack = new Mouse("Jack", "黄色");
     4 
     5 
     6 Console.WriteLine("[场景说明]: 一只黑猫蹑手蹑脚的走了过来");
     7 Cat Tom = new Cat("Tom", "黑色");
     8 Console.WriteLine("[场景说明]: 为了安全的偷油,登记了一个猫叫的事件");
     9 Tom.CatShoutEvent += new Cat.CatShoutEventHandler(Jerry.MouseRun);
    10 Tom.CatShoutEvent += new Cat.CatShoutEventHandler(Jack.MouseRun);
    11 Console.WriteLine("[场景说明]: 猫叫了三声
    ");
    12 Tom.CatShout();
    13 
    14 Console.WriteLine("
    
    
    ");
    15 
    16 //当其他颜色的猫过来时
    17 Console.WriteLine("[场景说明]: 一只蓝色的猫蹑手蹑脚的走了过来");
    18 Cat BlueCat = new Cat("BlueCat", "蓝色");
    19 Console.WriteLine("[场景说明]: 为了安全的偷油,登记了一个猫叫的事件");
    20 BlueCat.CatShoutEvent += new Cat.CatShoutEventHandler(Jerry.MouseRun);
    21 BlueCat.CatShoutEvent += new Cat.CatShoutEventHandler(Jack.MouseRun);
    22 Console.WriteLine("[场景说明]: 猫叫了三声");
    23 BlueCat.CatShout();

    运行结果如下:

    也可以使用前面学过的Lamda表达式来简洁的写以上的事件注册

    Cat Doraemon = new Cat("哆啦A梦", "彩色");
    Doraemon.CatShoutEvent += (sender, e) => Jerry.MouseRun(sender, e);
    Doraemon.CatShoutEvent += (sender, e) => Jack.MouseRun(sender, e);
    Doraemon.CatShout();

    调用后结果一样.

     

    5. 事件应用实例

    如果上面的简单实例不够过瘾,我下面列举几个日常开发过程中应用事件解决实际问题的例子,加深对事件的理解。

    示例一:我们使用一个事件来监控一个文件夹下文件的变更情况

    先看一下UML类图 

     

    代码如下:

      1     /// <summary>
      2     /// 文件夹监控
      3     /// </summary>
      4     public class FolderWatch
      5     {
      6         public class Files
      7         {
      8             string _fileName;
      9             public string FileName
     10             {
     11               get { return _fileName; }
     12               set { _fileName = value; }
     13             }   
     14             DateTime _fileLastDate;
     15             public DateTime FileLastDate
     16             {
     17               get { return _fileLastDate; }
     18               set { _fileLastDate = value; }
     19             }
     20 
     21         }
     22         /// <summary>
     23         /// 文件夹路径
     24         /// </summary>
     25         public string folderPath;
     26 
     27         /// <summary>
     28         /// 文件集合
     29         /// </summary>
     30         public List<Files> fileList=new List<Files>();
     31       
     32         /// <summary>
     33         /// 文件夹监控事件
     34         /// </summary>
     35         public event FolderWatchEventHandler FolderWatchEvent;
     36 
     37         /// <summary>
     38         /// 构造函数
     39         /// </summary>
     40         public FolderWatch(string path)
     41         {
     42             folderPath = path;
     43           
     44             //获取目录下所有文件
     45             foreach (var file in  new  System.IO.DirectoryInfo(path).GetFiles())
     46             {
     47                 fileList.Add(
     48                      new Files() 
     49                     {
     50                         FileName = file.Name,     
     51                         FileLastDate=file.LastWriteTime
     52                     }
     53 
     54                 );
     55             }
     56         }
     57         public void OnFieldChange()
     58         {
     59             if (FolderWatchEvent != null)
     60                 FolderWatchEvent(this, new MonitorFileEventArgs { Files = fileList });
     61         }
     62 
     63         /// <summary>
     64         /// 委托实现方法
     65         /// </summary>
     66         /// <param name="sender"></param>
     67         /// <param name="e"></param>
     68         public void MonitorFiles(object sender, MonitorFileEventArgs e)
     69         {
     70             
     71             while(true)
     72             {               
     73                 //遍历fileList文件列表,检测是否有变更(删除或修改)         
     74                 if(e.Files!=null)
     75                 foreach(var file in this.fileList)
     76                 {
     77                     string fileFullName=folderPath + "\" + file.FileName;
     78                     //检测是否存在
     79                     if (!System.IO.File.Exists(fileFullName))
     80                         Console.WriteLine("文件"" + file.FileName + ""已被删除或更名;
    ");
     81                     else if (file.FileLastDate != (new System.IO.FileInfo(fileFullName)).LastWriteTime)
     82                     {
     83                         Console.WriteLine("文件"" + file.FileName + ""已被修改过(上次修改日期:" + file.FileLastDate + ",本次检测到日期为:" + (new System.IO.FileInfo(fileFullName)).LastWriteTime + ");
    ");
     84                                          
     85                     }                  
     86                     
     87                 }                
     88                      //重新获取目录下所有文件
     89                 List<Files> newFiles = new List<Files>();
     90                 foreach (var newFile in new System.IO.DirectoryInfo(this.folderPath).GetFiles())
     91                 {
     92                     newFiles.Add(
     93                          new Files()
     94                          {
     95                              FileName = newFile.Name,
     96                              FileLastDate = newFile.LastWriteTime
     97                          }
     98 
     99                     );
    100                     if(!(fileList.Any(m=>m.FileName==newFile.Name)))
    101                         Console.WriteLine("新建文件"" + newFile.Name+""
    ");
    102                 }
    103                  fileList.Clear();
    104                 this.fileList = newFiles;     
    105             }
    106         }
    107     }
    108 
    109     /// <summary>
    110     /// 文件夹监控委托
    111     /// </summary>
    112     public delegate void FolderWatchEventHandler(object sender, MonitorFileEventArgs e);
    113 
    114     /// <summary>
    115     /// 事件传递参数类
    116     /// </summary>
    117     public class MonitorFileEventArgs : EventArgs
    118     {
    119         /// <summary>
    120         /// 文件
    121         /// </summary>
    122         public List<FolderWatch.Files> Files { get; set; }
    123 
    124     }

    调用如下:

    string MyFolder = "MyFolder";
    FolderWatch folder = new FolderWatch(System.IO.Directory.GetCurrentDirectory() +"\"+ MyFolder);
    
    
    folder.FolderWatchEvent += new FolderWatchEventHandler(folder.MonitorFiles);
    folder.OnFieldChange();

    运行结果如下:

    可以看到当我们增加,修改,删除文件时,就会返回文件夹内文件更改的提示信息。

    实际上对于文件更改的监控.NET提供了专门的类FileSystemWatcher来完成。上面的示例只是为了加深理解事件,在实际应用中对文件的变更还是有缺陷的,比如同一文件更名、通过时间判断文件变更也是不科学的。

    下面我们就使用.net提供的FileSystemWatcher类来完成文件夹监控,代码非常简单

     1   static void watcher_Renamed(object sender,System.IO.RenamedEventArgs e)
     2         {
     3             Console.WriteLine("文件"" + e.OldName + ""更名为:"+e.Name+"");
     4         }
     5         static void watcher_Deleted(object sender, System.IO.FileSystemEventArgs e)
     6         {
     7             Console.WriteLine("文件"" + e.Name + ""已被删除;
    ");
     8         }
     9         static void watcher_Changed(object sender, System.IO.FileSystemEventArgs e)
    10         {
    11             Console.WriteLine("文件"" + e.Name + ""已被修改;
    ");
    12         }
    13         static void watcher_Created(object sender, System.IO.FileSystemEventArgs e)
    14         {
    15             Console.WriteLine("新创建了文件"" + e.Name + "";
    ");
    16         }

    调用如下:

     string MyFolder = "MyFolder";
                
                System.IO.FileSystemWatcher watcher = new System.IO.FileSystemWatcher(System.IO.Directory.GetCurrentDirectory() + "\" + MyFolder);
    
                watcher.Renamed+= watcher_Renamed;
                watcher.Deleted+=watcher_Deleted;
                watcher.Changed+=watcher_Changed;
                watcher.Created +=watcher_Created;
    
                watcher.EnableRaisingEvents = true;

    运行结果如下:

    示例二:使用事件完成了一个文件下载进度条的示例,平时我们看到很多进度条程序员为了偷懒都是加载完成直接跳到100%,这个示例就是传说中的真进度条。

    UML类图如下:

    代码如下:

     1 public partial class Form1 : Form
     2     {
     3         System.Threading.Thread thread;
     4         public Form1()
     5         {
     6             InitializeComponent();
     7         }
     8 
     9        
    10         private void downButton_Click(object sender, EventArgs e)
    11         {
    12            
    13             if(thread==null)
    14                 thread = new System.Threading.Thread(new System.Threading.ThreadStart(StartDown));
    15 
    16             //使用子线程工作
    17              if (this.downButton.Text == "开始下载文件")
    18              {
    19                  this.downButton.Text = "停止下载文件";
    20                  if (thread.ThreadState.ToString() == "Unstarted")
    21                  {                    
    22                      thread.Start();
    23                  }
    24                  else if (thread.ThreadState.ToString() == "Suspended")
    25                      thread.Resume();
    26              }
    27              else
    28              {
    29                  this.downButton.Text = "开始下载文件";           
    30                  thread.Suspend();
    31              }
    32         }
    33 
    34         //开始加载进度
    35         public void StartDown()
    36         {
    37             //注册事件 
    38             DownLoad down = new DownLoad();
    39            down.onDownLoadProgress+=down_onDownLoadProgress;
    40            down.onDownLoadProgress += down_ShowResult;
    41            down.Start();
    42 
    43         }
    44 
    45         public void down_onDownLoadProgress(long total,long current)
    46         {
    47      
    48 
    49             if (this.InvokeRequired)
    50             {               
    51                 this.Invoke(new DownLoad.DownLoadProgress(down_onDownLoadProgress), new object[] { total, current });
    52             }
    53             else
    54             {
    55                 this.myProgressBar.Maximum = (int)total;
    56                 this.myProgressBar.Value = (int)current;                
    57             }          
    58            
    59         }
    60 
    61         public void down_ShowResult(long total,long current)
    62         {
    63             Action<long, long> ac = (c, t) => { this.resultShow.Text = ((double)current / total).ToString("P"); ; };
    64             this.Invoke(ac, new object[] { current, total });
    65         }
    66 
    67        
    68 
    69         //下载处理类
    70         class DownLoad
    71         {
    72             //委托
    73             public delegate void DownLoadProgress(long total, long current);
    74 
    75             //事件
    76             public event DownLoadProgress onDownLoadProgress;
    77 
    78             //事件
    79             public event DownLoadProgress down_ShowResult;
    80 
    81             public void Start()
    82             {
    83                 //下载模拟
    84                 for (int i = 0; i <= 100; i++)
    85                 {
    86                     if (onDownLoadProgress != null)
    87                         onDownLoadProgress(100, i);
    88                     if (down_ShowResult != null)
    89                         down_ShowResult(100, i);
    90                     System.Threading.Thread.Sleep(100);
    91                 }
    92             }
    93            
    94                   
    95         }
    96     }

    运行结果如下:

    上面示例使用winform应用程序,实现了一个进度条即时计算进度的例子。在文件下载子类(DownLoad)中有两个事件,一个是进度条事件,一个是进度百分比显示事件,在初始化调用时,采用了线程。启用线程时,注册了两个事件。随着模拟进度的加载,触发了进度条事件和显示百分比事件。做到了即时显示。

    关于线程相关知识,在后面有时间了会详细说明。 

    要点

    6.1 事件:事件是对象发送的消息,发送信号通知客户发生了操作。这个操作可能是由鼠标单击引起的,也可能是由某些其他的程序逻辑触发的。事件的发送方不需要知道哪个对象或者方法接收它引发的事件,发送方只需知道它和接收方之间的中介(delegate)。

    6.2 事件处理程序总是返回void,它不能有返回值。

    6.3 只要使用EventHandler委托,参数就应是objectEventArgs。第一个参数是引发事件的对象。第二个参数EventArgs是包含有关事件的其他有用信息的对象;这个参数可以是任意类型,只要它派生自EventArgs即可。

    6.4 方法的命名也应注意,按照约定,事件处理程序应遵循“object_event”的命名约定。

    6.5 事件具有以下特点:

    1)发行者确定何时引发事件,订户确定执行何种操作来响应该事件。

    2)一个事件可以有多个订户。 一个订户可处理来自多个发行者的多个事件。

    3)没有订户的事件永远也不会引发。

    4)事件通常用于通知用户操作,例如,图形用户界面中的按钮单击或菜单选择操作。

    5)如果一个事件有多个订户,当引发该事件时,会同步调用多个事件处理程序。 要异步调用事件,请参见使用异步方式调用同步方法

    6)在 .NET Framework 类库中,事件是基于 EventHandler 委托和 EventArgs 基类的。

    6.6  事件的创建步骤

    (1)、定义delegate对象类型,它有两个参数,第一个参数是事件发送者对象,第二个参数是事件参数类对象。
    (2)、定义事件参数类,此类应当从System.EventArgs类派生。如果事件不带参数,这一步可以省略。
    (3)、定义"事件处理方法,它应当与delegate对象具有相同的参数和返回值类型"
    (4)、用event关键字定义事件对象,它同时也是一个delegate对象。
    (5)、用+=操作符添加事件到事件队列中(-=操作符能够将事件从队列中删除)。
    (6)、在需要触发事件的地方用调用delegate的方式写事件触发方法。一般来说,此方法应为protected访问限制,既不能以public方式调用,但可以被子类继承。名字是OnEventName
    (7)、在适当的地方调用事件触发方法触发事件。 

     ============================================================================================== 

    返回目录

     <如果对你有帮助,记得点一下推荐哦,有不明白的地方或写的不对的地方,请多交流>
     

    QQ群:467189533

    ==============================================================================================  

  • 相关阅读:
    长沙市资讯新闻汇总
    PC端页面同比例缩放
    操作系统之进程
    开发中遇到的一些mongoose的问题
    支持字母数字下划线和中文的正则
    将redis作为windows服务安装
    express中的会话存储方式
    getServletContext()接口解析(收藏)
    在本地计算机无法启动MYSQL服务错误1067进程意外终止
    css浮动中避免包含元素高度为0的4种解决方法
  • 原文地址:https://www.cnblogs.com/yubinfeng/p/4592387.html
Copyright © 2011-2022 走看看