主题:《白手起家Win32SDK应用程序》第7篇 获取消息及对消息缺省处理
[center]第7篇 获取消息及对消息缺省处理[/center]
[center]白云小飞[/center]
[b]一 重提上篇的问题:[/b]
还记得在上一篇的最后,经过调试后我们发现的问题吗?
1. 窗口不能自动被激活(即成为当前窗口)。只有最小化其它应用程序的窗口后,才能看到我们的这个窗口。(注意:在ShowWindow(hWnd, ncmdshow);函数中ncmdshow值我说过是SW_SHOWNORMAL值,应该会将窗口激活才对啊!)
2. 无法进行调整窗口大小,移动窗口位置等等的所有对窗口的操作。
你认为是什么原因呢?
[b]二 且听我说:[/b]
1. ShowWindow(hWnd, SW_SHOWNORMAL)函数作用虽然是显示窗口并激活窗口。它确实完成了窗口的显示,但是,它并没有去激活窗口,只是发了一系列与激活窗口有关的消息,这才是ShowWindow(…)函数所做的事。(那又由谁来激活呢?)
2. 真正激活窗口的任务是由DefWindowProc( )完成的。哦,也就是说我们得让DefWindowProc( )收到ShowWindow( )产生的消息并根据消息来执行相应任务。
3. 但ShowWindow( )产生的消息只是加入到该线程的消息队列中,并没有自动地去调用WinProc( )的回调函数对消息处理。因此,DefWindowProc( )也就没被调用,自然地就没法激活窗口了。(请边看代码边分析我的这段话吧!呵呵,你得有点想像力了!)
4. 哦,这不由地让我想起前面的创建窗口函数CreateWindowEx( ) 。这两个函数在执行中都会产生若干的消息。但CreateWindowEx会自动地调用了WinProc( ) 函数,而ShowWindow( )没有这样。
5. 同理,我们在窗口中的各种操作(调整大小、移动窗口等等),虽然会产生各种消息,但操作系统也只是把它们排入我们线程的消息队列中,并不自动去调用 Proc,所以同样的理由,DefWindowProc也没有去处理我们的消息了,窗口也就没法完成如调整大小,移动等等的操作了(你要知道:这些操作的实现都是由DefWindowProc( )完成的)。(还有,由于消息因为没有得到处理,就会一直留在消息队列中,这样,你的线程消息队列中的消息将会越积越多噢!)
看来,上述的两个问题都源于同一个原因:那就是对窗口操作产生的消息只是排入消息队列中,操作系统并没有自动地调用WinProc回调函数来处理我们的窗口消息。
哦!那我们只有自力更生了。
我猜你一定迫切想知道如何解决吧!
[b]三 介绍三个函数给你认识:[/b]
我们到了一个Window窗口程序框架最关键的部分了——消息处理机制。
消息队列中的本窗口大量消息并不会被自动取出,也没有自动地调用WinProc函数对消息加以处理,但是,Window系统提供了三个API函数给我们,让我们自己去完成这件事。看吧!
GetMessage( …);
TranslateMessage(…);
DispatchMessage(…);
下面就让我分别对这三个函数解释解释。
[b]1 GetMessage( …)[/b]
原型如下:
BOOL GetMessage(
LPMSG lpMsg,
HWND hWnd ,
UINT wMsgFilterMin,
UINT wMsgFilterMax);
[b]功能:[/b]这个API函数用来从消息队列中“摘取”一个消息信息放到lpMsg所指的变量里。(注:如果所取窗口的消息队列中没有消息,则程序会暂停在GetMessage(…) 函数里,不会返回。)
[b]参数及返回值:[/b]
[b]LPMSG lpMsg:[/b]是传出参数。消息结构MSG的指针。如果该函数执行成功,则从消息队列中“摘”取的一个消息信息会放入lpMsg所指的MSG结构变量中。
在Winuser.h中有定义如下:
typedef struct tagMSG {
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
} MSG, *PMSG, NEAR *NPMSG, FAR *LPMSG;
其中的成员变量message里才是我所说的WM_SIZE、WM_COMMAND、WM_QUIT等等消息标识。
hwnd中是这个消息所在的窗口句柄。
好了,其它成员变量我就暂时省略不说。
[b]HWND hWnd:[/b]传入参数。你要获取你程序中哪个窗口的消息,那就把相应的窗口句柄代入其中。
[b]UINT wMsgFilterMin,UINT wMsgFilterMax:[/b]这两个参数我就不介绍。你只要用0值代入就可了。
[b]返回值:[/b]如果取的是WM_QUIT消息,则返回值为0,如果取的是其它消息,返回值为非0值。WM_QUIT消息是退出程序的消息。当我们想让程序退出时,我们就可以发一个WM_QUIT消息,让GetMessage返回0值。
[b]2 TranslateMessage(…)[/b]
[b]原型:[/b]BOOL TranslateMessage( CONST MSG *lpMsg);
[b]功能:[/b]对GetMessage取得的MSG消息结构中的信息进行必要的预处理(你可不用管它做了什么。)。
[b]参数:[/b]
[b]CONST MSG *lpMsg:[/b]它是用来对你取的消息结构MSG变量进行必要的预处理。GetMessage函数取得的消息,要经过TranslateMessage处理一下,然后才可以传给DispatchMessage函数,因此,TranslateMessage必放在GetMessage与DispatchMessage之间。
[b]返回值:[/b]它虽然有一个返回值,我们总是忽略它,所以我也就不说了。
[b]3 DispatchMessage(…)[/b]
[b]原型:[/b] LONG DispatchMessage( CONST MSG *lpMsg);
[b]功能:[/b]用来完成调用WinPro回调函数并把由GetMessage取得的消息结构MSG变量中的信息传递给WinPro回调函数:(原型如下)
[b]参数及返回值:[/b]
[b]MSG *lpMsg :[/b]传入参数,MSG消息结构体类型指针,指向你已经取出的消息结构体变量。
[b]返回值:[/b]同上,我们总是忽略它。
[b]四 Come on,行动起来吧![/b]
看我添加的代码(粗体部分):
LRESULT CALLBACK WinProc(HWND hwnd,
UINT msg,
WPARAM wparam,
LPARAM lparam)
{
//这里可以添加你的消息处理代码
return DefWindowProc(hwnd, msg, wparam, lparam);
}
int WINAPI WinMain(HINSTANCE hinstance,
HINSTANCE hprevinstance,
LPSTR lpcmdline,
int ncmdshow)
{
HWND hWnd;
MSG msg;
WNDCLASSEX wndclass;
//…… 这里省略了前面所述的注册窗口类的过程
//
hWnd=CreateWindowEx(NULL,WND_CLS_NAME,
"这是我的第一个窗口",
WS_OVERLAPPEDWINDOW|WS_VISIBLE ,
CW_USEDEFAULT, 0,
400,400,
NULL,
NULL,
hinstance,
NULL );
if (!hWnd)
return 0;
ShowWindow(hWnd, ncmdshow);
[b] while(GetMessage(&msg, NULL, 0, 0)) [/b]//获取一个消息,成功后会放在msg中。
[b]{ [/b]
[b]TranslateMessage(&msg); [/b]//消息进行必要的处理转换。
[b] DispatchMessage(&msg); [/b]//调用WinProc,将msg的各项信息传递给WinProc
[b]} [/b]
return 0;
}
看清楚了:这是一个消息循环。只有当GetMessage(…)收到一个WM_QUIT时返回0值,程序才会退出。
[b]五 程序流程分析(粗体部分)[/b]
1. 当用户进行如调整窗口大小,移动窗口位置等等的操作时。会产生不同的消息。(当然包括了ShowWindow( )函数产生的若干个消息。
2. Window系统会将这些消息排入本线程的消息队列中。(在操作系统中完成)
3. 只要该窗口消息队列中有消息,我们的GetMessage(&msg,NULL,0,0)就会从消息队列中摘取一个消息的信息,填入msg结构体变量中,如果不是WM_QUIT则返回非零值,就执行循环体。(注意:如果消息队列中没有任何消息可取时,则程序会停在GetMessage函数里,直到消息队列中有了消息,GetMessage函数才会取一个消息信息并返回。)
4. 用TranslateMessage(&msg)对msg中的数据进行预处理(你先不必知道它具体做了什么,但不要忘记这个函数。)。
5. 然后是调用DespatchMessage(&msg),这个函数里会调用WinProc,并将msg中的数据通过WinProc的参数传递给WinProc;
6. 程序转入执行WinProc回调函数体内的代码。
7. 看代码处,WinProc此时只有一句 return DefWindowProc(hwnd, msg, wparam, lparam);这里,我们只是将WinProc传入的参数原样地传给了API函数DefWindowProc。所有的消息都让DefWindowProc进行缺省默认的处理。(你不用理会DefWindowProc都做了些什么。)
8. DefWindowProc完成一个消息处理后,返回消息处理的结果。
9. 我们的WinProc也原样地将DefWindowProc返回值返回。
10. WinProc执行完成后,程序又返回到DispatchMessage(&msg)函数体内。(因为是在DispathMessage( )中调用WinProc的。)
11. 退出DispatchMessage(&msg);函数后程序又执行下一个循环。即
while(GetMessage(&msg, NULL, 0, 0))
又取下一个消息,进行下一个消息的处理。
12. 直到GetMessage “摘取”到了退出程序的消息WM_QUIT,返回零值,退出循环,结束程序。
(哦,整个流程是通过我们的程序与Window系统相互协作来完成的。你可要多加理解罗!)
[b]六 调试这个程序[/b]
不设任何断点,按F5运行程序,
看来一切正常。可以移动窗口;可以调整窗口大小;可以最大化最小化;总之可以进行窗口的基本的操作了。(这些动作都是由DefWindowProc来完成的噢!)
哈!我们可是渐入佳境啦!这就是Window系统消息处理机制带给我们的成果。
不过……只是……有一个问题。
你有没有注意到,点击窗口右上角的关闭按钮时,窗口是关闭了,但程序并没有退出(看来点击关闭按钮时并没有产生WM_QUIT的消息。)。
你只能点击VC菜单的:Debug->Stop Debugging来退出程序了。
(欲知此为如何,请听下回分解!)
[center]白云小飞[/center]
[b]一 重提上篇的问题:[/b]
还记得在上一篇的最后,经过调试后我们发现的问题吗?
1. 窗口不能自动被激活(即成为当前窗口)。只有最小化其它应用程序的窗口后,才能看到我们的这个窗口。(注意:在ShowWindow(hWnd, ncmdshow);函数中ncmdshow值我说过是SW_SHOWNORMAL值,应该会将窗口激活才对啊!)
2. 无法进行调整窗口大小,移动窗口位置等等的所有对窗口的操作。
你认为是什么原因呢?
[b]二 且听我说:[/b]
1. ShowWindow(hWnd, SW_SHOWNORMAL)函数作用虽然是显示窗口并激活窗口。它确实完成了窗口的显示,但是,它并没有去激活窗口,只是发了一系列与激活窗口有关的消息,这才是ShowWindow(…)函数所做的事。(那又由谁来激活呢?)
2. 真正激活窗口的任务是由DefWindowProc( )完成的。哦,也就是说我们得让DefWindowProc( )收到ShowWindow( )产生的消息并根据消息来执行相应任务。
3. 但ShowWindow( )产生的消息只是加入到该线程的消息队列中,并没有自动地去调用WinProc( )的回调函数对消息处理。因此,DefWindowProc( )也就没被调用,自然地就没法激活窗口了。(请边看代码边分析我的这段话吧!呵呵,你得有点想像力了!)
4. 哦,这不由地让我想起前面的创建窗口函数CreateWindowEx( ) 。这两个函数在执行中都会产生若干的消息。但CreateWindowEx会自动地调用了WinProc( ) 函数,而ShowWindow( )没有这样。
5. 同理,我们在窗口中的各种操作(调整大小、移动窗口等等),虽然会产生各种消息,但操作系统也只是把它们排入我们线程的消息队列中,并不自动去调用 Proc,所以同样的理由,DefWindowProc也没有去处理我们的消息了,窗口也就没法完成如调整大小,移动等等的操作了(你要知道:这些操作的实现都是由DefWindowProc( )完成的)。(还有,由于消息因为没有得到处理,就会一直留在消息队列中,这样,你的线程消息队列中的消息将会越积越多噢!)
看来,上述的两个问题都源于同一个原因:那就是对窗口操作产生的消息只是排入消息队列中,操作系统并没有自动地调用WinProc回调函数来处理我们的窗口消息。
哦!那我们只有自力更生了。
我猜你一定迫切想知道如何解决吧!
[b]三 介绍三个函数给你认识:[/b]
我们到了一个Window窗口程序框架最关键的部分了——消息处理机制。
消息队列中的本窗口大量消息并不会被自动取出,也没有自动地调用WinProc函数对消息加以处理,但是,Window系统提供了三个API函数给我们,让我们自己去完成这件事。看吧!
GetMessage( …);
TranslateMessage(…);
DispatchMessage(…);
下面就让我分别对这三个函数解释解释。
[b]1 GetMessage( …)[/b]
原型如下:
BOOL GetMessage(
LPMSG lpMsg,
HWND hWnd ,
UINT wMsgFilterMin,
UINT wMsgFilterMax);
[b]功能:[/b]这个API函数用来从消息队列中“摘取”一个消息信息放到lpMsg所指的变量里。(注:如果所取窗口的消息队列中没有消息,则程序会暂停在GetMessage(…) 函数里,不会返回。)
[b]参数及返回值:[/b]
[b]LPMSG lpMsg:[/b]是传出参数。消息结构MSG的指针。如果该函数执行成功,则从消息队列中“摘”取的一个消息信息会放入lpMsg所指的MSG结构变量中。
在Winuser.h中有定义如下:
typedef struct tagMSG {
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
} MSG, *PMSG, NEAR *NPMSG, FAR *LPMSG;
其中的成员变量message里才是我所说的WM_SIZE、WM_COMMAND、WM_QUIT等等消息标识。
hwnd中是这个消息所在的窗口句柄。
好了,其它成员变量我就暂时省略不说。
[b]HWND hWnd:[/b]传入参数。你要获取你程序中哪个窗口的消息,那就把相应的窗口句柄代入其中。
[b]UINT wMsgFilterMin,UINT wMsgFilterMax:[/b]这两个参数我就不介绍。你只要用0值代入就可了。
[b]返回值:[/b]如果取的是WM_QUIT消息,则返回值为0,如果取的是其它消息,返回值为非0值。WM_QUIT消息是退出程序的消息。当我们想让程序退出时,我们就可以发一个WM_QUIT消息,让GetMessage返回0值。
[b]2 TranslateMessage(…)[/b]
[b]原型:[/b]BOOL TranslateMessage( CONST MSG *lpMsg);
[b]功能:[/b]对GetMessage取得的MSG消息结构中的信息进行必要的预处理(你可不用管它做了什么。)。
[b]参数:[/b]
[b]CONST MSG *lpMsg:[/b]它是用来对你取的消息结构MSG变量进行必要的预处理。GetMessage函数取得的消息,要经过TranslateMessage处理一下,然后才可以传给DispatchMessage函数,因此,TranslateMessage必放在GetMessage与DispatchMessage之间。
[b]返回值:[/b]它虽然有一个返回值,我们总是忽略它,所以我也就不说了。
[b]3 DispatchMessage(…)[/b]
[b]原型:[/b] LONG DispatchMessage( CONST MSG *lpMsg);
[b]功能:[/b]用来完成调用WinPro回调函数并把由GetMessage取得的消息结构MSG变量中的信息传递给WinPro回调函数:(原型如下)
[b]参数及返回值:[/b]
[b]MSG *lpMsg :[/b]传入参数,MSG消息结构体类型指针,指向你已经取出的消息结构体变量。
[b]返回值:[/b]同上,我们总是忽略它。
[b]四 Come on,行动起来吧![/b]
看我添加的代码(粗体部分):
LRESULT CALLBACK WinProc(HWND hwnd,
UINT msg,
WPARAM wparam,
LPARAM lparam)
{
//这里可以添加你的消息处理代码
return DefWindowProc(hwnd, msg, wparam, lparam);
}
int WINAPI WinMain(HINSTANCE hinstance,
HINSTANCE hprevinstance,
LPSTR lpcmdline,
int ncmdshow)
{
HWND hWnd;
MSG msg;
WNDCLASSEX wndclass;
//…… 这里省略了前面所述的注册窗口类的过程
//
hWnd=CreateWindowEx(NULL,WND_CLS_NAME,
"这是我的第一个窗口",
WS_OVERLAPPEDWINDOW|WS_VISIBLE ,
CW_USEDEFAULT, 0,
400,400,
NULL,
NULL,
hinstance,
NULL );
if (!hWnd)
return 0;
ShowWindow(hWnd, ncmdshow);
[b] while(GetMessage(&msg, NULL, 0, 0)) [/b]//获取一个消息,成功后会放在msg中。
[b]{ [/b]
[b]TranslateMessage(&msg); [/b]//消息进行必要的处理转换。
[b] DispatchMessage(&msg); [/b]//调用WinProc,将msg的各项信息传递给WinProc
[b]} [/b]
return 0;
}
看清楚了:这是一个消息循环。只有当GetMessage(…)收到一个WM_QUIT时返回0值,程序才会退出。
[b]五 程序流程分析(粗体部分)[/b]
1. 当用户进行如调整窗口大小,移动窗口位置等等的操作时。会产生不同的消息。(当然包括了ShowWindow( )函数产生的若干个消息。
2. Window系统会将这些消息排入本线程的消息队列中。(在操作系统中完成)
3. 只要该窗口消息队列中有消息,我们的GetMessage(&msg,NULL,0,0)就会从消息队列中摘取一个消息的信息,填入msg结构体变量中,如果不是WM_QUIT则返回非零值,就执行循环体。(注意:如果消息队列中没有任何消息可取时,则程序会停在GetMessage函数里,直到消息队列中有了消息,GetMessage函数才会取一个消息信息并返回。)
4. 用TranslateMessage(&msg)对msg中的数据进行预处理(你先不必知道它具体做了什么,但不要忘记这个函数。)。
5. 然后是调用DespatchMessage(&msg),这个函数里会调用WinProc,并将msg中的数据通过WinProc的参数传递给WinProc;
6. 程序转入执行WinProc回调函数体内的代码。
7. 看代码处,WinProc此时只有一句 return DefWindowProc(hwnd, msg, wparam, lparam);这里,我们只是将WinProc传入的参数原样地传给了API函数DefWindowProc。所有的消息都让DefWindowProc进行缺省默认的处理。(你不用理会DefWindowProc都做了些什么。)
8. DefWindowProc完成一个消息处理后,返回消息处理的结果。
9. 我们的WinProc也原样地将DefWindowProc返回值返回。
10. WinProc执行完成后,程序又返回到DispatchMessage(&msg)函数体内。(因为是在DispathMessage( )中调用WinProc的。)
11. 退出DispatchMessage(&msg);函数后程序又执行下一个循环。即
while(GetMessage(&msg, NULL, 0, 0))
又取下一个消息,进行下一个消息的处理。
12. 直到GetMessage “摘取”到了退出程序的消息WM_QUIT,返回零值,退出循环,结束程序。
(哦,整个流程是通过我们的程序与Window系统相互协作来完成的。你可要多加理解罗!)
[b]六 调试这个程序[/b]
不设任何断点,按F5运行程序,
看来一切正常。可以移动窗口;可以调整窗口大小;可以最大化最小化;总之可以进行窗口的基本的操作了。(这些动作都是由DefWindowProc来完成的噢!)
哈!我们可是渐入佳境啦!这就是Window系统消息处理机制带给我们的成果。
不过……只是……有一个问题。
你有没有注意到,点击窗口右上角的关闭按钮时,窗口是关闭了,但程序并没有退出(看来点击关闭按钮时并没有产生WM_QUIT的消息。)。
你只能点击VC菜单的:Debug->Stop Debugging来退出程序了。
(欲知此为如何,请听下回分解!)