SDK 消息处理

目录

消息处理

窗口通知消息处理

鼠标消息

键盘消息

绘图消息 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消息循环机制:

  1. 操作系统会把操作封装为一个结构体 MSG,投放到对应进程的消息队列中
  2. 程序从消息队列中取出消息,翻译,再派发给窗口过程函数进行处理

倘若,一个程序中有多个窗口,控件等,如何辨别消息的归属?

通过窗口过程函数的 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 进行分析

SDK 消息处理_第1张图片

当关闭窗口时,窗口接收到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 
#include 


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的时机:

  1. 从屏幕外移回屏幕内
  2. 尺寸被修改:最大化,最小化,还原,修改窗口尺寸大小
  3. 在 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 
#include 
#include 
#include 

#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 
#include 
#include 
#include 

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消息给该窗口或控件。具体的发送过程如下:

  1. 用户操作触发焦点变化:焦点变化可以由用户操作引发,例如点击窗口、按下Tab键或通过鼠标选择控件等。

  2. 窗口或控件接收到焦点变化消息:操作系统会检测到焦点变化,并将焦点变化消息发送给相应的窗口或控件。

  3. 系统发送WM_SETFOCUS消息:当窗口或控件接收到焦点变化消息后,系统会自动发送WM_SETFOCUS消息给该窗口或控件。

  4. 窗口或控件处理WM_SETFOCUS消息:接收到WM_SETFOCUS消息后,窗口或控件可以在消息处理函数中进行相应的操作,例如更新外观、执行逻辑操作或通知其他部分。

SDK 消息处理_第2张图片

这个其实还有很多缺陷,字体的高度宽度没有考虑到,但是也了解到一个文本编辑器输入文本的基本处理思路。其实想要得到一个输入记事本,可以直接使用控件。

定时器

定时器最好不要填窗口句柄,直接通过定时器编号来设置和关闭定时器。否则可能出现定时器对象未完全杀死的情况。

方式一:

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 ;

SDK 消息处理_第3张图片

方式二:直接让计时器调用计时器过程函数

VOID CALLBACK TimerProc (  HWND hwnd, UINT message, UINT iTimerID, DWORD dwTime)
{
处理 WM_TIMER 讯息
}

SetTimer (hwnd, iTimerID, iMsecInterval, TimerProc) ;

SDK 消息处理_第4张图片

方式三:不绑定窗口,将窗口句柄参数设置为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,那么使用此方法是很方便的。

你可能感兴趣的:(SDK,C++,SDK,Windows)