[.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+" 過來了,喵!喵!喵!\n");
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 + " 說:\"老貓來了,快跑!\"  \n我跑!!\n我使勁跑!!\n我加速使勁跑!!!\n");
39         }
40     }

調用如下:

 1             Console.WriteLine("[場景說明]: 一個月明星稀的午夜,有兩只老鼠在偷油吃\n");
 2             Mouse Jerry = new Mouse("Jerry", "白色");
 3             Mouse Jack = new Mouse("Jack", "黃色");
 4 
 5 
 6             Console.WriteLine("[場景說明]: 一只黑貓躡手躡腳的走了過來\n");
 7             Cat Tom = new Cat("Tom", "黑色");
 8 
 9             Console.WriteLine("[場景說明]: 為了安全的偷油,登記了一個貓叫的事件\n");
10             Tom.CatShoutEvent += new Cat.CatShoutEventHandler(Jerry.MouseRun);
11             Tom.CatShoutEvent += new Cat.CatShoutEventHandler(Jack.MouseRun);
12 
13             Console.WriteLine("[場景說明]: 貓叫了三聲\n");
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+" 過來了,喵!喵!喵!\n");
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 + " 來了,快跑!\"  \n我跑!!\n我使勁跑!!\n我加速使勁跑!!!\n");
51             else
52                 Console.WriteLine(mouseColor + " 的老鼠 " + mouseName + " 說:\" " + e.catColor + "" + e.catName + " 來了,慢跑!\"  \n我跑!!\n我慢慢跑!!\n我慢慢悠悠跑!!!\n");
53 
54         }
55     }

調用如下:

 1 Console.WriteLine("[場景說明]: 一個月明星稀的午夜,有兩只老鼠在偷油吃\n\n\n");
 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("[場景說明]: 貓叫了三聲\n");
12 Tom.CatShout();
13 
14 Console.WriteLine("\n\n\n");
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 + "\"已被刪除或更名;\n");
 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 + ");\n");
 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+"\"\n");
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+";\n");
 4         }
 5         static void watcher_Deleted(object sender, System.IO.FileSystemEventArgs e)
 6         {
 7             Console.WriteLine("文件\"" + e.Name + "\"已被刪除;\n");
 8         }
 9         static void watcher_Changed(object sender, System.IO.FileSystemEventArgs e)
10         {
11             Console.WriteLine("文件\"" + e.Name + "\"已被修改;\n");
12         }
13         static void watcher_Created(object sender, System.IO.FileSystemEventArgs e)
14         {
15             Console.WriteLine("新創建了文件\"" + e.Name + "\";\n");
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

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM