版权所有:www.d2school.com 作者:南郁第一篇 看透 Windows第三章 消息的世界(一) 什么是消息
什么是消息?真是无法解释。如果问什么是“短消息”,那倒是好回答一点:就是你在手机上七按八摁,最后按“发送”键以后,能导致中国移动或联通扣掉你1毛或1.5毛不等的东西。或者,就是当 你从北京坐火车到福建,一路上手机收到的各省人民对你的路过所表达的,七七八八的致意: “河北移动欢迎你!”、“欢迎来到江西省,江西移动随时为您服务!”……最后搞到我手机没电,结果没收到福建移动对我的欢迎。我在想会是什么呢?估计是一句:“嘿嘿,你总算到了!”。 当然,这些都是垃圾信息。删掉就是。不过,如果收到的是其它信息呢?我想起一个笑话。说是一个家伙在国庆长假时闲着,突发奇想要问候一下自己的老板,于是发过去短信一则: “老板,节日快乐!” 很快,老板回短信了: “谢谢,也祝你快乐。对了,今天公司没人值班,你能不能过去盯一盯?”
现在可以提“消息驱动”这个术语了。其实很简单不是吗?那家伙之所以在假日暴走街头,赶往公司,就是被那条老板回来的消息给“驱动”的。任何标准的Windows程序都要受各种各样的消息“驱动”,同时,也会发出各种各样的消息去驱动别的程序,或驱动自己。 下图示例了标准Windows程序最上层的逻辑——即一个消息接收与处理的循环。
(任何Windows程序,最上层都是一个消息循环) 解疑: 一些已经写过不少程序的程序员,可能会对“任何Windows程序都是一个消息循环”这种说法表示质疑:“我写了好多程序,可是什么消息也没发现啊?”。事实上,这是因为,当我们用VB,PB,JAVA,Delphi或我们在用的CB写Windows程序时,平常许多消息处理的过程,都被这些工具背后提供的代码封装起来,消息处理多数以一种称为“事件”的形式展现。但这并不等于我们就不需要去了解Windows更本质的内容,否则,我们的课题也不会叫做“看透”了。
理论上的东东总是不直观。来一个例子,相信大家会有点感觉。
3.1 向“画图”程序发消息请按以下两个步骤做一遍:
第一步:鼠标点击任务栏上“开始”按钮,“所有程序”或“程序”-> “附件” -> “画图”。会看到下面这个Windows老朋友。自打有了Windows,就有这个程序。
(画图程序) 确保: 你所看到的“画图”程序的标题文字,必须如上图所示的“未命名 - 画图”。如果不是,请点击画图的主菜单“文件”,然后选“新建”。
第二步:打开C++ Builder。新建一个Windows应用程序工程。
2.1 在默认创建的TForm1里,放一个TButton。(如果不会,看看上一章)。 2.2 双击Button1,在编辑窗口里写入以下代码(粗体部分):
(Button1 OnClick 事件代码) 解释:
a)NULL 是一个宏,即: #define NULL (void* 0)。 你可以理解它为:0,或才一个空的地址。(关于地址,请参见《白话C++》指针章节)。 b) HWND 也是一个类型定义别名,即:typedef void* HWND。即,它是一个无类型的指针。你可以理解它只是一个编号:用于给各个程序里的各个窗口编号。我们也习惯称这种编号为“句柄”。
辅助:本章辅助教材将提供如何查看这些宏或别名原始定义的方法。
b)FindWindow 是 Windows提供的API函数(见《白话 C++》函数章节)。该函数需要两个参数:要查找的窗口的“类名”和“标题”。“类名”我们暂不解释,这里我们也不按“类名”查找,所以我给了一个NULL。标题则为“未命名 - 画图”。请注意其间的2个空格。如果竟然是用非中文版的Windows。那只好自己去看标题是什么了。FindWindows的意思是“查找Window”。顾名思义,它会去找指定的窗口。我们这里要让它找到画图程序主窗口的编号(句柄)。
辅助:本章辅助教材将提供如何查找API函数说明的方法,同时教学员配置一个更加得心应手C++Builder集成环境。
c)FindWindows 有没有找到我们想要的“画图”程序的主窗口呢?我们用这句话来判断: if (hWnd != NULL) 即,如果找到的编号不是一个0,就是找到了。 d) 找到以后做什么?这是这个程序的关键。我们将向找到的窗口发一个消息。SendMessage也是一个API函数。同样顾名思义:它的功能是“发送消息”。它需要4个参数: 第一个参数指明“向谁发消息”,即目标窗口,这里是我们找到的,画图程序的主窗口。 第二到四个参数,都用于表明“发送什么消息”。WM_CLOSE 也是一个宏。其实它肯定整数,可能是101,也可能是777?具体是多少,如果你用VB或PB编号,恐怕是要费一番力气去查一查。而像我们用C++,就不必了,因为Windows本身是用C写的,我们直接就用Windows提供的宏即可。不过这个宏是什么意思呢?在Windows,“消息”由三个整数组成:第一个整数是消息的编号,其余两个是消息附加的参数。“WM_CLOSE”表示让一个窗口关闭/Close的消息。这个消息不需要什么参数,所以剩下的两个参数,我们都填0,即: SendMessage(hWnd, WM_CLOSE, 0,0) //表示:向hWnd这个窗口,发送一个WM_CLOSE消息,附带参数都为0。 e)想一想,我们的程序现在是不是有些像“中国移动”了?人家画图程序好好地呆在屏幕上,一动也不动,正休息呢,我们就不管不顾地发给它一个消息。更过份的是,中国移动好歹是发个“欢迎”消息,而我们发的呢,一会儿你就会明白,这是一个“滚蛋”消息。
一个程序,接收到另一程序强制发来的“滚蛋”消息。结果会如何呢?有两种:第一种是“逆来顺受”型,即默默地真的消失了。另一种是“坚决不理”型。 画图程序,这个Windows的老员工,就是一个典型的逆来顺受型。废话少说。保存工程,代码。按F9吧!确保刚才你的画图还在桌面上运行着。然后按我们的程序的Button1。你就会发现画图程序自动退出了。
尝试:在电脑桌面上双击“我的电脑”,打开“我的电脑”窗口。然后修改前面代码中的要找的窗口标题为“我的电脑”。重新编译,运行,点击Button1……结果是什么?曾经,我记得Windows95吧?这是可以的,那时的Windows的“我的电脑”窗口。也可以用这种方法关闭。而现在,我只听到“当”一声……(这“当”一声表示什么?表示“我的电脑”对我们试图通过发“短消息”来当掉它的做法表示不满吗?)
辅助:辅助教材将提供以同样的原理实现的“密码查看器”。我和很多网民一样,曾经在网上申请好多免费email。然后在机器上装一个Foxmail.登录这些信箱都需要密码,可我在Foxmail时一一设好以后,用上1个月,我就忘了这些密码是什么了。Foxmail密码一栏里显示的已经是“*******”了。怎么办?还是通过向Foxmail强行发一个“我要看你的文字”的消息,然后就看Foxmail是“逆来顺受”还是“坚决不理”了。
3.2 在VCL里处理自定义消息
未经商量,就向另一个程序(比如“画图”)发消息,怎么说也显得不礼貌。 下面我们自己往自己写的窗口发消息。 Borland C++ Builder 提供一套主要用于封门Windows各种窗口的形为的程序框架,称为VCL(可视化元件库)。当然也包括了处理各种Windows常见消息的方法。因此,多数情况下,我们并不用去操心如何处理那些所有应用程序都不得不处理的消息。事实上,这个处理过程是相当无趣的,因为千篇一律。不过,Windows还允许我们自定义消息,然后在程序中发送和处理这些消息。
有了更有趣,也为了更好的了解一个“消息”从生到死的完整过程,才有了这一节。 3.2.1 自定义消息前面说了,消息需要三个无符号整数:消息的编号,消息的两个参数。 为了不和Windows预定义的消息重复,Windows规定我们自定义的消息必须从一个基数开始。这个基数,同样被定义为一个宏:WM_USER。 至于消息的两个参数,完全由我们来自定义了。事实上这只是一个“约定”,也就是说,你必须在消息的“发送者”和“接收者”之间约定好这两个参数的意义。想像一下,如果你发给你老婆一个号“7521”,你的本意是“妻我爱你”,多甜蜜啊……可是如果没有事先约定,谁敢保证你老婆不会理解为“欺我儿女”呢?所以保证这个约定不出错很重要。就算是像我们现在这样,集消息的“定义者”、“发送者”、“接受者”于一身,也得凡事小心。如何保证不出错呢?当然也是用宏定义了。将整数定义成一个个有字面意义的宏,是个比较好的办法。
由于Windows很多消息的宏,都是以“WM_”开头,为了表示区别,用户自定义消息的宏,通过以"CM_"开头。
先说说我们要做些什么?假设我们要发给窗口一个消息,窗口接到这个消息后,可以往“上、下、左、右”某个方向移动指定的距离。
//自定义消息编号,基于WM_USER,我们就从加1天始吧。 #define CM_MOVEWINDOW (WM_USER + 1)
接下来,我们做个约定:第一个参数,用于指明往哪个方向移动,为了不出错,我们定义一个枚举类型表示: enum TMoveDir {mdLeft, mdBottom, mdRight, mdUp}; //左,下,右,上
第二个参数,用来表示要移动的距离。
也就是说,假如,某个窗口收到这样一条信息: CM_MOVEWINDOW, mdLeft, 2 那么,这个窗口应该主动向左,移动2个像素。
约定好后,可以开始写程序了。让我们在CB里新建一个Windows应用工程(原来的工程记得保存)。
3.2.2 准备两个窗口
一、新工程将默认产生一个主表单,在表单上放一个标签控件:TLabel。名称为默认的Label1. 二、选择主菜单“File”->“New”->“Form”,让我们再建一个表单,新建的表单默认名为Form2,我们用它来发送消息。而原来的表单(主窗口),用来接收消息。 三、为了确保Form2能被自动创建,在完成第二步之后,请点击主菜单“Project”->“Options”,找到“Forms”,请确保Form1和Form2都出现左边的列表中。如果不是,通过界面上的“<”按钮移到左边。
(确保两个Form都在左边的列表) 确认退出第三步的对话框。 四、改变,Form1,Form2的位置,大小,并在Form2上放置四个按钮,注意它们在图上摆的位置与次序。最终如下图。
(左上是Form1, 右下是Form2)
3.2.3 消息发送
五、鼠标点一下Form2的标题,按F12,打开Unit2.cpp;再按Ctrl+F6,或者点击编辑框下面的
(Unit2.h 文件内添加代码) 六、切换回Unit2.cpp,代码上部加入以下加粗的一行,即引用Unit1.h。因为我们需要Unit2.cpp里用到Form1,而Form1在Unit1.h里定义。
(在Unit2.cpp中,include Unit1.h)
七、在Form2上,双击Button1,在代码编辑窗口输入以下加粗部分代码。
(Button1 的 OnClick代码:发送向上移动10个象素的消息)
八、和第七步类似,分别单击Button2,Button3,Button4,完成以下加粗部分代码的输入:
(完成剩余的三个方向)
现在我们完成了发送消息部分的代码。下面该处理接收消息了。 从上面的代码我们看到,消息是发送到“Form1->Handle”.Form1是新建工程时,默认就创建的那个表单,而Handle,是表单的窗口句柄。因此,消息的接受者,是Form1. 切换到Unit1.cpp,再按Ctrl+F6,切换到Unit1.h。 辅助:C++ Builder 集成环境的基本使用方法。
3.2.4 消息接收方的处理九、在Unit1.h 里。加上对Unit2.h的引用:
十、在VCL里,有专门的方法,来完成消息响应。请在Unit1.h里,点击鼠标右键,在弹出菜单中,选择“View Explorer”,或者直接按Ctrl + Shift + E。出现“类专家”窗口:
(类专家窗口,该窗口出现时,有可能是“嵌套”在代码窗口里)
十一、如上图,选中TForm1。然后点击鼠标右键。在弹出菜单中,选择“New Method...”。我们将为TForm1新建一个成员函数。
(New Method: 为指定类新建一个成员方法) 十二、在出现的对话框中,作出以下红圈标明的改动: /*免费版本不提供*/ (Add Method :添加方法) 十三、点击OK退出后,CB会自在Unit1.cpp里,添加一个函数。请加入以下加粗部分的代码。
(接收,处理消息的代码) 3.2.5 让Form2和Form1一起显示
十四、现在编译并运行程序,你会发现只看到Form1,但找不到Form2. 这是因为只有默认产生的主窗口,也就是Form1,才会自动显示。 所以,我们希望在Form1显示时,也自动显示Form2.请用鼠标单击一下Form1.然后按F11,找到控件属性窗口,并切换到Event页:
(找到Form1的 OnShow 事件) 如上图,找到OnShow,然后用鼠标双击其右的空白。 十五、在出现的代码编辑窗口中,加入下面加粗的一行代码:
3.2.6 运行
十六、到现在还没有保存过代码吗?这可很危险。保存(Ctrl + Shift S)。然后按F9编译并运行。分别按Form2的四个按钮,看Form1的形为是否正确。下面是在我机器上运行的一个截图:
(最终运行截图)
看不去很长的一过程,我们完成了“定义、发送、处理”消息三大步骤。事实上,要完成同样的功能,完全可以不通过自定义消息来处理(辅助:本例相同功能的“非自定义消息”版)。但是通过消息处理,我们可以使程序在设计上变得更灵活,而本章的重点,正是在于建立“消息处理”的概念。 在本例中,按中Form2中的四个按钮,都将向Form1发出CM_MOVEWINDOW这个消息。记住:CM_MOVEWINDOW只是一个整数。程序并不会因为这个宏含有“Move Window”的文字,就自动将Form1上下左右的移!要想完成相关的处理,完全需要靠我们代码来工作。消息带有两个参数,这两个参数也是整数,它们的具体含意,如何使用,同样要依赖于实际代码来处理。
3.3 小结本章只有两节。 第一节我们尝试向一个外部的程序:“画图”,发送一个Windows预定的消息:WM_CLOSE。“画图”程序的主窗口接收到该消息后,会做出预定义的动作:退出。但对于其它程序,可能会做出判断,发现该消息来自外部的程序,而不做处理。 第二节,我们在同一个程序内部的两个不同的窗口之间,发送自定义的消息。由此,我们了解到,原始的Windows消息由三部分组成:消息编号、及两个消息参数。 这就是本章的要点。如果你原来不知道Windows世界里有“消息”这一回事,现在你应该有所了解了。不过,如果你刚才在看课程时,只一个劲地Copy代码,那么现在是你回头再重新看一遍的时候了。 下一章见。 |
||||||||