程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> 深入理解.Net事件

深入理解.Net事件

編輯:關於.NET

前兩天在與朋友聊天時提到了事件,故寫下此文與他分享對事件的理解。因不敢獨享所以拿出來請大家指正。

在進行WinForm編程時,事件這個概念無時無刻都圍繞身邊。點個按鈕就是一個事件。在.Net中,事件這個概念總是讓人覺得比較復雜,有著深奧的理論,而且其中的delegate關鍵字本身就讓人覺得很深奧。

其實呢,事件並沒有那麼復雜而且深奧。只是MS為了讓程序員寫的代碼少一點,鼓搗出個代理的概念。其實如果您對Java的界面編程有所了解之後,對.Net事件的理解就會順利多了。當然,下面我們將先接觸一段Java的代碼。

在Java的GUI編程中,沒有代理這個概念,它用的是接口。我們先來看一個帶按鈕的窗口:

1import java.awt.event.ActionEvent;
2import java.awt.event.ActionListener;
3import java.awt.event.WindowAdapter;
4import java.awt.event.WindowEvent;
5import javax.swing.JButton;
6import javax.swing.JFrame;
7
8public class EventStudy {
9 public static void main(String[] args) {
10   JFrame f = new JFrame();
11   JButton b = new JButton();
12   f.addWindowListener(new WindowAdapter(){
13     @Override
14     public void windowClosing(WindowEvent e) {
15       System.exit(0);
16     }
17   });
18   f.setSize(300, 200);
19   b.setText("I'm a Button");
20   b.addActionListener(new ActionListener(){
21     @Override
22     public void actionPerformed(ActionEvent e) {
23       System.out.println("the Button is Clicked.");
24     }
25   });
26   f.add(b);
27   f.setVisible(true);
28 }
29}
30

現在,我們來看看上面的代碼。這是一個包含了一個代碼的窗體。其中,當單擊了按鈕之後,在控制台上會顯示“the Button is Clicked.”那麼Java是怎麼做到這些事情的呢?我們先來看看下面這段代碼:

1b.addActionListener(new ActionListener(){
2  @Override
3  public void actionPerformed(ActionEvent e) {
4    System.out.println("the Button is Clicked.");
5  }
6});

這段代碼其含義就是向JButton對象注冊一個事件監聽器。在Java中,事件監聽器就是一個接口。比如這個ActionListener接口,就包含一個actionPreformed方法。繼承了這個接口的類就可以傳入JButton對象的addActionListener方法中。一旦我們單擊按鈕之後,Swing的事件處理機制會調用actionPerformed方法,並把一個ActionEvent對象傳入這個方法。

好了,讓我們回到.Net。首先我們通過一個例子模擬一下按鈕被單擊的效果吧。當然,這個按鈕並不是一個真正的按鈕,不過是一個自己編寫的類罷了。

1using System;
2using System.Collections.Generic;
3using System.Linq;
4using System.Text;
5
6namespace EventStudy
7{
8  interface ActionListener
9  {
10    void actionPreformed();
11  }
12
13  class MyButton
14  {
15    private ActionListener al = null;
16    public MyButton() { }
17    public string Text { get; set; }
18
19    public void setActionListener(ActionListener al)
20    {
21      this.al = al;
22    }
23
24    public void ClickMe()
25    {
26      if(al != null)
27        al.actionPreformed();
28    }
29  }
30
31  class Program
32  {
33    class myActionListener : ActionListener
34    {
35      ActionListener Members#region ActionListener Members
36      public void actionPreformed()
37      {
38        Console.WriteLine("Button Clicked!");
39      }
40      #endregion
41    }
42
43    static void Main(string[] args)
44    {
45      MyButton b = new MyButton();
46      b.Text = "A Button.";
47      b.setActionListener(new myActionListener());
48      b.ClickMe();
49    }
50  }
51}

首先呢,我們要有一個ActionListener接口,然後是MyButton類來模擬實際的按鈕。MyButton類中的ClickMe方法就是模擬人工單擊按鈕這個過程。

最後,我們就在Main函數中設定好一切,然後“單擊”這個按鈕。

一般來說,當我們單擊一個按鈕之後,Windows會重繪按鈕的圖片,讓它看起來像是被按下去一樣,然後再去調用Click事件。在此我們省略重繪按鈕圖片的過程,直接讓它觸發事件。因此,ActionListener對象的actionPreformed方法就會被調用。

當然,上面用的是Java GUI對事件處理的設計模式。換到.Net中,我們可以把這個只包含一個方法的接口換成一個代理:

1namespace EventStudy
2{
3  delegate void OnClick();
4
5  class MyButton
6  {
7    private OnClick click = null;
8    public MyButton() { }
9    public string Text { get; set; }
10
11    public void setOnClickEvent(OnClick oc)
12    {
13      this.click = oc;
14    }
15
16    public void ClickMe()
17    {
18      if (click != null)
19        click();
20    }
21  }
22
23  class Program
24  {
25    static void Main(string[] args)
26    {
27      MyButton b = new MyButton();
28      b.Text = "A Button.";
29      b.setOnClickEvent(delegate()
30      {
31        Console.WriteLine("Button Clicked!");
32      });
33      b.ClickMe();
34    }
35  }
36}

現在,接口變成代理了,相應的一些代碼也有所改動。其結果還是一樣,不過代碼確實可以少寫點了。

對於事件處理,有一個多播的概念。那麼多播是怎麼回事呢?其實就是向一個事件注冊多個監聽者。如果不好理解就來看看下面的代碼吧:

