( 之前的一篇教程如果有什么不明白,不要紧,请继续往下走,以后回过头看就自然不会再迷糊! )
Windows 用的是基于事件驱动的编程方法,所以检测并处理消息是很重要的。现在让我们继续研究一下消息队列 (Queue) :
一个更加生动的例子! 系统 和 我 之间隔着一条河,系统在上游,我则处在下游并设置了一个水闸。 系统有什么要通知我就写到瓶子里,扔进河中。 最后这些瓶子在我设的水闸前排起队来了。 (这个例子不错嘛) 我有空的时候就捞起一个瓶子,读出里面的指示,接着就知道下一步要做些什么了。
如果我没有空怎么办? 那么你所处理的消息有可能已经错过了最佳时机。 有一个办法可以让你的消息插队,那就是用 PostMessage() 。
好了,先来动动手,模仿 系统 给我的程序的 消息队列 发送消息。
要用到的函数: 发送消息到队列 - PostThreadMessage() , 检索队列中消息 - PeekMessage() ,还有一个装载消息和相关数据的结构 MSG (也就是装指令的瓶子),具体如下:
typedef struct tagMSG {
HWND hwnd; // 与该条消息相关的窗口句柄,为 0 则没有相关的窗口
UINT message; // 消息的具体数值,相当于没符号的 int (16位),范围是 0 — 65535
WPARAM wParam; // 第一个相关参数,类型同上。
LPARAM lParam; // 第二个相关参数,相当于 long 定义,范围 (略)
DWORD time; // 系统计时,相当于 unsigned long ,用 GetTickCount() 可以取得相同的值
POINT pt; // 鼠标现时的坐标,相当于 ( LONG x; LONG y; ) 坐标
} MSG;
具体的源程序见下面:
// File Name: WinMain.cpp
#define WIN32_LEAN_AND_MEAN // Say No to MFC !!
#include <windows.h>
char Temp[177] = "Hello world"; // 临时字符串变量
char Result[512] = ""; // 用于保存、输出结果
char Title[] = "Sample for Message details __CopyRight - `海风 `";
// Name: WinMain() // 主程序入口
// ------ ---------- ----------- ---------
int WINAPI WinMain( HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow )
{
MSG msg; // 定义了一个装载消息的结构
DWORD CurThreadID = GetCurrentThreadId( ); // 取得当前线程的 ID (标志号)
MessageBox( NULL, " 请按 确定 开始测试!", Title, MB_OK | MB_TOPMOST );
BOOL done = false; // 用来保存 发送消息 是否成功
// 发送消息需用线程的 ID 指示要送到哪个消息队列中去,ID 由 GetCurrentThreadId() 取得
done = PostThreadMessage( CurThreadID, 123 , 1, 22 );
if (! done ) MessageBox( NULL, "发送消息失败_1!", Title, MB_OK | MB_TOPMOST );
done = PostThreadMessage( CurThreadID, 345 , 2, 22 );
if (! done ) MessageBox( NULL, "发送消息失败_2!", Title, MB_OK | MB_TOPMOST );
done = PostThreadMessage( CurThreadID, 4321 , 3, 22 );
if (! done ) MessageBox( NULL, "发送消息失败_3!", Title, MB_OK | MB_TOPMOST );
// 以下这个发送消息的效果相当于 PostQuitMessage( 12 );
done = PostThreadMessage( CurThreadID, WM_QUIT , 12, 0 );
if (! done ) MessageBox( NULL, "发送消息失败_4!", Title, MB_OK | MB_TOPMOST );
PostQuitMessage( 12 ); // 发送退出消息
// 相当于发送了 WM_QUIT 消息,但只有 GetMessage() 会检测它,PeekMessage() 则要外加判断来处理。
// WM_QUIT 的值等于 18 (即0x0012),点击鼠标右键看 "Go To Definition Of WM_QUIT"
long i = 24, k = 0; // i 为循环系数初始值, k 用来纪录接受了多少条消息
while (i) // 循环系数 i 为 0 则退出
{
if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
wsprintf( Temp , "检测到 第 %ld 号 消息 \n "
" 第一参数 wParam = %ld ;"
" 第二参数 lParam = %ld ;\n "
" (系统时间) msg.time = %ld ; "
" (涉及的句柄) msg.hwnd = %ld \n\n",
msg.message, msg.wParam, msg.lParam, msg.time, msg.hwnd );
lstrcat( Result, Temp ); k++; // 追加到结果字符串后面
}
i--; // 循环系数减少 1,为 0 则退出
}
wsprintf( Temp , "共收到 %ld 条消息\n", k ); // 整理报告数字
lstrcat( Result, Temp );
MessageBox( NULL, Result, Title, MB_OK | MB_TOPMOST ); // 显示检测到的消息详细情况
ExitProcess(0);
return NULL;
}
以上这个源程序要注意两个地方:
其一是 PeekMessage() 的最后一个参数 PM_REMOVE ,如果换成 PM_NOREMOVE ,则虽然取得了消息,但仍保留一份在消息队列里面 (MSDN 原文:Messages are not removed from the queue after processing by PeekMessage. ),会有什么效果?你自己换一下参数试一下吧。
其二是 如果使用 PostQuitMessage() 函数,其实际效果相当于用 PostThreadMessage() 向你的线程发送 WM_QUIT 消息,你可以在消息泵里判断 如果 ( msg.message == WM_QUIT ) 为真就退出 消息(处理)循环 (使用 break ) 。
另外,在上面的例子中改用 PM_NOREMOVE 来测试,如果得不到预期的结果,我一点都不觉得奇怪,建议采用下面的方法来显示结果。
while (true) // 清除所有多余消息
{ if(! PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) break; }
MessageBox( NULL, Result, Title, MB_OK | MB_TOPMOST ); // 显示检测到的消息详细情况
下面我们将继续尝试检测真正来自系统给我们的消息!
不过在此之前,我们需要先简单介绍一下句柄,因为一会儿要创建的新窗口就有一个。
句柄是翻译过来的名词 (英文是 Handle ) ,可是它一点都不好帮助理解!
我的一个朋友发表了他的看法 " 句柄就好像一个苹果的小柄,如果你要吃苹果... " 另一个朋友打断了他 " 什么嘛,应该是这样!你做了亏心事,让我拿住了把柄,嘿嘿嘿,我要你做什么你都要听我的... "
对! 我看就是这么回事,比方说创建了一个窗口, 你要知道它的状态,大小,位置等,你要有句柄; 你要改变它的属性,改变它的式样,你也要有句柄才行; 就算你要毁了它,一样要有它的句柄才成事。 既然句柄那么重要,那么什么时候可以得到句柄呢?
一般来说,当你使用 CreateWindowEx() 函数创建一个新窗口的时候,函数会返回一个,记得要保存它!
( 一个提示: 句柄并不是只是窗口才有的,许多 windows 里面的对象都用句柄来访问, 比如你向系统申请内存, 就要有一个堆的句柄 (handle of heap) 等等,与句柄相关的内容以后还会提到。 )
接着我们本篇的主题 - 检测来自系统的消息 。
( 以下例子使用 CreateWiondowEx() 函数创建一个新窗口,关于该函数具体用法以后会作详细说明 )
在开始之前,我们要先大概有一个印象,系统会发什么事件给我们? 什么时候会发给我们?
先回答后面一个问题,一般来说,当我们的程序获得输入焦点,也就是程序的窗口处于最上面,且标题变成蓝色底色,系统会把输入信息发给我们的程序来处理。
会有些什么样的输入信息呢?大概是这些,比方说,我们按动了键盘,当我们按下键盘上一个键,系统会向 程序(当前输入焦点) 发送一个 WM_KEYDOWN 的信息,(WM_KEYDOWN == 256),而我们松开键的时候系统会发出 WM_KEYUP (WM_KEYUP == 257,通过查看16位的 msg.wParam 可以区分按下的是哪一个键 ),类似的还有 WM_MOUSEMOVE 和 WM_LBUTTONDOWN 等与鼠标输入有关的信息。
在下面的例子中,在按下 确定 继续以后,你有三秒时间令系统向你的程序发送输入信息!
// File Name: WinMain.cpp
#define WIN32_LEAN_AND_MEAN // Say No to MFC !!
#include <windows.h>
char Temp[177] = "Hello world";
char Result[1024] = ""; // 用于保存、输出结果
char Title[] = "Sample for Message details __CopyRight - `海风 ...............";
// Name: WinMain() // 主程序入口
// ------ ---------- ----------- ---------
int WINAPI WinMain( HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow )
{
MSG msg; // 定义了一个装载消息的结构
//*
// 下面用 CreateWindowEx() 函数创建一个临时窗口 , 使用 hWnd 暂时保存句柄
HWND hWnd=CreateWindowEx( WS_EX_TOPMOST | WS_EX_TOOLWINDOW ,
"Edit","1231",
WS_OVERLAPPEDWINDOW,
120,120,
280,180,
NULL,NULL,hInstance,NULL);
ShowWindow( hWnd, SW_SHOW ); // 让这个窗口可以被看见
MessageBox( NULL, "创建了测试窗口,按 确定 继续测试!", Title, MB_OK | MB_TOPMOST );
// */
Sleep(3000); // 利用这段时间在新窗口上触发一些如 鼠标移动,按下键钮 等事件
// 先发送三条自定义消息
BOOL done = false; // 用来保存 发送 是否成功
// 当前线程的 ID 由 GetCurrentThreadId() 取得
done = PostThreadMessage( GetCurrentThreadId(), 123 , 1, 22 );
if (! done ) MessageBox( NULL, "发送消息失败_1!", Title, MB_OK | MB_TOPMOST );
// PostMessage() 发送的是与窗口相关的消息
done = PostMessage( hWnd, WM_KEYDOWN , 55, 4321 ); // 所发送的是假的 WM_KEYDOWN 消息
if (! done ) MessageBox( NULL, "发送消息失败_2!", Title, MB_OK | MB_TOPMOST );
PostQuitMessage( true ); // 发送退出消息
// 只有 GetMessage() 可以检测它,PeekMessage() 要外加判断
// 以下开始检测系统消息
long i = 24, k = 0; // i 为循环系数初始值, k 用来纪录接受了多少条消息
while (i) // 循环系数 i 为 0 则退出
{
if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
wsprintf( Temp , "检测到 第 %ld 号 消息 \n "
" 第一参数 wParam = %ld ;"
" 第二参数 lParam = %ld ;\n "
" (系统时间) msg.time = %ld ;"
" (涉及的句柄) msg.hwnd = %ld \n\n",
msg.message, msg.wParam, msg.lParam, msg.time, msg.hwnd );
lstrcat( Result, Temp ); k++; // 追加到结果字符串后面
}
i--; // 循环系数减少 1,为 0 则退出
}
wsprintf( Temp , "共收到 %ld 条消息\n", k ); // 整理报告数字
lstrcat( Result, Temp );
DestroyWindow( hWnd ); // 退出程序前要销毁窗口
MessageBox( NULL, Result, Title, MB_OK | MB_TOPMOST ); // 显示截获消息的详细内容
// 请细心的读者比较一下消息的系统时间,是不是有插队的情况发生呢?
ExitProcess(0);
return NULL;
}
在以上实验中,如果曾按下键钮,应该会收到 WM_KEYDOWN (第 256 号消息),和 WM_KEYUP (第 257 号消息),它们会成对的出现。
不过,出现了 第 280 号 消息( msg.message == 0x0118 ),我搞不懂那是什么消息来着。
…… `海风 2002年10月17日 am 2:01
——————————————————————————
学习的秘诀是要知道 Why ,而不仅仅是 How ...
目前喜欢的歌: 動力火車 - 忠孝东路走九遍