理解消息循环
在写任何有实际价值的程序之前,都要理解整个消息循环和windows程序传递消息的结构.当前为止我们已经试了一些消息的处理,我还应该更进一步的看一下这个过程,
因为你如果不理解它们的话,后面的內容将会对你很难.
什么是消息?
一个消息就是一个整数.如果你到头文件中看一下(了解API的好地方)你就会发现这样的內容:
#define WM_INITDIALOG 0x0110
#define WM_COMMAND 0x0111
#define WM_LBUTTONDOWN 0x0201
...
等等..消息用来进行windows系统中的所有通信,至少在基本方面是这样的.如果你想要你的窗口或是控件(其实就一种特別的窗口)做点什么你就给它发个消息.
如果另外一个窗口要你做点什么它也发给你一个消息..如果发生了用戶按动了键盘,移动了鼠标,点击了一个按钮之类的事件,系统就向相关的窗口发送消息.你要是就是那个被传至消息的窗口,你就要处理这些消息.
每个windows消息可能拥有至多两个参数,wParam和lParam.最初wParam是16bit,lParam是32bit,但在Win32平台下两者都是32bit.不是每个消息使用了这些参数,而且每个消息以不同的方式来使用.比如,WM_CLOSE不使用它们任一个,所以你应该忽略它们.WM_COMMAND消息两个都使用,wParam有两个部分,HIWORD(wParam)中含有提示消息(如果有的话),LOWORD(wParam)含有发送消息的控件或菜单的标识号.lParam含有发送消息的控件的HWND(窗口的句柄)或者为NULL,当消息不是由控件发送.
HIWORD()和LOWORD()是windows定义的两个宏,用来从一个32bit值中分离出高字(0xffff0000)和低字(0x0000ffff)的两字节.在Win32中,一个WORD是16bit,所以一个DWORD为32bit.
可以用PostMessage()或SendMessage()来发送消息.PostMessage()把消息放入消息队列再立即返回.就是说你调用了PostMessage()后消息可能被处理了,也可能还沒有被处理.
SendMessage()则真接把消息送往窗口并且在窗口沒有结束处理消息之前不返回.如果我们想关闭一个窗口我们可以发送一个WM_CLOSE消息:PostMessage(hwnd,WM_CLOSE,0,0);这跟我们点击窗口顶部的
按钮一样的效果.注意wParam和lParam都为0.这是因为我们刚才说了WM_CLOSE并不用它们.
对话框
一旦你开始使用对话框,你将需要向此控件发送消息以与它们通信.你可以先使用GetDlgItem()来用ID得到控件的句柄然后用SendMessage(),也可以直接用SendDlgItemMessage()这个一步到位的函数.你给它一个窗口的句柄和一个子窗口的ID,它就会得到字窗口的句柄并向它发送消息.SendDlgItemMessage()和类似的API,如GetDlgItemText()可以用在所有的窗口上面,而不是仅仅在对话框上.
什么是消息队列
打个比方,你正在处理WM_PAINT消息,此时突然用戶在键盘上敲了一大堆的东西.这时候会怎样?你应该中止你的工作去响应键盘的输入,还是简单地把这些输入给忽略掉? 错!显然两种方式都不能接受,所以我们引入了消息队列的概念,消息被发送时就被放入队列,被处理后就从队列中删除.这就保证了你不会丟掉消息,你在处理某个的时候,另外的就在队列中等待你来取走它们.
什么是消息循环
while(GetMessage(&Msg, NULL, 0, 0) > 0)
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
-
消息循环调用了GetMessage(), 它到你的消息队列中去看.如果队列是空的你的程序就在那里等(阻塞在那里).
-
当一个事件发生导致一个消息加到队列中去(比如系统注冊了一次鼠标点击)GetMessage()就返回一个正值表示有一个消息待处理,并且它将我们传递的MSG结构体填充.如果遇到了WM_QUIT它就返回0,如果有错误发生就返回负值.
-
我们拿到信号(在Msg变量中)并传给TranslateMessage(),它进行一些额外的处理,将虛键值转为字符信息.这一步其实是可有可无的,但是有些地方会依赖这个步骤.
-
一旦上面的工作完成了我们把消息传给DispatchMessage().DispatchMessage()先看看信号是给哪个窗口的,再找到那个窗口的窗口过程.
再调用那个过程,参数为窗口的句柄,消息,wParam和lParam.
-
在你的窗口过程中,你得到了那些参数,就可以为所欲为了.如果你沒有处理某些特定的消息,那你就调用DefWindowProc()来为你做一些默认的操作(一般就是什么都不做).
-
一旦你完成了对消息的处理,你的窗口过程就返回了,DispatchMessage()返回了,我们就再次循环.
这是windows程序中一个非常重要的概念..你的窗口过程并不是由系统来神奇地调用的,实际上你在DispatchMessage()中自己间接地调用了它.如果你愿意,你可以对消息调用GetWindowLong()得到它的窗口过程再直接调用它!
while(GetMessage(&Msg, NULL, 0, 0) > 0)
{
WNDPROC fWndProc = (WNDPROC)GetWindowLong(Msg.hwnd, GWL_WNDPROC);
fWndProc(Msg.hwnd, Msg.message, Msg.wParam, Msg.lParam);
}
我对前面的例子用了这种方法,可以工作,但是有很多的方面这种方法沒有注意到,比如Unicode/ANSI转換,时钟回调函数等等,它很可能在我们的简单试验中可以工作.所以试下就可以了,不要在实际的代码中用它:)
注意我们用GetWindowLong()来从窗口来查找它的窗口过程.为什么我们不直接调用WndProc()?因为我们的消息循环为我们程序中的所有窗口的消息服务,包括按钮,列表框这样拥有自己的窗口过程的窗口,所以我们要我们调用了正确的窗口过程.因为多个窗口可能使用一个窗口过程,
第一个参数(窗口的句柄)告诉窗口过程某个消息是为那个窗口的.
你可以看到,你的应用程序的主要时间就是在消息循环这里打转,你很高兴地向那些快乐的窗口发消息让他们处理.如果你要退出程序你怎么办?因为我们使用了一个while循环,如果GetMessage()返回了FLASE(就是0),循环就结束我们就可以到WinMain()的结束点.这就是PostQuitMessage()做的工作.它向队列发送一个WM_QUIT消息,GetMessage()就向Msg结构体填充数据并返回0,而不返回正的值.
这个地方,Msg的wParam成员含有你传向PostQuitMessage()的数据,你可以忽略它,也可以从WinMain()返回当这个进程的结束码.
要点:GetMessage()遇到错误后返回-1.你要记住这点,说不定哪次你在这里会犯错...即使GetMessage()被定义为返回一个BOOL值,它还是会返回TRUE和FLASE之外的值,因为BOOL被定义为UINT(unsigned
int).下面的代码可能看起来可工作,但是不能正确处理某些情況:
while(GetMessage(&Msg, NULL, 0, 0))
while(GetMessage(&Msg, NULL, 0, 0) != 0)
while(GetMessage(&Msg, NULL, 0, 0) == TRUE)
上面的写法都是错误的!可能你注意到我在这个教程中使用了第一个,刚刚提到了,如果GetMessage()不失败,并不会出错,你代码要是正确的话是不会出错.但是你要是在读本教程的话我并不能以此做为前提,你的代码可能有很多错误,GetMessage()会在某些点出错:)这种写法我已经更正了,如果我漏了某些地方请原谅.
while(GetMessage(&Msg, NULL, 0, 0) > 0)
应该始终使用这段拥有相同的效果的代码.
我希望你对windows消息循环有了进一步的了解,如果沒有,不用怕,你使用它们一些时间后就会更清楚了.
|