深入理解.Net事件

前两天在与朋友聊天时提到了事件,故写下此文与他分享对事件的理解。因不敢独享所以拿出来请大家指正。

  在进行WinForm编程时,事件这个概念无时无刻都围绕身边。点个按钮就是一个事件。在.Net中,事件这个概念总是让人觉得比较复杂,有着深奥的理论,而且其中的delegate关键字本身就让人觉得很深奥。

其实呢,事件并没有那么复杂而且深奥。只是MS为了让程序员写的代码少一点,鼓捣出个代理的概念。其实如果您对Java的界面编程有所了解之后,对.Net事件的理解就会顺利多了。当然,下面我们将先接触一段Java的代码。

JavaGUI编程中,没有代理这个概念,它用的是接口。我们先来看一个带按钮的窗口:


 1 import  java.awt.event.ActionEvent;
 2 import  java.awt.event.ActionListener;
 3 import  java.awt.event.WindowAdapter;
 4 import  java.awt.event.WindowEvent;
 5 import  javax.swing.JButton;
 6 import  javax.swing.JFrame;
 7
 8 ExpandedBlockStart.gifContractedBlock.gif public   class  EventStudy  {
 9ExpandedSubBlockStart.gifContractedSubBlock.gif public static void main(String[] args) {
10      JFrame f = new JFrame();
11      JButton b = new JButton();
12ExpandedSubBlockStart.gifContractedSubBlock.gif      f.addWindowListener(new WindowAdapter(){
13         @Override
14ExpandedSubBlockStart.gifContractedSubBlock.gif         public void windowClosing(WindowEvent e) {
15             System.exit(0);
16         }

17      }
);
18      f.setSize(300200);
19      b.setText("I'm a Button");
20ExpandedSubBlockStart.gifContractedSubBlock.gif      b.addActionListener(new ActionListener(){
21         @Override
22ExpandedSubBlockStart.gifContractedSubBlock.gif         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是怎么做到这些事情的呢?我们先来看看下面这段代码:


1 ExpandedBlockStart.gif ContractedBlock.gif b.addActionListener( new  ActionListener() {
2    @Override
3ExpandedSubBlockStart.gifContractedSubBlock.gif    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。首先我们通过一个例子模拟一下按钮被单击的效果吧。当然,这个按钮并不是一个真正的按钮,不过是一个自己编写的类罢了。

 1 using  System;
 2 using  System.Collections.Generic;
 3 using  System.Linq;
 4 using  System.Text;
 5
 6 namespace  EventStudy
 7 ExpandedBlockStart.gifContractedBlock.gif {
 8    interface ActionListener
 9ExpandedSubBlockStart.gifContractedSubBlock.gif    {
10        void actionPreformed();
11    }

12
13    class MyButton
14ExpandedSubBlockStart.gifContractedSubBlock.gif    {
15        private ActionListener al = null;
16ExpandedSubBlockStart.gifContractedSubBlock.gif        public MyButton() { }
17ExpandedSubBlockStart.gifContractedSubBlock.gif        public string Text getset; }
18
19        public void setActionListener(ActionListener al)
20ExpandedSubBlockStart.gifContractedSubBlock.gif        {
21            this.al = al;
22        }

23
24        public void ClickMe()
25ExpandedSubBlockStart.gifContractedSubBlock.gif        {
26            if(al != null)
27                al.actionPreformed();
28        }

29    }

30
31    class Program
32ExpandedSubBlockStart.gifContractedSubBlock.gif    {
33        class myActionListener : ActionListener
34ExpandedSubBlockStart.gifContractedSubBlock.gif        {
35ContractedSubBlock.gifExpandedSubBlockStart.gif            ActionListener Members#region ActionListener Members
36            public void actionPreformed()
37ExpandedSubBlockStart.gifContractedSubBlock.gif            {
38                Console.WriteLine("Button Clicked!");
39            }

40            #endregion

41        }

42
43        static void Main(string[] args)
44ExpandedSubBlockStart.gifContractedSubBlock.gif        {
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中,我们可以把这个只包含一个方法的接口换成一个代理:


 1 namespace  EventStudy
 2 ExpandedBlockStart.gifContractedBlock.gif {
 3    delegate void OnClick();
 4 
 5    class MyButton
 6ExpandedSubBlockStart.gifContractedSubBlock.gif    {
 7        private OnClick click = null;
 8ExpandedSubBlockStart.gifContractedSubBlock.gif        public MyButton() { }
 9ExpandedSubBlockStart.gifContractedSubBlock.gif        public string Text getset; }
10 
11        public void setOnClickEvent(OnClick oc)
12ExpandedSubBlockStart.gifContractedSubBlock.gif        {
13            this.click = oc;
14        }

15 
16        public void ClickMe()
17ExpandedSubBlockStart.gifContractedSubBlock.gif        {
18            if (click != null)
19                click();
20        }

21    }

22 
23    class Program
24ExpandedSubBlockStart.gifContractedSubBlock.gif    {
25        static void Main(string[] args)
26ExpandedSubBlockStart.gifContractedSubBlock.gif        {
27            MyButton b = new MyButton();
28            b.Text = "A Button.";
29            b.setOnClickEvent(delegate()
30ExpandedSubBlockStart.gifContractedSubBlock.gif            {
31                Console.WriteLine("Button Clicked!");
32            }
);
33            b.ClickMe();
34        }

35    }

36}

 

现在,接口变成代理了,相应的一些代码也有所改动。其结果还是一样,不过代码确实可以少写点了。

对于事件处理,有一个多播的概念。那么多播是怎么回事呢?其实就是向一个事件注册多个监听者。如果不好理解就来看看下面的代码吧:

  

 1 class  MyButton
 2 ExpandedBlockStart.gifContractedBlock.gif {
 3    private List<OnClick> clickEvents = new List<OnClick>();
 4ExpandedSubBlockStart.gifContractedSubBlock.gif    public MyButton() { }
 5ExpandedSubBlockStart.gifContractedSubBlock.gif    public string Text getset; }
 6
 7    public void addOnClickEvent(OnClick oc)
 8ExpandedSubBlockStart.gifContractedSubBlock.gif    {
 9        this.clickEvents.Add(oc);
10    }

11
12    public void ClickMe()
13ExpandedSubBlockStart.gifContractedSubBlock.gif    {
14        foreach (OnClick click in this.clickEvents)
15            click();
16    }

17}

18
19 class  Program
20 ExpandedBlockStart.gifContractedBlock.gif {
21    static void Main(string[] args)
22ExpandedSubBlockStart.gifContractedSubBlock.gif    {
23        MyButton b = new MyButton();
24        b.Text = "A Button.";
25        b.addOnClickEvent(delegate()
26ExpandedSubBlockStart.gifContractedSubBlock.gif        {
27            Console.WriteLine("First Listener:Button Clicked!");
28        }
);
29        b.addOnClickEvent(delegate()
30ExpandedSubBlockStart.gifContractedSubBlock.gif        {
31            Console.WriteLine("Second Listener:Button Clicked!");
32        }
);
33        b.ClickMe();
34    }

35}

 

  其实多播事件就是先用一个容器来保存所有注册的事件监听器(或者说处理函数),然后在触发事件时顺序地调用这些事件监听器。

当然,在.Net中有一个event关键字来让我们更轻松地完成这个任务:

 

 1 class  MyButton
 2 ExpandedBlockStart.gifContractedBlock.gif {
 3    public event OnClick Click;
 4ExpandedSubBlockStart.gifContractedSubBlock.gif    public MyButton() { }
 5ExpandedSubBlockStart.gifContractedSubBlock.gif    public string Text getset; }
 6    
 7    public void ClickMe()
 8ExpandedSubBlockStart.gifContractedSubBlock.gif    {
 9        Click();
10    }

11}

12
13 class  Program
14 ExpandedBlockStart.gifContractedBlock.gif {
15    static void Main(string[] args)
16ExpandedSubBlockStart.gifContractedSubBlock.gif    {
17        MyButton b = new MyButton();
18        b.Text = "A Button.";
19        b.Click += delegate()
20ExpandedSubBlockStart.gifContractedSubBlock.gif        {
21            Console.WriteLine("First Listener:Button Clicked!");
22         }
;
23         b.Click += delegate()
24ExpandedSubBlockStart.gifContractedSubBlock.gif         {
25             Console.WriteLine("Second Listener:Button Clicked!");
26          }
;
27          b.ClickMe();
28    }

29}

现在,我们的MyButton已经非常接近实际.Net类库中的事件处理的设计方式了。目前的差距就是关于事件处理函数的参数。一般来说,WinForm的控件的事件处理函数都会包含两个参数:sendere,一个是触发事件的对象sender和这个事件相关的参数。要让我们的MyButton更贴近真实,我们来看看如何加入这两个内容:


 1 class  MyClickEventArgs : EventArgs
 2 ExpandedBlockStart.gifContractedBlock.gif {
 3ExpandedSubBlockStart.gifContractedSubBlock.gif    public string MyMessage setget; }
 4}

 5
 6 delegate   void  OnClick( object  sender, MyClickEventArgs e);
 7
 8 class  MyButton
 9 ExpandedBlockStart.gifContractedBlock.gif {
10    public event OnClick Click;
11ExpandedSubBlockStart.gifContractedSubBlock.gif    public MyButton() { }
12ExpandedSubBlockStart.gifContractedSubBlock.gif    public string Text getset; }
13
14    public void ClickMe()
15ExpandedSubBlockStart.gifContractedSubBlock.gif    {
16        MyClickEventArgs e = new MyClickEventArgs();
17        e.MyMessage = "This is a Message";
18        Click(this, e);
19    }

20}

21
22 class  Program
23 ExpandedBlockStart.gifContractedBlock.gif {
24    static void Main(string[] args)
25ExpandedSubBlockStart.gifContractedSubBlock.gif    {
26        MyButton b = new MyButton();
27        b.Text = "A Button.";
28        b.Click += delegate(object sender, MyClickEventArgs e)
29ExpandedSubBlockStart.gifContractedSubBlock.gif        {
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。

 

 1 delegate   void  ClickEventHanler( object  sender, MyClickEventArgs e);
 2   
 3 class  MyButton
 4 ExpandedBlockStart.gifContractedBlock.gif {
 5    public event ClickEventHanler BeforeClick;
 6    public event ClickEventHanler OnClick;
 7    public event ClickEventHanler AfterClick;
 8
 9ExpandedSubBlockStart.gifContractedSubBlock.gif    public MyButton() { }
10 
11ExpandedSubBlockStart.gifContractedSubBlock.gif    public string Text getset; }
12 
13    public void ClickMe()
14ExpandedSubBlockStart.gifContractedSubBlock.gif    {
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  
31 class  Program
32 ExpandedBlockStart.gifContractedBlock.gif {
33    static void Main(string[] args)
34ExpandedSubBlockStart.gifContractedSubBlock.gif    {
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)
44ExpandedSubBlockStart.gifContractedSubBlock.gif    {
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的代理写起来方便呢?

转载于:https://www.cnblogs.com/blacktear23/archive/2008/11/14/1333375.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值