C++与C#对比学习:消息,事件(一)
窗体(windows),事件(event),消息(message)
我们大部分人用的操作系统都是微软的windows系列.windows顾名思义是窗口,窗体.它最大的特点就是几乎所有操作都是图形界面.在面向对象开发中是一切皆对象,那在windows的操作中是一切皆窗体.窗体背后对应的是消息机制.是以消息以基础,以事件为驱动.所谓窗体就是我们能用眼睛看得到的一个个图形界面,几乎任何可显示的实体都是窗口,有时弹出的对话框也算个小窗体,一个按钮也算窗体.一个界面可以由多个窗体组合而成.事件就代表着所有的操作,比如鼠标动一下,点一下,窗口拉大拉小,文本框输入文字等...而事件发生后是以消息的形式发给应用程序对应的函数去处理.所以窗体,事件,消息三者的关系是我们对窗体的操作就是一个事件,事件会产生消息.
windows操作系统是事件驱动,那运行在它上面的绝大部分应用程序自然也是事件驱动.应用程序大部分时候是傻傻的呆那不动的,只有你去点下它才会动起来,做相应的操作.当然了有些应用程序可能会有些后台操作,比如设个时钟,每过多久自动刷新下或做些其他啥操作.
我觉得人其实也有点像个事件驱动的机器了.我们接受各种内部或外部的易做图,然后产生各种反应.就算在睡觉时也在接受易做图做些本能的反应.行为主义心理学派就认为人根本没有所谓的自由意志,就只知道通过一些易做图做些反应.只不过各种易做图非常复杂,并且反应也是一系列的连锁反应,所以我们不容易感觉的到.当然各大 心理学派所持的观点不一样,那些理论我们也不容易证否.跟算法里面著名的NP难问题有点类似,我们不知道NP难问题是不是能解决.不能证明它能解决,也不能证明它不能解决.
消息控件一般分三层结构.
第一层,windows内核.里面维护一个消息队列,像鼠标,键盘等一些IO设备产生事件后不是直接传给应用程序,而是先易做图作系统处理,转换成一个个的"消息".由于创建窗体时会指定一个应用程序句柄,所以当操作某个窗体产生时操作系统知道哪些消息是属于哪个应用程序的,操作系统为每个应用程序维护一个消息队列.
第二层,应用程序.在windows API的编程中一般通过一个while循环,用GetMessage或PeekMessage来获取消息队列中的消息.不过这些信息还只相当于是原材料,需要进一步加工.通过TranslateMessage把它进一步加工,做一些转换,或者如果是些无用的消息就会被直接忽略掉.加工完了后又通过DispatchMessage把信息重新传回操作系统.每一个消息实际上是一个一个结构体,里面有些相关信息,里面有一个窗体句柄.这样我们就知道这个消息是哪个窗体产生的.
第三层,窗体过程(windows procedure),创建窗体时会指定一个窗体过程,实际上就是窗体对应的一些处理消息的函数.当应用程序把消息重新传回操作系统后.操作系统再把消息发送给窗口过程,窗口过程就根据消息中的一些信息做些判断做些不同的处理.
C#中的事件处理机制
在C#中做了很多的封装,我们根本看不到消息的处理过程.通过层层封装最后我们看到的只是事件(event)这个概念,在C#中event是个关键字,也可以看做某种类型.当对窗体做一些操作时可以生成一个个的事件.比如点击按键btnOK,它对应的事件是btnOK.Click.(Click是C#中事先定义好了的事件,我们只要拿来用就行.)其实我们可以这样简单的理解,消息控制的三层结构中,前面所有操作都给你封装了,直到最后指定一个个的窗体过程,而且不同的消息对应的窗体过程都指定好的了.只不过那个过程为空,有点相当于个函数指针,要你自己去指定一个具体的函数.在C#中没有指针,但有个概念叫代理(delegate),它类似于函数指针.而事件相当于是对代理的封装,或者说是代理的一种应用.代理还可以用在其他很多地方.
比如定义事件Click是这样定义的.
(1)public event EventHandler Click; // 其中EventHandler是一个代理类型.定义事件的规范格式就是用关键字event然后加代理类型,然后再加变量名.定义代理EventHandler的格式是这样的.
(2)public delegate void EventHandler(object sender, EventArgs e);//它表示EventHandler可以指向任意以object,EventArgs为参数,返回类型是void的函数.我们前面说了事件Click还只相当于一个空的窗体过程.那我们怎么去指定具体的函数来.我们可以通过+=这样的关键字来指定一个事件处理程序.比如
(3)btnOK.Click += new System.EventHandler(btnOK_Click); //其中btnOK_Click是函数名,你也可以取其他任何名字.然后在class中任何地方定义函数
而且绑定多个像+=添加字符串一样直添加.当一个事件绑定了多个函数时,事件触发时多个函数会全部执行.当然我们还能通过-=这样的关键字解除某个函数的绑定.
void btnOK_Click(object sender, EventArgs e)
{
//操作相应操作的代码
}
反正C#中所有与窗口或控件相关的事件都给你封装好了,你直接拿来用,只要绑定个具体的函数就行.另外事件绑定的函数类型一般是两个参数,其中第一个是object sender,它表明是哪个窗体对象触发的这个事件.如果是按钮的话可以把把这个object对象转换成button对象,然后获取按钮其他信息(button)sender.另外一个参数是EventArgs,其实这个参数有时会不一样,如果是键盘触发的事件它就是KeyPressEventArgs了,它是附带有其他一些信息,比如键盘按的哪个按钮啊.
不过实际上我们也很少用到这两参数,让它们摆在那不管,自己在函数中写其他代码就行了.
自定义事件
另外除了预定义好的事件可以直接拿来用外,我们还可以自己定义些事件,使用的语法格式完全一样.自已定义的事件不一定得是窗体事件,可以是其他任何东东.比如某个变量值达到多少时触发个事件之类的。
自定义的事件具体是怎么触发它执行的由于已经封装了我们也看不到.不过如果是自定义事件肯定得指定怎么去执行.举个简单的例子看下吧.
public delegate void SayHello(string str); //声明一个代理
public event SayHello onSayHello; //定义一个事件onSayHello
触发事件执行的代码就是
string msg = "arwen";
onSayHello(msg); //看起来跟调用一个函数没啥区别了.当然你调用它之前还得先绑定一个函数给它,不然会出错的.所以一般用if(onSayHello != null)这样判断一下,如果事件还没绑定啥函数上去就为null
那既然事件都跟调用函数都差不多了,还不如直接调用函数或者调用代理,为啥搞得多此一举,再整个事件来啊.
用自定义事件可能基于两种原因吧,当然这是我自己瞎猜的啊
一,是体现一种设计模式,订阅者模式.
二,是当体现封装原则.
比如一个class想调用另外一个class中某个函数时,不想让直接调用,或者函数声明为private的,就可通过event间接的调用.因为delegate相当于指向函数的指针,而event又相当于是对delegate的封装.那你可能又问封装有啥好的啊,这可能是体现一种设计思想,对以后扩充功能或做一些额外的处理有用吧.不然像像我们把所以类中的字段都给封装成一个个的属性(property),大部分时候是一点都看不出来有啥用,反而觉得有点多此一举,还不如直接用字段别用属性呢.但如果我们有时想在属性中做些额外的处理时特别有用.比如只让你读取字段值不让赋值,或者反过来.
举个用自定义事件调用其他类中的private函数的简单例子吧.
在class A中有private的函数sayHello.那class B中肯定不能直接调用sayHello了.当然我们另外还要假设在A中用到了B.
delegate void DelegateSayHello(string name); //在类外面某个地方,它其实也可以看成一种特殊的类了.当然也可以放在某个类里面定义
public class A
{
B sb;
sb.HowAreYou += new DelegateSayHello(sayHello);
private void sayHello(string name)
{
Console.WriteLine("Hello," + name);
}
}
public class B
{
public event DelegateSayHello HowAreYou;
string name = "arwen";
private void DoSomething()
{
HowAreYou(name); //这里就调用了class A中的private函数sayHello了啊
}
}
补充:软件开发 , C++ ,