(三) 窗口的创建与消息处理
原来那个程序是不是感觉有点简单?这里我们要制作一个复杂一点的Windows程序 - 显示一个空白窗口。
出于稳定性考量,Visual C++编译器中int、long等基本类型基本上没有变化(16->32仅int变化了一次,64位没有变化),但是指针则16位、32位、64位一直在变化,为了更好地适应指针的变化,以及方便地与指针相互转换,Windows中定义了平台相关整数,它们随指针长度而变化:
INT_PTR 和
UINT_PTR - 16/32/64位平台相关整数,前者有符号,后者无符号
LONG_PTR 和
ULONG_PTR - 32/32/64位平台相关整数,前者有符号,后者无符号
这四个类型在以后的编程中要经常用到,务必明白它们的意思。
关于调用约定:调用约定表示调用函数时参数的传递方式。在Windows中绝大多数WinAPI都是
__stdcall,只有wsprintf等参数数量可变的函数是
__cdecl,自己定义的各种Win32函数也最好采用
__stdcall。关于调用约定的内部信息请参阅相关资料,这里不详述。
一、准备一个窗口消息处理函数
Windows中
窗口事件的处理是靠
窗口消息来完成的,为了处理窗口消息,每一个窗口都至少对应一个窗口消息处理函数,窗口消息处理函数的定义如下:
LONG_PTR __stdcall WndProc(void* hWnd, unsigned int msg, UINT_PTR wp, LONG_PTR lp)
{
return DefWindowProc(hWnd, msg, wp, lp);
}
程序在处理完消息之后,return 0;直接返回,否则应该调用
DefWindowProc来处理不想处理的消息。
常用的消息如下:
WM_CREATE - 窗口创建时被调用
WM_DESTROY - 窗口销毁时被调用
WM_PAINT - 窗口绘制时被调用
WM_COMMAND - 控件或窗口有消息时被调用
WM_SIZE - 窗口大小改变时被调用
为了在同一个函数中处理不同消息,我们一般使用switch (msg) { case WM_XXX: ... }分支来编写WndProc函数:
LONG_PTR __stdcall WndProc(void* hWnd, unsigned int msg, UINT_PTR wp, LONG_PTR lp)
{
switch (msg)
{
case WM_CREATE:
return 0;
case WM_DESTROY:
return 0;
}
return DefWindowProc(hWnd, msg, wp, lp);
}
在这里,我们只需处理WM_DESTROY即可:
LONG_PTR __stdcall WndProc(void* hWnd, unsigned int msg, UINT_PTR wp, LONG_PTR lp)
{
switch (msg)
{
case WM_DESTROY:
return 0;
}
return DefWindowProc(hWnd, msg, wp, lp);
}
二、全局变量
我们需要两个全局变量保存模块和主窗口的句柄:
void* hInst;
void* hMainWnd;
在wWinMain中,第一个参数提供了模块句柄,现在保存到全局变量:
int __stdcall wWinMain(void* hInstance, void* reserved, wchar_t* cmdline, int nshow)
{
hInst = hInstance;
}
三、注册窗口类
在Windows中,窗口的部分特性需要与窗口类(
WNDCLASS)进行自定义。WNDCLASS是一个C语言结构体,我们需要填写它们的内容。
WNDCLASS {
样式 函数(样式=窗口类样式,常用的有CS_HREDRAW和CS_VREDRAW;函数=窗口处理函数)
类增 窗增(高级参数,表示窗口类和每个窗口附加数据的字节数,一般填0)
模块 图标 光标(模块=模块句柄;图标=左上角图标;光标=常说的鼠标指针)
背景 菜单 类名(背景=背景画刷;菜单=菜单资源标识,字符串或小于65536的整数;类名=窗口类的名称,随便写)
}
在这里如下填写,这些内容一般情况下是比较固定的:
WNDCLASS wc = {
CS_HREDRAW | CS_VREDRAW, // 样式:横向、纵向改变大小时重绘
WndProc,
0, 0,
hInst,
LoadIcon(NULL, IDI_APPLICATION),
LoadCursor(NULL, IDC_ARROW),
GetStockObject(WHITE_BRUSH),
NULL,
L"MyWindow",
};
填写完这个类之后,我们可以调用
RegisterClass函数注册这个窗口类:
if (!RegisterClass(&wc))
return 1;
四、新建窗口
新建窗口需要使用
CreateWindowEx函数,这个函数有12个参数,看似比较多,其实也不难记:
CreateWindowEx(
扩展样式, 窗口类名, 窗口标题, 窗口样式, // 窗口样式常用WS_OVERLAPPEDWINDOW
左, 上, 宽, 高, // 不想填写可以使用CW_USEDEFAULT
父窗句柄, 菜单句柄, 模块句柄, 可选参数
)
不是所有参数都是必要的,部分参数可以填0或NULL。
在这里,我们这样建立一个标准的窗口:
hMainWnd = CreateWindowEx(
0, L"MyWindow", L"这是主窗口", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInst, NULL);
建立窗口以后,我们一般需要显示并更新窗口,这一步比较简单:
ShowWindow(hMainWnd, nshow);
UpdateWindow(hMainWnd);
五、进入消息循环
窗口显示以后,我们的程序
并没有就此停住并继续接收窗口消息,因此我们要在wWinMain中不停地等待消息,并让系统进行处理。
其中
GetMessage等待并获取消息,
TranslateMessage将虚拟键码转换为Unicode字符,
DispatchMessage将消息分发到处理函数
代码如下:
MSG msg;
int rslt;
while (rslt = GetMessage(&msg, NULL, 0, 0))
{
if (rslt == -1)
return 1;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
六、退出消息循环
接收到一般消息时,
GetMessage会返回TRUE,这时消息循环继续运行。当接收到WM_QUIT消息时,GetMessage会返回FALSE,程序将退出。退出代码保存在msg.wParam中:
return (int)msg.wParam;
在窗口被销毁时,我们期望消息循环退出,因此要向消息循环发送WM_QUIT消息,方法是调用
PostQuitMessage(n)函数,n为退出码。在WndProc中添加以下代码:
LONG_PTR __stdcall WndProc(void* hWnd, unsigned int msg, UINT_PTR wp, LONG_PTR lp)
{
switch (msg)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hWnd, msg, wp, lp);
}
七、运行效果
八、完整的程序
void* hInst;
void* hMainWnd;
LONG_PTR __stdcall WndProc(void* hWnd, unsigned int msg, UINT_PTR wp, LONG_PTR lp)
{
switch (msg)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hWnd, msg, wp, lp);
}
int __stdcall wWinMain(void* hInstance, void* reserved, wchar_t* cmdline, int nshow)
{
hInst = hInstance;
WNDCLASS wc = {
CS_HREDRAW | CS_VREDRAW,
WndProc,
0, 0,
hInst,
LoadIcon(NULL, IDI_APPLICATION),
LoadCursor(NULL, IDC_ARROW),
GetStockObject(WHITE_BRUSH),
NULL,
L"MyWindow",
};
if (!RegisterClass(&wc))
return 1;
hMainWnd = CreateWindowEx(
0, L"MyWindow", L"这是主窗口", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInst, NULL);
ShowWindow(hMainWnd, nshow);
UpdateWindow(hMainWnd);
MSG msg;
int rslt;
while (rslt = GetMessage(&msg, NULL, 0, 0))
{
if (rslt == -1)
return 1;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}</windows.h>
200字以内,仅用于支线交流,主线讨论请采用回复功能。