第二章 一个最简单窗口程序的转型
我知道,可能会有很多朋友对上一章的“Hello, World!”ATL版不以为然,因为它并不能算是什么ATL程序——毕竟它只不过是有了个CComModule而已。不过不管怎样我还是要说,它几乎仍然拥有了一个ATL GUI程序的所有组成部分:入口、初始化、程序体、卸载……
“等等!”也许你会突然打断我,“——还有注册窗口类、消息循环呢?”
当然,对于一个完整的GUI程序来讲,这也是必要的。
貌似废话
不清楚你是否已经为本章的内容做好了准备,因为下面我们就要动真格的了。不过考虑到本书的读者群中可能会存在着相当一部分了解MFC却对Win32 GUI的基本原理和流程不甚熟悉的朋友,所以李马特别为你们准备了这一节的内容。SDK的粉丝们可以跳过这一节,如果你们觉得李马讲的有些拖沓冗长的话。
那么,我还是先以一个标准的Win32 SDK程序开始:
////////////////////////////////////////////////////////////////////////// // ATL的GUI程序设计配套源代码 // 第二章 一个最简单窗口程序的转型 // 工程名称:HelloSDK // 作者:李马 // http://www.titilima.cn //////////////////////////////////////////////////////////////////////////
#include <windows.h> #include <tchar.h>
LRESULT CALLBACK HelloWndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) { switch ( uMsg ) { case WM_DESTROY: { PostQuitMessage( 0 ); } break; case WM_PAINT: { HDC hdc; PAINTSTRUCT ps;
hdc = BeginPaint( hWnd, &ps ); DrawText( hdc, _T("Hello, SDK!"), -1, &ps.rcPaint, DT_CENTER | DT_VCENTER | DT_SINGLELINE ); EndPaint( hWnd, &ps ); } break; default: return DefWindowProc( hWnd, uMsg, wParam, lParam ); } return 0; }
BOOL InitApplication( HINSTANCE hInstance ) { WNDCLASS wc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hbrBackground = (HBRUSH)GetStockObject( WHITE_BRUSH ); wc.hCursor = LoadCursor( NULL, IDC_ARROW ); wc.hIcon = LoadIcon( NULL, IDI_APPLICATION ); wc.hInstance = hInstance; wc.lpfnWndProc = HelloWndProc; wc.lpszClassName = _T("HelloSDK"); wc.lpszMenuName = NULL; wc.style = CS_HREDRAW | CS_VREDRAW;
return RegisterClass( &wc ); }
int WINAPI _tWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nShowCmd ) { // 注册窗口类 InitApplication( hInstance );
// 创建窗口 HWND hWnd = CreateWindow( _T("HelloSDK"), _T("Hello SDK"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL ); ShowWindow( hWnd, nShowCmd ); UpdateWindow( hWnd );
// 消息循环 MSG msg; while ( GetMessage( &msg, NULL, 0, 0 ) ) { TranslateMessage( &msg ); DispatchMessage( &msg ); }
return msg.wParam; } |
不知道你是否会觉得这段代码有些冗长?事实上,这个程序已经体现了Win32 GUI程序运行的所有流程(请注意,我并不会对这些代码进行详细的解释,因为我已经假设你已经了解了这些代码具体行为的必要细节。如果不是这样的话,请参考相关的书籍或者MSDN):
- 注册窗口类的部分。在这个程序中,InitApplication函数完成了这一工作。窗口类的概念类似于OO(面向对象)中的类,所有你在Windows中能看到的窗口都是某个特定窗口类的一份实例。但是,窗口类并非任何一种OOP语言中的类——它所包括的并不是通称的属性和方法(在C++中称作成员变量和成员函数),而是属性和响应。这个区别可能会使你感到费解,我会在下一章中为你详细介绍——因为ATL中对窗口的封装类将这一点体现得十分淋漓尽致。
- 创建窗口的部分。在通常的SDK代码里,这些代码被封装在一个名为InitInstance的函数中。这段代码所做的工作一般是创建窗口并将其显示出来。
- 消息循环。Windows是一个基于消息机制的操作系统,各个窗口之间的通信也主要是靠Windows消息来完成的。而程序中的消息循环也就是将本程序UI线程中的消息队列中提取各种消息,进行处理(如果有必要的话)之后分发给各个消息的属主窗口(或者说是目标窗口)。
在这里需要指出的是,HelloWndProc是我们自己定义的一个函数,我们需要用它来控制我们对特定窗口消息的特定响应。我们只需要在注册窗口类之前,将这个函数的地址(也就是函数名)赋值给WNDCLASS::lpfnWndProc成员就可以了。这个函数我们自己不需要进行调用,它的调用是当我们的窗口收到窗口消息后,由Windows完成的。在这个回调函数中,我们的处理是这样的:
- WM_DESTROY。在窗口被销毁的时候,窗口会收到此消息。在这里,我们会调用PostQuitMessage,用以向当前UI线程的消息队列之中发送一条WM_QUIT消息,GetMessage在收到这条消息后,会返回FALSE,也就结束了消息循环,WinMain也就结束了。
- WM_PAINT。在窗口需要绘制的时候,窗口会收到此消息。在这里我们只是简单的在窗口的中间绘制了一行文字“Hello, SDK!”。
- 其它消息。这些消息都是我们不关心的,所以我们将其交由系统默认的窗口过程DefWindowProc来处理。
这段代码貌似冗长,但实际上还是很有条理的,你可以根据它以及我以上的解说来对照这个程序的ATL版本。
ATL等同品
在写作这本书的时候,我总是希望我每次都能够能使用让你不太陌生的代码来循序渐进地引导你。考虑再三,对于“Hello, ATL!”的这个程序,我决定先把它的WinMain展现给你:
int WINAPI _tWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nShowCmd ) { _Module.Init( NULL, hInstance );
// 创建窗口 CHelloATLWnd wnd; wnd.Create( NULL, CHelloATLWnd::rcDefault, _T("Hello ATL") ); wnd.ShowWindow( nShowCmd ); wnd.UpdateWindow();
// 消息循环 MSG msg; while ( GetMessage( &msg, NULL, 0, 0 ) ) { TranslateMessage( &msg ); DispatchMessage( &msg ); }
_Module.Term(); return msg.wParam; } |