Windows窗口程序基础
在屏幕上显示一个窗口的过程一般包括以下步骤,也就是入口函数WinMain的执行流程:
- 注册窗口类
在注册之前,要先填写RegisterClassEx函数的参数WNDCLASSEX结构的各个字段。
创建窗口
显示窗口 、刷新窗口客户区
运行消息循环
获取消息、转换消息、将消息分发到回调函数WindowProc处理。
接下来分别介绍每一个步骤:
注册窗口类
RegisterClassEx函数用于注册窗口类,其函数原型如下:
1
| ATOM RegisterClassEx(_In_const WNDCLASSEX* lpwcx);
|
其中参数lpwcx是一个指向WNDCLASSEX结构的指针,调用RegisterClassEx函数必须先初始化此结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| typedef struct tagWNDCLASSEX { UINT cbSize; UINT style; WNDPROC lpfnWndProc; int cbClsExtra; int cbWndExtra; HINSTANCE hInstance; HICON hIcon; HCURSOR hCursor; HBRUSH hbrBackground; LPCWSTR lpszMenuName; LPCWSTR lpszClassName; HICON hIconSm; } WNDCLASSEX, * PWNDCLASSEX;
|
每一项可使用的取值可查看《深入浅出WindowsAPI程序设计》对应此处的内容。
创建窗口
创建窗口的函数是CreateWindowEx,其函数原型如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| HWND CreateWindowEx( _In_ DWORD dwExStyle, _In_opt_ LPCWSTR lpClassName, _In_opt_ LPCWSTR lpWindowName, _In_ DWORD dwStyle, _In_ int x, _In_ int y, _In_ int nWidth, _In_ int nHeight, _In_opt_ HWND hWndParent, _In_opt_ HMENU hMenu, _In_ HINSTANCE hInstance, _In_opt_ LPVOID lpParam );
|
显示窗口
ShowWindow函数用于设置指定窗口的显示状态:
1 2 3 4
| BOOL ShowWindow( _In_ HWND hWnd, _In_ int nCmdShow );
|
在ShowWindow函数执行前,窗口已经在Windows内部创建了,但此时还未显示。其作用是设置指定窗口的显示状态,其通过指定nCmdShow参数来设置。
刷新窗口客户区
UpdateWindow函数通过向窗口发送WM_PAINT消息来刷新窗口客户区,该函数将WM_PAINT消息发送给指定的窗口过程,其绕过消息队列 ,直接调用窗口过程。适用于修改窗口内容后立即刷新内容。
1
| BOOL UpdateWindow(_In_ HWND hWnd);
|
消息循环
程序运行后会发生很多事件,Windows为每个程序维护消息队列,事件发生后系统将其转化为消息放置在队列中,程序会通过以下的消息循环对其进行处理:
GetMessage函数用于从调用线程的消息队列中获取消息:
1 2 3 4 5 6
| BOOL GetMessage( _Out_ LPMSG lpMsg, _In_opt_ HWND hWnd, _In_ UINT wMsgFilterMin, _In_ UINT wMsgFilterMax );
|
MSG结构体用于存放函数获取的消息的具体信息,其定义如下:
1 2 3 4 5 6 7 8
| typedef struct tagMSG{ HWND hwnd; UINT message; WPARAM wParam; LPARAM lParam; DWORD time; POINT pt; }
|
pt字段是一个POINT结构,表示消息发生时的光标位置,其结构在windef.h中定义如下:
1 2 3 4
| typedef struct tagPOINT { LONG x; LONG y; } POINT, * PPOINT,NEAR *NPPOINT, FAR *LPPOINT;
|
窗口过程
窗口过程是程序的消息处理中心,无论是队列还是非队列消息窗口过程都会为程序处理。窗口过程的定义如下:
1 2 3 4 5 6
| LRESULT CALLBACK WindowProc( _In_ HWND hwnd, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam );
|
窗口过程的名称可以任意命名,只要不与其他函数名称冲突即可,WNDPROC是窗口过程指针类型
1
| typedef LRESULT (CALLBACK* WNDPROC)(HWND, UINT, WPARAM, LPARAM);
|
- WM_CREATE消息:
WinMain调用CreateWindowEx函数创建窗口后,系统会向窗口发送WM_CREATE消息。其是窗口过程较早收到的的消息之一,程序常常会在此处做一些初始化工作。
- WM_CLOSE消息:
当用户关闭窗口时,系统会向窗口发送WM_CLOSE消息,DefWindowsProc函数会对其处理,即调用DestroyWindow函数销毁窗口,DestroyWindow函数会向窗口发送WM_DESTROY消息。DefWindowProc函数不会处理该消息,需要我们自己处理:在WM_DESTROY消息中调用PostQuitMessage函数,该函数向消息队列中发送WM_QUIT消息,GetMessage函数收到该消息后返回0,从而结束消息循环退出程序。
- 其他消息处理
DefWindowProc函数用于调用Windows提供的默认 窗口过程,可确保程序的每个消息都得到处理。 1 2 3 4 5
| LRESULT CALLBACK WindowProc( _In_ HWND hwnd, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam);
|
4. WM_PAINT重绘消息
WinMain调用UpdateWindow函数时,系统会向窗口发送WM_PAINT消息。这是Windows编程中很重要的一条消息,当窗口客户区的部分或全部变为无效 (首次创建、调整大小、最小化等)时,系统会向窗口发送WM_PAINT消息,要求窗口重绘客户区。
若一个窗口不对WM_PAINT消息进行处理,那么应交由DefWindowProc函数处理,其依次调用BeginPaint和EndPaint函数,BeginPaint函数用于获取设备上下文,EndPaint函数用于释放设备上下文。
BeginPaint函数原型如下:
1 2 3 4
| HDC BeginPaint( _In_ HWND hWnd, _Out_ LPPAINTSTRUCT lpPaint );
|
PAINTSTRUCT结构用于存放BeginPaint函数获取的设备上下文,其定义如下:
1 2 3 4 5 6 7 8
| typedef struct tagPAINTSTRUCT { HDC hdc; BOOL fErase; RECT rcPaint; BOOL fRestore; BOOL fIncUpdate; BYTE rgbReserved[32]; } PAINTSTRUCT, *PPAINTSTRUCT;
|
EndPaint函数原型如下:
1 2 3 4
| BOOL EndPaint( _In_ HWND hWnd, _In_ const PAINTSTRUCT* lpPaint );
|
一个完整的Windows窗口程序实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| #include <Windows.h> #include <tchar.h> #pragma comment(lib, "Winmm.lib")
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { WNDCLASSEX wndclass; TCHAR szClassName[] = TEXT("MyWindow"); TCHAR szAppName[] = TEXT("HelloWindows"); HWND hwnd; MSG msg; wndclass.cbSize = sizeof(WNDCLASSEX); wndclass.style = CS_HREDRAW | CS_VREDRAW; wndclass.lpfnWndProc = WindowProc; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = 0; wndclass.hInstance = hInstance; wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wndclass.lpszMenuName = NULL; wndclass.lpszClassName = szClassName; wndclass.hIconSm = NULL; RegisterClassEx(&wndclass); hwnd = CreateWindowEx( 0, szClassName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 300, 180, NULL, NULL, hInstance, NULL ); ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); while (GetMessage(&msg, NULL, 0, 0) != 0) { TranslateMessage(&msg); DispatchMessage(&msg); } return (int)msg.wParam; } LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; TCHAR szStr[] = TEXT("你好,Windows程序设计"); switch (uMsg) { case WM_CREATE: PlaySound(TEXT("成都(两会版).wav"), NULL, SND_FILENAME | SND_ASYNC); return 0; case WM_PAINT: hdc = BeginPaint(hwnd, &ps); TextOut(hdc, 10, 10, szStr, _tcslen(szStr)); EndPaint(hwnd, &ps); return 0; case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProc(hwnd, uMsg, wParam, lParam); }
|
杂项
播放音乐
播放音乐的函数是PlaySound,其函数原型如下:
1 2 3 4 5
| BOOL PlaySound( _In_opt_ LPCWSTR lpszSound, _In_opt_ HMODULE hmod, _In_ UINT fdwSound );
|
其中fdwSound参数可以取以下值:
- SND_ASYNC:异步播放(开始播放后立即返回)
- SND_SYNC:同步播放(播放完音乐后函数才返回)
- SND_LOOP:循环播放
显示字符串
TextOut函数用于在指定位置显示一个字符串,其函数原型如下:
1 2 3 4 5 6 7
| BOOL TextOut( _In_ HDC hdc, _In_ int nXStart, _In_ int nYStart, _In_ LPCTSTR lpString, _In_ int cchString );
|