1class MyButton
2{
3  private List<OnClick> clickEvents = new List<OnClick>();
4  public MyButton() { }
5  public string Text { get; set; }
6
7  public void addOnClickEvent(OnClick oc)
8  {
9    this.clickEvents.Add(oc);
10  }
11
12  public void ClickMe()
13  {
14    foreach (OnClick click in this.clickEvents)
15      click();
16  }
17}
18
19class Program
20{
21  static void Main(string[] args)
22  {
23    MyButton b = new MyButton();
24    b.Text = "A Button.";
25    b.addOnClickEvent(delegate()
26    {
27      Console.WriteLine("First Listener:Button Clicked!");
28    });
29    b.addOnClickEvent(delegate()
30    {
31      Console.WriteLine("Second Listener:Button Clicked!");
32    });
33    b.ClickMe();
34  }
35}

其實多播事件就是先用一個容器來保存所有注冊的事件監聽器(或者說處理函數),然後在觸發事件時順序地調用這些事件監聽器。

當然,在.Net中有一個event關鍵字來讓我們更輕松地完成這個任務:

1class MyButton
2{
3  public event OnClick Click;
4  public MyButton() { }
5  public string Text { get; set; }
6  
7  public void ClickMe()
8  {
9    Click();
10  }
11}
12
13class Program
14{
15  static void Main(string[] args)
16  {
17    MyButton b = new MyButton();
18    b.Text = "A Button.";
19    b.Click += delegate()
20    {
21      Console.WriteLine("First Listener:Button Clicked!");
22     };
23     b.Click += delegate()
24     {
25       Console.WriteLine("Second Listener:Button Clicked!");
26     };
27     b.ClickMe();
28  }
29}

現在,我們的MyButton已經非常接近實際.Net類庫中的事件處理的設計方式了。目前的差距就是關於事件處理函數的參數。一般來說,WinForm的控件的事件處理函數都會包含兩個參數:sender和e,一個是觸發事件的對象sender和這個事件相關的參數。要讓我們的MyButton更貼近真實,我們來看看如何加入這兩個內容:

1class MyClickEventArgs : EventArgs
2{
3  public string MyMessage { set; get; }
4}
5
6delegate void OnClick(object sender, MyClickEventArgs e);
7
8class MyButton
9{
10  public event OnClick Click;
11  public MyButton() { }
12  public string Text { get; set; }
13
14  public void ClickMe()
15  {
16    MyClickEventArgs e = new MyClickEventArgs();
17    e.MyMessage = "This is a Message";
18    Click(this, e);
19  }
20}
21
22class Program
23{
24  static void Main(string[] args)
25  {
26    MyButton b = new MyButton();
27    b.Text = "A Button.";
28    b.Click += delegate(object sender, MyClickEventArgs e)
29    {
30      Console.WriteLine("Button Clicked!");
31      Console.WriteLine("Message:{0}", e.MyMessage);
32      Console.WriteLine("Button Text:{0}", ((MyButton)sender).Text);
33    };
34    b.ClickMe();
35  }
36}

首先,事件參數應該從EventArgs基類繼承。當然,編寫自己的事件參數類也不是不可以。至於sender,就是MyButton對象自己。至於EventArgs中到底有什麼參數應該視情況而定。

最後,讓我們來看看事件的先後順序。比如按鈕中的MouseDown,MouseUp,MouseClick這三個事件是順序觸發的。那麼這個過程是如何來實現的呢。下面我們繼續修改上面的代碼,加入兩個新的事件:BeforeClick和AfterClick。

1delegate void ClickEventHanler(object sender, MyClickEventArgs e);
2 
3class MyButton
4{
5  public event ClickEventHanler BeforeClick;
6  public event ClickEventHanler OnClick;
7  public event ClickEventHanler AfterClick;
8
9  public MyButton() { }
10
11  public string Text { get; set; }
12
13  public void ClickMe()
14  {
15    MyClickEventArgs e;
16
17    e = new MyClickEventArgs();
18    e.MyMessage = "Before Click";
19    BeforeClick(this, e);
20
21    e = new MyClickEventArgs();
22    e.MyMessage = "On Click";
23    OnClick(this, e);
24
25    e = new MyClickEventArgs();
26    e.MyMessage = "After Click";
27    AfterClick(this, e);
28  }
29}
30
31class Program
32{
33  static void Main(string[] args)
34  {
35    MyButton b = new MyButton();
36    b.Text = "A Button.";
37    b.BeforeClick += new ClickEventHanler(Program.HandleEvent);
38    b.OnClick += new ClickEventHanler(Program.HandleEvent);
39    b.AfterClick += new ClickEventHanler(Program.HandleEvent);
40    b.ClickMe();
41  }
42
43  public static void HandleEvent(object sender, MyClickEventArgs e)
44  {
45    Console.WriteLine("Button Text:{0}", ((MyButton)sender).Text);
46    Console.WriteLine("Message:{0}", e.MyMessage);
47  }
48}

這次為了方便,我們把所有的事件處理代理都聲明為ClickEventHandler。然後在MyButton類中聲明3個事件:BeforeClick,OnClick,AfterClick。在Program類中,聲明一個HandleEvent方法作為事件處理的一個公共方法。

在這個例子中,變動最大的是ClickMe方法,這個方法會按照順序觸發3個事件,這樣我們就可以看到注冊的3個事件按照先後順序來觸發了。

至於為什麼每次觸發事件都要重新new一個EventArgs,這考慮到多線程的問題。當然,在沒有什麼苛刻環境的情況下,改變EventArgs的屬性然後再傳入事件也是可以的。至於每次都會new一個新的對象,在語意上也是合理的。畢竟每個事件的參數都應該是獨立的。

看完上面的這些例子之後,你會發現事件並沒有想像中那麼復雜,無非是一個函數的調用而已。而.Net之所以整出這麼多新的概念其目的就是讓我們編程的時候更加簡潔。想想看,是Java的事件監聽器寫起來方便還是.Net的代理寫起來方便呢?

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved