目录
消息处理
窗口通知消息处理
鼠标消息
键盘消息
绘图消息 WM_PAINT
客户区与非客户区
WM_PAINT消息
BeginPaint && EndPaint
模仿记事本输入字符功能
定时器
消息处理
窗口的过程函数接收到消息后并且进行处理。平时常用的消息以及官方参考文档:
- 绘图消息:WM_PAINT消息 - Win32 apps | Microsoft Learn
- 窗口通知消息:窗口通知 - Win32 apps | Microsoft Learn
- 键盘消息:键盘输入通知 - Win32 apps | Microsoft Learn
- 鼠标消息:鼠标输入通知 - Win32 apps | Microsoft Learn
- 定时器消息:计时器通知 - Win32 apps | Microsoft Learn
当在程序的窗口进行了操作,就会触发WINDOWS消息循环机制:
- 操作系统会把操作封装为一个结构体 MSG,投放到对应进程的消息队列中
- 程序从消息队列中取出消息,翻译,再派发给窗口过程函数进行处理
倘若,一个程序中有多个窗口,控件等,如何辨别消息的归属?
通过窗口过程函数的 hwnd
参数,可以在函数内部判断消息属于哪个窗口,并进行相应的处理。可以使用该句柄来获取窗口的属性、调用窗口相关的函数或者向窗口发送消息等操作。需要注意的是,窗口过程函数的第一个参数 hwnd
是一个有效的窗口句柄,它对应着接收到消息的窗口。在处理消息时,可以使用该句柄来唯一地标识和操作窗口。
WinMain()函数的参数句柄是这个进程的实例句柄,而程序中的每个窗口都有一个自己的句柄,用于标识和操作窗口,要区分开。
窗口通知消息处理
关注WM_CREATE,WM_CLOSE,WM_DESTORY,WM_QUIT即可
注意:WM_CLOSE 是用户关闭窗口时发送的消息,WM_DESTROY 是窗口销毁前发送的消息,而 WM_QUIT 是应用程序退出的消息。它们之间的关系是:当用户关闭窗口时,系统会发送 WM_CLOSE 消息给窗口,窗口过程处理 WM_CLOSE 消息后,通常会发送一个 WM_DESTROY 消息,最终导致应用程序收到 WM_QUIT 消息并退出。
在系统中,消息可以自己手动处理,也可以使用系统默认的处理方式,调用 DefWindowProc 进行处理。
代码实例
LRESULT OnCreate(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
OutputDebugString(_T("[Martlet14fly]: WM_Create\n"));
return TRUE;
}
LRESULT OnClose(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
OutputDebugString(_T("[Martlet14fly]: WM_Close\n"));
DestroyWindow(hwnd);
return FALSE;
}
LRESULT OnDestroy(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
OutputDebugString(_T("[Martlet14fly]: WM_Destory\n"));
PostMessage(hwnd, WM_QUIT, 0, NULL);
return TRUE;
}
LRESULT CALLBACK MyWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
LRESULT lResult = FALSE;
switch (uMsg)
{
case WM_CREATE:
lResult = OnCreate(hwnd, uMsg, wParam, lParam);
break;
case WM_CLOSE:
lResult = OnClose(hwnd, uMsg, wParam, lParam);
break;
case WM_DESTROY:
lResult = OnDestroy(hwnd, uMsg, wParam, lParam);
break;
}
if (!lResult) {
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return lResult;
}
结合 DeBugView 进行分析
当关闭窗口时,窗口接收到WM_CLOSE进行处理,关闭窗口并调用函数 DestoryWindow 发送WM_DESTORY给窗口,并对此消息进行处理,销毁窗口等所有内存资源。
DestoryWindow函数:DestroyWindow 函数 (winuser.h) - Win32 apps | Microsoft Learn
PostMessage函数:PostMessageA 函数 (winuser.h) - Win32 apps | Microsoft Learn
鼠标消息
仅尝试常用的消息
/*
* 下面四个常见的鼠标响应消息,有两个参数 wparam,lparam
* wparam:指示各种虚拟键是否已按下。 此参数可使用以下一个或多个值。
* 值有很多,比如:CTRL 鼠标左键 鼠标中键 SHIFT等
* lparam:
* 低序字指定光标的 x 坐标。 坐标相对于工作区的左上角。
* 高序字指定光标的 y 坐标。 坐标相对于工作区的左上角。
* int xPos = LOWORD(lParam); // 获得鼠标位置的x坐标
* int yPos = HIWORD(lParam); // 获得鼠标位置的y坐标
*/
// 响应鼠标左键单击摁下
LRESULT OnLButtonDown(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
int xPos = LOWORD(lParam);
int yPos = HIWORD(lParam);
TCHAR szBuf[MAXBYTE];
wsprintf(szBuf, _T("[Martlet14fly]: WM_LBUTTONDOWN: xPos:%d yPos:%d\n"), xPos, yPos);
OutputDebugString(szBuf);
return TRUE;
}
// 响应鼠标左键单击抬起
LRESULT OnLButtonUP(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
int xPos = LOWORD(lParam);
int yPos = HIWORD(lParam);
TCHAR szBuf[MAXBYTE];
wsprintf(szBuf, _T("[Martlet14fly] File:%s Line:%d OnLButtonUp hwnd=%p uMsg=%p wParam=%p lParam:%p"
"WM_LBUTTONUP: xPos:%d yPos:%d\n"), __FILE__, __LINE__, xPos, yPos);
OutputDebugString(szBuf);
return TRUE;
}
// 响应鼠标移动消息
LRESULT OnMouseMove(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
int xPos = LOWORD(lParam);
int yPos = HIWORD(lParam);
TCHAR szBuf[MAXBYTE];
wsprintf(szBuf, _T("[Martlet14fly] File:%s Line:%d OnLButtonUp hwnd=%p uMsg=%p wParam=%p lParam:%p"
"WM_LBUTTONUP: xPos:%d yPos:%d\n"), __FILE__, __LINE__, xPos, yPos);
OutputDebugString(szBuf);
return TRUE;
}
// 响应鼠标左键双击消息
LRESULT OnLButtonDoubleClick(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
int xPos = LOWORD(lParam);
int yPos = HIWORD(lParam);
TCHAR szBuf[MAXBYTE];
wsprintf(szBuf, _T("[Martlet14fly]: WM_LBUTTONDOWN: xPos:%d yPos:%d\n"), xPos, yPos);
OutputDebugString(szBuf);
return TRUE;
}
LRESULT CALLBACK MyWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
LRESULT lResult = FALSE;
switch (uMsg)
{
case WM_CREATE:
lResult = OnCreate(hwnd, uMsg, wParam, lParam);
break;
case WM_CLOSE:
lResult = OnClose(hwnd, uMsg, wParam, lParam);
break;
case WM_DESTROY:
lResult = OnDestroy(hwnd, uMsg, wParam, lParam);
break;
case WM_LBUTTONDOWN: // 鼠标左键按下
lResult = OnLButtonDown(hwnd, uMsg, wParam, lParam);
break;
case WM_LBUTTONUP: // 鼠标左键弹起
lResult = OnLButtonUP(hwnd, uMsg, wParam, lParam);
break;
case WM_MOUSEMOVE: // 鼠标左键移动消息
lResult = OnMouseMove(hwnd, uMsg, wParam, lParam);
break;
case WM_LBUTTONDBLCLK: // 鼠标左键双击消息
lResult = OnLButtonDoubleClick(hwnd, uMsg, wParam, lParam);
break;
}
if (!lResult) {
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return lResult;
}
DebugView分析
键盘消息
下面的代码
/*
下面三个消息是常见的响应键盘消息
有两个参数:
1. 非系统密钥的虚拟密钥代码,也就是键盘按钮获得的代码
2. 重复计数、扫描代码、扩展键标志、上下文代码、以前的键状态标志和转换状态标志
*/
// 响应键盘按钮按下的消息
LRESULT OnKeydown(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
TCHAR szBuf[MAXBYTE];
if (wParam >= 'A' && wParam <= 'Z') {
// 获取键盘状态
BYTE KeyState[256];
if (!GetKeyboardState(KeyState))
return TRUE;
//键盘扫描码 扫描码 ascii高位置1,置0,up
BYTE ScanCode = (int)lParam >> 16 & 0xff;
WORD ch;
ToAscii(wParam, ScanCode, KeyState, &ch, 0);
wsprintf(szBuf, _T("[51asm] OnKeyDown %c\n"), ch);
}
else {
wsprintf(szBuf, _T("[51asm] OnKeyDown VK: %x\n"), wParam);
}
OutputDebugString(szBuf);
return TRUE;
}
// 响应键盘按钮抬起的消息
LRESULT OnKeyUp(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
TCHAR szBuf[MAXBYTE];
if (wParam >= 'A' && wParam <= 'Z') {
// 获取键盘状态
BYTE KeyState[256];
if (!GetKeyboardState(KeyState))
return TRUE;
//键盘扫描码 扫描码 ascii高位置1,置0,up
BYTE ScanCode = (int)lParam >> 16 & 0xff;
WORD ch;
ToAscii(wParam, ScanCode, KeyState, &ch, 0);
wsprintf(szBuf, _T("[51asm] OnKeyUp %c\n"), ch);
}
else {
wsprintf(szBuf, _T("[51asm] OnKeyUp VK: %x\n"), wParam);
}
OutputDebugString(szBuf);
return TRUE;
}
// 获取按键
LRESULT OnChar(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
TCHAR szBuf[MAXBYTE];
wsprintf(szBuf, _T("[51asm] OnChar %c\n"), wParam);
OutputDebugString(szBuf);
return TRUE;
}
LRESULT CALLBACK MyWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
LRESULT lResult = FALSE;
switch (uMsg)
{
case WM_CREATE:
lResult = OnCreate(hwnd, uMsg, wParam, lParam);
break;
case WM_CLOSE:
lResult = OnClose(hwnd, uMsg, wParam, lParam);
break;
case WM_DESTROY:
lResult = OnDestroy(hwnd, uMsg, wParam, lParam);
break;
case WM_LBUTTONDOWN: // 鼠标左键按下
lResult = OnLButtonDown(hwnd, uMsg, wParam, lParam);
break;
case WM_LBUTTONUP: // 鼠标左键弹起
lResult = OnLButtonUP(hwnd, uMsg, wParam, lParam);
break;
case WM_MOUSEMOVE: // 鼠标左键移动消息
lResult = OnMouseMove(hwnd, uMsg, wParam, lParam);
break;
case WM_LBUTTONDBLCLK: // 鼠标左键双击消息
lResult = OnLButtonDoubleClick(hwnd, uMsg, wParam, lParam);
break;
case WM_KEYDOWN: // 键盘按键按下的消息
lResult = OnKeydown(hwnd, uMsg, wParam, lParam);
break;
case WM_KEYUP: // 键盘按键抬起的消息
lResult = OnKeyUp(hwnd, uMsg, wParam, lParam);
break;
case WM_CHAR: // 包含按下的键的字符代码。
lResult = OnChar(hwnd, uMsg, wParam, lParam);
break;
}
if (!lResult) {
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return lResult;
}
DebugView分析
当 TranslateMessage 函数翻译WM_KEYDOWN消息时,使用键盘焦点发布到窗口。 WM_CHAR消息包含按下的键的字符代码。所以打印的顺序时WM_KEYDOWN,WM_CHAR,WM_KEYUP
上面的总代码
#include <Windows.h>
#include <tchar.h>
void ShowErrorMsg() {
// 声明一个void 类型的指针,表示任意类型的指针,这是一个缓冲区
LPVOID lpMsgBuf;
// 调用函数把错误信息写入缓冲区
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&lpMsgBuf,
0,
NULL
);
MessageBox(NULL, (LPCTSTR)lpMsgBuf, _T("ERROR"), MB_OK | MB_ICONINFORMATION);
// 释放由 FormatMessage 函数分配的缓冲区,以避免内存泄漏。
LocalFree(lpMsgBuf);
}
LRESULT OnCreate(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
OutputDebugString(_T("[Martlet14fly]: WM_Create\n"));
return TRUE;
}
LRESULT OnClose(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
OutputDebugString(_T("[Martlet14fly]: WM_Close\n"));
DestroyWindow(hwnd);
return TRUE;
}
LRESULT OnDestroy(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
OutputDebugString(_T("[Martlet14fly]: WM_Destory\n"));
PostMessage(hwnd, WM_QUIT, 0, NULL);
return TRUE;
}
/*
* 下面四个常见的鼠标响应消息,有两个参数 wparam,lparam
* wparam:指示各种虚拟键是否已按下。 此参数可使用以下一个或多个值。
* 值有很多,比如:CTRL 鼠标左键 鼠标中键 SHIFT等
* lparam:
* 低序字指定光标的 x 坐标。 坐标相对于工作区的左上角。
* 高序字指定光标的 y 坐标。 坐标相对于工作区的左上角。
* int xPos = LOWORD(lParam); // 获得鼠标位置的x坐标
* int yPos = HIWORD(lParam); // 获得鼠标位置的y坐标
*/
// 响应鼠标左键单击摁下
LRESULT OnLButtonDown(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
int xPos = LOWORD(lParam);
int yPos = HIWORD(lParam);
TCHAR szBuf[MAXBYTE];
wsprintf(szBuf, _T("[Martlet14fly]: WM_LBUTTONDOWN: xPos:%d yPos:%d\n"), xPos, yPos);
OutputDebugStringA(szBuf);
return TRUE;
}
// 响应鼠标左键单击抬起
LRESULT OnLButtonUP(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
int xPos = LOWORD(lParam);
int yPos = HIWORD(lParam);
TCHAR szBuf[MAXBYTE];
wsprintf(szBuf, _T("[Martlet14fly] File:%s Line:%d OnLButtonUp hwnd=%p uMsg=%p wParam=%p lParam:%p"
"WM_LBUTTONUP: xPos:%d yPos:%d\n"), __FILE__, __LINE__, xPos, yPos);
OutputDebugStringA(szBuf);
return TRUE;
}
// 响应鼠标移动消息
LRESULT OnMouseMove(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
int xPos = LOWORD(lParam);
int yPos = HIWORD(lParam);
TCHAR szBuf[MAXBYTE];
wsprintf(szBuf, _T("[Martlet14fly] File:%s Line:%d OnLButtonUp hwnd=%p uMsg=%p wParam=%p lParam:%p"
"WM_LBUTTONUP: xPos:%d yPos:%d\n"), __FILE__, __LINE__, xPos, yPos);
OutputDebugStringA(szBuf);
return TRUE;
}
// 响应鼠标左键双击消息
LRESULT OnLButtonDoubleClick(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
int xPos = LOWORD(lParam);
int yPos = HIWORD(lParam);
TCHAR szBuf[MAXBYTE];
wsprintf(szBuf, _T("[Martlet14fly]: WM_LBUTTONDOWN: xPos:%d yPos:%d\n"), xPos, yPos);
OutputDebugStringA(szBuf);
return TRUE;
}
/*
下面三个消息是常见的响应键盘消息
有两个参数:
1. 非系统密钥的虚拟密钥代码,也就是键盘按钮获得的代码
2. 重复计数、扫描代码、扩展键标志、上下文代码、以前的键状态标志和转换状态标志
*/
// 响应键盘按钮按下的消息
LRESULT OnKeydown(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
TCHAR szBuf[MAXBYTE];
if (wParam >= 'A' && wParam <= 'Z') {
// 获取键盘状态
BYTE KeyState[256];
if (!GetKeyboardState(KeyState))
return TRUE;
//键盘扫描码 扫描码 ascii高位置1,置0,up
BYTE ScanCode = (int)lParam >> 16 & 0xff;
WORD ch;
ToAscii(wParam, ScanCode, KeyState, &ch, 0);
wsprintf(szBuf, _T("[Martlet14fly] OnKeyDown %c\n"), ch);
}
else {
wsprintf(szBuf, _T("[Martlet14fly] OnKeyDown VK: %x\n"), wParam);
}
OutputDebugString(szBuf);
return TRUE;
}
// 响应键盘按钮抬起的消息
LRESULT OnKeyUp(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
TCHAR szBuf[MAXBYTE];
if (wParam >= 'A' && wParam <= 'Z') {
// 获取键盘状态
BYTE KeyState[256];
if (!GetKeyboardState(KeyState))
return TRUE;
//键盘扫描码 扫描码 ascii高位置1,置0,up
BYTE ScanCode = (int)lParam >> 16 & 0xff;
WORD ch;
ToAscii(wParam, ScanCode, KeyState, &ch, 0);
wsprintf(szBuf, _T("[Martlet14fly] OnKeyUp %c\n"), ch);
}
else {
wsprintf(szBuf, _T("[Martlet14fly] OnKeyUp VK: %x\n"), wParam);
}
OutputDebugString(szBuf);
return TRUE;
}
// 获取按键
LRESULT OnChar(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
TCHAR szBuf[MAXBYTE];
wsprintf(szBuf, _T("[Martlet14fly] OnChar %c\n"), wParam);
OutputDebugString(szBuf);
return TRUE;
}
LRESULT CALLBACK MyWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
LRESULT lResult = FALSE;
switch (uMsg)
{
case WM_CREATE:
lResult = OnCreate(hwnd, uMsg, wParam, lParam);
break;
case WM_CLOSE:
lResult = OnClose(hwnd, uMsg, wParam, lParam);
break;
case WM_DESTROY:
lResult = OnDestroy(hwnd, uMsg, wParam, lParam);
break;
case WM_LBUTTONDOWN: // 鼠标左键按下
lResult = OnLButtonDown(hwnd, uMsg, wParam, lParam);
break;
case WM_LBUTTONUP: // 鼠标左键弹起
lResult = OnLButtonUP(hwnd, uMsg, wParam, lParam);
break;
case WM_MOUSEMOVE: // 鼠标左键移动消息
lResult = OnMouseMove(hwnd, uMsg, wParam, lParam);
break;
case WM_LBUTTONDBLCLK: // 鼠标左键双击消息
lResult = OnLButtonDoubleClick(hwnd, uMsg, wParam, lParam);
break;
case WM_KEYDOWN: // 键盘按键按下的消息
lResult = OnKeydown(hwnd, uMsg, wParam, lParam);
break;
case WM_KEYUP: // 键盘按键抬起的消息
lResult = OnKeyUp(hwnd, uMsg, wParam, lParam);
break;
case WM_CHAR: // 包含按下的键的字符代码。
lResult = OnChar(hwnd, uMsg, wParam, lParam);
break;
}
if (!lResult) {
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return lResult;
}
int WINAPI wWinMain(
_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nShowCmd)
{
//设计注册窗口类
TCHAR szWndClassName[] = { _T("Martlet14fly") };
WNDCLASSEX wc = { 0 }; //创建一个类名为 wc 的窗口类
wc.cbSize = sizeof(WNDCLASSEX); //报错参数不对,官方文档要求填写此参数
wc.style = CS_VREDRAW | CS_HREDRAW; //默认填CS_VREDRAM | CS_HREDRAM 的组合,如果调整了窗口的长度和宽度,就重绘窗口
wc.lpfnWndProc = MyWindowProc; //窗口过程函数(窗口回调函数)
wc.cbClsExtra = 0; //默认填0
wc.cbWndExtra = 0; //默认填0
wc.hInstance = hInstance; //标注这个窗口类属于哪个进程,便于从消息队列获取该进程的消息
wc.hIcon = LoadIcon(NULL, IDI_HAND); //图标
wc.hCursor = LoadCursor(NULL, IDC_ARROW); //光标
wc.hbrBackground = CreateSolidBrush(RGB(255, 255, 255)); //用户去背景 白色
wc.lpszMenuName = NULL; //菜单
wc.lpszClassName = szWndClassName; //窗口的类名
// 注册窗口类
if (RegisterClassEx(&wc) == 0) {
ShowErrorMsg();
return 0;
}
// 创建窗口
TCHAR szWndName[] = { _T("Martlet") };
HWND hWnd = CreateWindowEx(
0,
szWndClassName, // 窗口类名
szWndName, // 窗口名
WS_OVERLAPPEDWINDOW, // 组合属性,窗口的最大化,最小化,调整大小边框,标题栏
CW_USEDEFAULT, // 系统默认值
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL, // 父窗口句柄
NULL, // 菜单句柄
hInstance, // 应用程序实例句柄
NULL);
if (hWnd == NULL)
{
ShowErrorMsg();
return 0;
}
// 显示更新窗口
ShowWindow(hWnd, SW_SHOWNORMAL);
UpdateWindow(hWnd);
// 消息循环
BOOL bRet;
MSG msg;
while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0)
{
if (bRet == -1) {
break;
}
else {
TranslateMessage(&msg);// 转换键盘消息
DispatchMessage(&msg); // 派发消息
}
}
return 0;
}
绘图消息 WM_PAINT
参考文档:WM_PAINT消息 - Win32 apps | Microsoft Learn
客户区与非客户区
非客户区(Non Client Area):滚动条,菜单栏,状态栏都是客户区
客户区(Client Area):窗口除了非客户区域
WM_PAINT消息
这个消息在Windows程序设计中是很重要的。当窗口显示区域的一部分显示内容或者全部变为「无效」,以致于必须「更新画面」时,将由这个消息通知程序。
显示区域的显示内容怎么会变得无效呢?在最初建立窗口的时候,整个显示区域都是无效的,因为程序还没有在窗口上画什么东西。第一条WM_PAINT消息(通常发生在WinMain中呼叫UpdateWindow时)指示窗口消息处理程序在显示区域上画一些东西。
触发WM_PAINT的时机:
- 从屏幕外移回屏幕内
- 尺寸被修改:最大化,最小化,还原,修改窗口尺寸大小
- 在 win xp环境下,当窗口被遮挡编程无效区会发送WM_PAINT消息,win10在这点上已经被优化了
WM_PAINT发送的原理:系统内部为每个窗口维护了一个自己的对象,用来管理窗口,当修改窗口尺寸的时候,这个修改后的值将会发送给窗口对象内部的Rect结构体,系统一直检测矩形成员是否有值,当检测到窗口对象的矩形结构体中有值的时候,就会往消息队列里面发送一个WM_PANIT消息
这一部分的使用通过下面的小程序分析学习
BeginPaint && EndPaint
他们二者之间必须配套使用,否则会引起资源泄漏。也就是GDI对象泄漏
对WM_PAINT的处理几乎总是从一个BeginPaint呼叫开始:
hdc = BeginPaint (hwnd, &ps) ;
而以一个EndPaint呼叫结束:
EndPaint (hwnd, &ps) ;
参数:
- hwnd:程序的窗口句柄
- 指向型态为PAINTSTRUCT的结构指针
在BeginPaint呼叫中,如果显示区域的背景还未被删除,则由Windows来删除。它使用注册窗口类别的WNDCLASS结构的hbrBackground字段中指定的画刷来删除背景。在HELLOWIN中, 这是一个白色备用画刷。这意味着,Windows将通过把窗口背景设定为白色来删除窗口背景。
返回值:BeginPaint呼叫令整个显示区域有效,并传回一个「设备内容句柄」。
设备内容:指实体输出设备(如视讯显示器)及其设备驱动程序。在窗口的显示区域显示文字和图形需要设备内容句柄。
EndPaint释放设备内容句柄,使之不再有效。保证了进程GDI对象的恒定,如果没有该句,会出现每次刷新就增长一次的GDI对象。可通过任务管理器查看。
#include <windows.h>
#include <tchar.h>
#include <fstream>
#include <string>
#define IDM_OPEN 102
#define IDM_SAVE 103
#define IDM_EXIT 104
using namespace std;
string g_Text;
TEXTMETRIC g_tm; // 字体信息
void ShowErrorMsg() {
LPVOID lpMsgBuf;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&lpMsgBuf,
0,
NULL
);
MessageBox(NULL, (LPCTSTR)lpMsgBuf, _T("ERROR"), MB_OK | MB_ICONINFORMATION);
LocalFree(lpMsgBuf);
}
LRESULT OnCreate(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
OutputDebugString(_T("[51asm]: WM_Create\n"));
HDC hdc = GetDC(hwnd);
SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));
GetTextMetrics(hdc, &g_tm);
ReleaseDC(hwnd, hdc);
return TRUE;
}
LRESULT OnClose(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
OutputDebugString(_T("[51asm]: WM_Close\n"));
DestroyWindow(hwnd);
return FALSE;
}
LRESULT OnDestroy(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
OutputDebugString(_T("[51asm]: WM_Destory\n"));
PostMessage(hwnd, WM_QUIT, 0, NULL);
return TRUE;
}
LRESULT OnChar(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
TCHAR szBuf[MAXBYTE];
if ((char)wParam == '\r') {
g_Text += (char)wParam;
g_Text += '\n';
}
else if ((char)wParam == '\b') {
if (!g_Text.empty()) {
g_Text.pop_back();
}
}
else {
g_Text += (char)wParam;
}
wsprintf(szBuf, _T("[51asm] OnChar %s\n"), g_Text.data());
OutputDebugString(szBuf);
// // 获取窗口HDC,用API时一定要阅读文档
// // 获取一个新的句柄时,往往是需要释放的,否则该进程的内存会越来愈大
// //HDC hdc = GetWindowDC(hwnd); // 非客户区域
//
// HDC hdc = GetDC(hwnd);
//
// //TextOut(hdc, 0, 0, g_Text.data(), g_Text.length());
// // 获取窗口客户区域大小
// RECT rc;
// GetClientRect(hwnd, &rc);
//
// // 创建一个白色的刷子
// HGDIOBJ hBrushOld;
// HBRUSH hBrush = CreateSolidBrush(RGB(255, 255, 255));
// //check,可能会失败,需要GetLastError
//
//
// // DC选择刷子
// hBrushOld = SelectObject(hdc, hBrush);
// //check
//
// // 绘制背景
// FillRect(hdc, &rc, hBrush);
// //check
//
// // 绘制文本
// DrawText(hdc, g_Text.data(), g_Text.length(), &rc, DT_LEFT);
// //check
//
// // 还原默认刷子
// SelectObject(hdc, hBrushOld);
// //check
//
// // 释放刷子
// DeleteObject(hBrush);
// //check
//
// // 释放DC
// ReleaseDC(hwnd, hdc);
// //check
//
// SetCaretPos(g_tm.tmAveCharWidth * g_Text.length(), 0);
// ShowCaret(hwnd);
// 采用方式2:
RECT rc;
GetClientRect(hwnd, &rc);
// 把整个窗口设置为无效区域
InvalidateRect(hwnd, NULL, TRUE);
// 每当写入后就会产生WM_PAINT消息
return TRUE;
}
LRESULT OnSetFocus(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
OutputDebugString(_T("[51asm] OnSetFocus\n"));
CreateCaret(hwnd, (HBITMAP)NULL, 1, g_tm.tmHeight);
SetCaretPos(0, 0);
ShowCaret(hwnd);
return TRUE;
}
LRESULT OnKillFocus(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
OutputDebugString(_T("[51asm] OnKillFocus\n"));
DestroyCaret();
return TRUE;
}
// 绘制
LRESULT OnPaint(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
OutputDebugString(_T("[51asm] OnPaint\n"));
// // 方式1
// HDC hdc = GetDC(hwnd);
//
// // 获取窗口客户区域大小
// RECT rc;
// GetClientRect(hwnd, &rc);
//
// // 绘制文本
// DrawText(hdc, g_Text.data(), g_Text.length(), &rc, DT_LEFT);
// //check
//
// // 释放DC
// ReleaseDC(hwnd, hdc);
// //check
//
// // 将无效区域设置为有效区域
// ValidateRect(hwnd,&rc);
// 方式2:推荐
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
RECT rc;
GetClientRect(hwnd, &rc);
DrawText(hdc, g_Text.data(), g_Text.length(), &rc, DT_LEFT);
EndPaint(hwnd, &ps); // 自动把无效区域设置为有效区域
return TRUE;
// 无效区域,有变化的区域,系统需要重新绘制,WM_PAINT来了
// 有效区域,不需要变化,系统不需要冲洗绘制
// 设置无效区域为有效区域属于GDI的API
return TRUE;
}
// 擦除背景
LRESULT OnEraseBackground(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
OutputDebugString(_T("[51asm] OnEraseBackground\n"));
DestroyCaret();
return TRUE;
}
LRESULT OnCommand(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
OutputDebugString(_T("[51asm] OnCommand\n"));
WORD wID = LOWORD(wParam);
switch (wID) {
case IDM_OPEN:
MessageBox(NULL, "打开", "51asm", MB_OK);
break;
case IDM_EXIT:
PostQuitMessage(0); // 给自己投递QUIT消息
break;
}
return TRUE;
}
// 消息处理
// 可以下断点debug调试分析消息,在监视这里可以 uMsg.wm 可以以看到
// 先创建非客户区,再创建客户区,还有创建窗口等很多消息
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
LRESULT lResult = FALSE;
switch (uMsg) {
case WM_CREATE:
lResult = OnCreate(hwnd, uMsg, wParam, lParam);
break;
case WM_CLOSE:
lResult = OnClose(hwnd, uMsg, wParam, lParam);
break;
case WM_DESTROY:
lResult = OnDestroy(hwnd, uMsg, wParam, lParam);
break;
case WM_CHAR:
lResult = OnChar(hwnd, uMsg, wParam, lParam);
break;
case WM_SETFOCUS:
lResult = OnSetFocus(hwnd, uMsg, wParam, lParam);
break;
case WM_KILLFOCUS:
lResult = OnKillFocus(hwnd, uMsg, wParam, lParam);
break;
case WM_ERASEBKGND: // 刷背景,最大化时候会刷背景,最小化不会刷
lResult = OnEraseBackground(hwnd, uMsg, wParam, lParam);
break;
case WM_PAINT: //绘制消息,窗口(界面)发生变化就会产生这个消息
lResult = OnPaint(hwnd, uMsg, wParam, lParam);
break;
case WM_COMMAND: //绘制消息,窗口(界面)发生变化就会产生这个消息
lResult = OnCommand(hwnd, uMsg, wParam, lParam);
break;
}
if (!lResult) {
return DefWindowProc(hwnd, uMsg, wParam, lParam); // 默认窗口过程处理函数,包括销毁窗口
}
return lResult;
}
int WINAPI wWinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPWSTR lpCmdLine,
int nShowCmd
) {
// 骚操作,在本窗口给别的窗口发消息
// 启动一个记事本,发送退出消息
// HWND hCalc = FindWindow("Notepad", NULL);
// if (hCalc == NULL) {
// return FALSE;
// }
// // 需要让GetMassage()拿到这个消息
// //SendMessage(hCalc,WM_QUIT,0,NULL); 调用对方过程函数,消息没进消息队列,处理不到
// //PostMessage();
// PostMessage(hCalc,WM_QUIT,0,NULL);
// HWND hNotepad = FindWindow("Notepad", NULL);
// if (hNotepad == NULL) {
// return FALSE;
// }
// HWND hEdit = GetWindow(hNotepad, GW_CHILD);
// PostMessage(hEdit, WM_KEYDOWN, 'A', 0);
// PostMessage(hEdit, WM_KEYUP, 'B', 0);
// PostMessage(hEdit, WM_KEYDOWN, 'C', 0);
//
// HDC hdc = GetDC(hEdit);
// while (TRUE) {
// SetTextColor(hdc,RGB(255,0,0));
// TextOut(hdc, 0, 0, "SB", 2);
// }
// ReleaseDC(hEdit, hdc);
// 创建窗口实例
TCHAR szWndClassName[] = { _T("CR41WndClassName") };
WNDCLASSEX wc = { 0 };
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_VREDRAW | CS_HREDRAW | CS_DBLCLKS;
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_HAND);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = CreateSolidBrush(RGB(255, 255, 255));
wc.lpszMenuName = NULL;
wc.lpszClassName = szWndClassName;
// 注册窗口
if (RegisterClassEx(&wc) == 0) {
ShowErrorMsg();
return 0;
}
// 创建窗口
TCHAR szWndName[] = { _T("51asm") };
HWND hWnd = CreateWindowEx(0,
szWndClassName,
szWndName,
WS_OVERLAPPEDWINDOW, //组合属性,可拉伸窗口
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
hInstance,
NULL);
if (hWnd == NULL) {
ShowErrorMsg();
return 0;
}
// 菜单
HMENU hMenu = CreateMenu();
// 弹出菜单
BOOL ret;
ret = AppendMenu(hMenu, MF_STRING | MF_POPUP, (UINT_PTR)hMenu, "文件(&F)");
ret = AppendMenu(hMenu, MF_STRING | MF_POPUP, (UINT_PTR)hMenu, "编辑(&E)");
SetMenu(hWnd, hMenu);
// 添加子菜单
HMENU hSubMenu = GetSubMenu(hMenu, 0);
ret = AppendMenu(hSubMenu, MF_STRING, IDM_OPEN, "打开(&O)");
ret = AppendMenu(hSubMenu, MF_STRING, IDM_SAVE, "保存(&O)");
ret = AppendMenu(hSubMenu, MF_STRING, IDM_EXIT, "退出(&O)");
SetMenu(hWnd, hMenu);
RECT rc;
GetClientRect(hWnd, &rc);
// 控件:带有特殊功能的窗口
// 按钮 编辑框
// HWND hEdit = CreateWindowEx(0,
// _T("Edit"),
// NULL,
// WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_HSCROLL | ES_MULTILINE, //组合属性,可拉伸窗口 最后一个表示支持多行
// 0,
// 0,
// rc.right - rc.left,
// rc.bottom - rc.top,
// hWnd,
// NULL,
// hInstance,
// NULL);
// 显示,更新窗口
ShowWindow(hWnd, SW_SHOWNORMAL); // 调用Show时候父子窗口都会被调用
//ShowWindow(hChild, SW_SHOWNORMAL); 非子窗口需要单独show
UpdateWindow(hWnd); // 产生WM_PAINT
SetClassLong(hWnd, GCL_HCURSOR, (LONG)LoadCursor(NULL, IDC_CROSS));
// 消息循环
BOOL bRet;
MSG msg;
while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0)
{
if (bRet == -1) {
break;
}
else {
TranslateMessage(&msg);// 转换键盘消息
DispatchMessage(&msg); // 派发消息
}
}
return msg.wParam;
}
模仿记事本输入字符功能
通过这个小功能实现的代码,进一步学习键盘消息,以及WM_PAINT
需要考虑一个问题,当发现输入错误时,删除输入的字符,如何实现?答案是重新绘制窗口区域,就是先清除掉所有字符,再重新打印输入的字符即可
#include <windows.h>
#include <tchar.h>
#include <fstream>
#include <string>
using namespace std;
string g_Text;
TEXTMETRIC g_tm; // 字体信息,TEXTMETRIC 结构包含有关物理字体的基本信息
void ShowErrorMsg() {
LPVOID lpMsgBuf;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&lpMsgBuf,
0,
NULL
);
MessageBox(NULL, (LPCTSTR)lpMsgBuf, _T("ERROR"), MB_OK | MB_ICONINFORMATION);
LocalFree(lpMsgBuf);
}
LRESULT OnCreate(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
OutputDebugString(_T("[Martlet14fly]: WM_Create\n"));
HDC hdc = GetDC(hwnd);
// 使用Windows API函数来选择系统固定宽度字体,并获取该字体的文本度量信息
// 通过将字体选入设备上下文,后续的文本绘制操作将使用该字体进行显示。通过获取文本度量信息,可以准确计算字符的位置和大小。
SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT)); // 将系统固定宽度字体选入设备上下文
GetTextMetrics(hdc, &g_tm); // 返回系统固定宽度字体的句柄
ReleaseDC(hwnd, hdc);
return TRUE;
}
LRESULT OnClose(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
OutputDebugString(_T("[Martlet14fly]: WM_Close\n"));
DestroyWindow(hwnd);
return FALSE;
}
LRESULT OnDestroy(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
OutputDebugString(_T("[Martlet14fly]: WM_Destory\n"));
PostMessage(hwnd, WM_QUIT, 0, NULL);
return TRUE;
}
LRESULT OnChar(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
TCHAR szBuf[MAXBYTE];
if ((char)wParam == '\r') { // 处理换行符,SDK接收的换行符有点问题,需要自己补上一个 \n
g_Text += (char)wParam;
g_Text += '\n';
}
else if ((char)wParam == '\b') { // 处理删除键
if (!g_Text.empty()) {
g_Text.pop_back(); // 弹出上一次输入的
}
}
else {
g_Text += (char)wParam; // 把输入的字符放到字符串中
}
wsprintf(szBuf, _T("[Martlet14fly] OnChar %s\n"), g_Text.data());
OutputDebugString(szBuf);
/*
注意:获取句柄都有失败的可能,可以之后调用一下ShowError()获取错误信息
*/
// 获取窗口工作区句柄,记得需要释放句柄
HDC hdc = GetDC(hwnd);
//HDC hdc = GetWindowDC(hwnd); // 非客户区域
// 获取窗口客户区域大小
// RECT 表示矩形区域的坐标和尺寸信息。
RECT rc;
GetClientRect(hwnd, &rc);
// 创建一个白色的刷子
HGDIOBJ hBrushOld;
HBRUSH hBrush = CreateSolidBrush(RGB(255, 255, 255)); // 创建具有指定纯色的逻辑画笔。
// DC选择刷子
hBrushOld = SelectObject(hdc, hBrush);
// 绘制背景
FillRect(hdc, &rc, hBrush);
// 绘制文本
DrawText(hdc, g_Text.data(), g_Text.length(), &rc, DT_LEFT);
// 还原默认刷子
SelectObject(hdc, hBrushOld);
// 释放刷子句柄
DeleteObject(hBrush);
// 释放窗口工作区句柄
ReleaseDC(hwnd, hdc);
SetCaretPos(g_tm.tmAveCharWidth * g_Text.length(), 0);
ShowCaret(hwnd);
return TRUE;
}
LRESULT OnSetFocus(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
OutputDebugString(_T("[Martlet14fly] OnSetFocus\n"));
CreateCaret(hwnd, (HBITMAP)NULL, 1, g_tm.tmHeight); // 创建了光标。
SetCaretPos(0, 0); // 用于设置光标的位置
ShowCaret(hwnd); // 用于显示光标
return TRUE;
}
LRESULT OnKillFocus(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
OutputDebugString(_T("[Martlet14fly] OnKillFocus\n"));
// 销毁光标
DestroyCaret();
return TRUE;
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
LRESULT lResult = FALSE;
switch (uMsg) {
case WM_CREATE:
lResult = OnCreate(hwnd, uMsg, wParam, lParam);
break;
case WM_CLOSE:
lResult = OnClose(hwnd, uMsg, wParam, lParam);
break;
case WM_DESTROY:
lResult = OnDestroy(hwnd, uMsg, wParam, lParam);
break;
case WM_CHAR:
lResult = OnChar(hwnd, uMsg, wParam, lParam);
break;
case WM_SETFOCUS:
lResult = OnSetFocus(hwnd, uMsg, wParam, lParam);
break;
case WM_KILLFOCUS:
lResult = OnKillFocus(hwnd, uMsg, wParam, lParam);
break;
}
if (!lResult) {
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return lResult;
}
int WINAPI wWinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPWSTR lpCmdLine,
int nShowCmd)
{
TCHAR szWndClassName[] = { _T("Martlet14fly") };
WNDCLASSEX wc = { 0 };
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_VREDRAW | CS_HREDRAW | CS_DBLCLKS;
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_HAND);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = CreateSolidBrush(RGB(255, 255, 255));
wc.lpszMenuName = NULL;
wc.lpszClassName = szWndClassName;
if (RegisterClassEx(&wc) == 0)
{
ShowErrorMsg();
return 0;
}
// 创建窗口
TCHAR szWndName[] = { _T("51asm") };
HWND hWnd = CreateWindowEx(
0,
szWndClassName,
szWndName,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
hInstance,
NULL);
if (hWnd == NULL) {
ShowErrorMsg();
return 0;
}
ShowWindow(hWnd, SW_SHOWNORMAL);
SetClassLong(hWnd, GCL_HCURSOR, (LONG)LoadCursor(NULL, IDC_CROSS));
BOOL bRet;
MSG msg;
while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0)
{
if (bRet == -1) {
break;
}
else {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return msg.wParam;
}
获取句柄后,需要释放,否则会占用内存会越来越大
WM_SETFOCUS:用于通知窗口或控件获得焦点。当一个窗口或控件被激活并接收到输入焦点时,系统会发送WM_SETFOCUS
消息给该窗口或控件
WM_KILLFOCUS:就是通过其失去焦点
在Windows操作系统中,当一个窗口或控件需要获得焦点时,系统会自动发送WM_SETFOCUS
消息给该窗口或控件。具体的发送过程如下:
-
用户操作触发焦点变化:焦点变化可以由用户操作引发,例如点击窗口、按下Tab键或通过鼠标选择控件等。
-
窗口或控件接收到焦点变化消息:操作系统会检测到焦点变化,并将焦点变化消息发送给相应的窗口或控件。
-
系统发送
WM_SETFOCUS
消息:当窗口或控件接收到焦点变化消息后,系统会自动发送WM_SETFOCUS
消息给该窗口或控件。 -
窗口或控件处理
WM_SETFOCUS
消息:接收到WM_SETFOCUS
消息后,窗口或控件可以在消息处理函数中进行相应的操作,例如更新外观、执行逻辑操作或通知其他部分。
这个其实还有很多缺陷,字体的高度宽度没有考虑到,但是也了解到一个文本编辑器输入文本的基本处理思路。其实想要得到一个输入记事本,可以直接使用控件。
定时器
定时器最好不要填窗口句柄,直接通过定时器编号来设置和关闭定时器。否则可能出现定时器对象未完全杀死的情况。
方式一:
SetTimer (hwnd, 1, uiMsecInterval, NULL) ;
KillTimer (hwnd, 1) ;
#define 叙述定义不同的计时器 ID
#define TIMER_SEC 1
#define TIMER_MIN 2
然後您可以使用两个 SetTimer 呼叫来设定两个计时器
SetTimer (hwnd, TIMER_SEC, 1000, NULL) ;
SetTimer (hwnd, TIMER_MIN, 60000, NULL) ;
case WM_TIMER:
switch (wParam)
{
case TIMER_SEC:
//每秒一次的处理
break ;
case TIMER_MIN:
//每分钟一次的处理
break ;
}
return 0 ;
方式二:直接让计时器调用计时器过程函数
VOID CALLBACK TimerProc ( HWND hwnd, UINT message, UINT iTimerID, DWORD dwTime)
{
处理 WM_TIMER 讯息
}
SetTimer (hwnd, iTimerID, iMsecInterval, TimerProc) ;
方式三:不绑定窗口,将窗口句柄参数设置为NULL。
注意:传递给 SetTimer 的 hwnd参数被设定为 NULL,并且第二个参数(通常为计时器 ID)被忽略了,最后,此函式传回计时器 ID:
iTimerID = SetTimer (NULL, 0, wMsecInterval, TimerProc) ;
如果没有可用的计时器,那么从 SetTimer 传回的 iTimerID 值将为 NULL。
KillTimer 的第一个参数(通常是视窗代号)也必须为 NULL,计时器 ID 必须是 SetTimer 的传回值:
KillTimer (NULL, iTimerID) ;
传递给 TimerProc 计时器函式的 hwnd 参数也必须是 NULL。
适用场景:这种设定计时器的方法很少被使用。如果想程序在不同时刻有一系列的 SetTimer 呼叫,而又不希望追踪您已经用过了那些计时器 ID,那么使用此方法是很方便的。