|
[VC++学习]《精通MFC》——第二章(IV) 读书笔记
|
1 应用程序定义的通知
这种消息是由应用程序而不是在SDK头文件中定一的。应用程序利用这些消息来指示它所拥有的窗口完成特定的任务或同其他应用程序的窗口通信。
应用程序要为自定义消息赋予一个惟一的32位标记。必须保证该标记和已有的系统定一的消息的标志符不冲突,可以有两种方法实现:
确保值在0x0400(WM_USER)和0x7FFF之间。操作系统保留了0x0000到0x03FF(WM_USER - 1)之间的值,应用程序不能使用。
向RegisterWindowMessage传入一个消息字符串,获得一个介于0xC000和0xFFFF之间的系统范围内惟一的消息标志符:
UINT RegisterWindowMessage(
LPCTSTR lpString //消息字符串
);
2 消息参数
系统向窗口过程传递消息时会传递如下四个参数。这四个参数被称为消息参数:
hWnd:表示处理该消息的窗口,该窗口的窗口过程将被调用以处理消息。
Message:消息的惟一标志。
wParam:处理该消息所需要的参数。
lParam:处理该消息的额外参数。
wParam和lParam设定了处理该消息所需要的数据或数据的位置。其含义和值依赖于具体消息。如果消息不需要使用参数,需要将wParam和lParam设为NULL。窗口过程必须检查消息类型并根据消息类型决定如何解释wParam和lParam。
3 消息对列
窗口本身仅提供了处理消息的窗口过程,真正的处理是由创建窗口的线程来进行的。线程对创建的窗口有拥有权。如果一个线程建立一个窗口然后结束,操作系统会自动销毁线程所拥有的全部窗口。建立窗口的线程必须是为窗口处理所有消息的线程。这也意味着每个线程,如果它至少建立了一个窗口,都应由系统对它分配一个容纳待处理的消息的队列。这个队列用于窗口消息的派送。这个队列就是线程的消息队列。为了使窗口接收这些消息,线程必须有它自己的消息循环。
线程通过消息循环,从消息队列中取出消息,并分派到特定的窗口过程处理该消息。在处理过程中可能又会产生其它消息,这些消息被放到消息队列中。如此不断往复,直到应用程序调用PostQuitMessage为止。PostQuitMessage不会在消息队列中投递一个消息,而是在内部设定QS_QUIT唤醒标志并设置THREADINFO结构的nExitCode成员。因为这些操作永远不会失败,所以PostQuitMessage的原型被定义成返回VOID。
除了为每个线程维持一个消息队列外,系统还维持一个全局的消息队列,用于容纳各种硬件输入消息的系统硬件输入队列。当系统初始化时,需建立一个特殊的线程,即原始输入线程(Raw Input Thread, RIT),同时建立系统硬件输入队列(System Hardware Input Queue, SHIQ)。
对鼠标消息,原始输入线程只是确定哪一个窗口在鼠标光标下。利用这个窗口,原始输入线程调用GetWindowThreadProc elssId来确定是哪个线程建立了这个窗口。返回的线程ID指出哪一个线程应该得到这个鼠标消息。
在任何给定时刻,只有一个线程同原始输入线程“连接”。这个线程就是我们前面讨论的前台线程(Foreground Thread),因为它建立了正在与用户交互的窗口。当切换前台窗口时(无论是通过键盘、鼠标还是通过函数调用),原始输入线程将自动切换和当前的前台线程的连接。
一般情况下,最新的消息总是放到队列的末尾,但WM_PAINT消息是惟一的例外。为了优化屏幕的绘制,系统对WM_PAINT消息采取了特殊的处理。首先,只有当消息队列中没有其它消息时,该消息才会被传递给窗口过程进行处理。其次,同一个窗口的多个WM_PAINT消息会被合并为一个WM_PAINT消息,组合时所有的待绘制的无效区域合并为一个区域。
为了将消息投递到线程的消息队列,系统首先要依据MSG结构进行过滤,然后将MSG结构拷贝到消息队列。下面是MSG结构的定义:
Typedef struct tagMSG{
HWMD hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
}MSG;
Time参数表示消息投递的时间,pt参数表示鼠标的位置。
应用程序通过调用GetMessage从消息对列中移出一条消息。如果仅仅是检查消息而不用移出,可以调用PeekMessage,该函数获得当前消息的一份拷贝。
移出一条消息后,应用程序通过调用DispatchMessage将消息分派到适当的窗口过程进行处理。DispatchMessage函数接收的MSG指针所指的数据由前面调用的GetMessage或PeekMessage填充。
在向窗口过程传递消息时,没有传递time 和pt参数。应用程序在处理消息时,可通过调用GetMessageTime和GetMessagePos获得这些消息。
通过调用WaitMessage,线程可以在其消息队列为空时将控制权转交给其他线程。WaitMessage会阻塞当前线程,直到有新的消息进入线程的消息队列。
还可以通过调用SetMessageExtraInfo,为当前线程的线程对列设置附加信息。
LPARAM SetMessageExtraInfo(
LPARAM lParam
);
该函数返回当前线程的消息队列的上次设定的附加消息。调用该消息后,会“冲掉”以前为该线程的消息队列设置的任何附加信息。
通过调用GetMessageExtraInfo,可以获得当前线程的消息队列的附加信息。
4 消息循环
任何GUI线程都拥有一组消息队列,并需要不断从消息队列中取出消息进行处理,处理过程忠又产生新的消息,如此往复构成消息循环。消息循环是Windows应用的典型结构。
一个简单的消息循环如下:
MSG msg;
bool bRet;
while (bRet = GetMessage(&msg, NULL, 0, 0) != 0)
{
if (bRet == -1)
{
//错误处理
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
代码中的GetMessage从消息队列中检索到消息并将它拷贝到MSG结构中。它可能返回-1、0和其它值。-1表示碰到错误;0表示检索到WM_QUIT消息。
一般的情形是在主窗口的窗口过程中响应WM_DESTROY时,调用PostQuitMessage以结束消息循环。
Case WM_DESTROY:
//清理
PostQuitMessage(0);
Break;
线程消息循环中必须包含对TranslateMessage的调用,以接收键盘输入: |
|
|
|