SDK 消息处理

news2024/9/24 17:08:21

目录

消息处理

窗口通知消息处理

鼠标消息

键盘消息

绘图消息 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 进行分析

当关闭窗口时,窗口接收到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的时机:

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

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

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

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

  4. 窗口或控件处理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,那么使用此方法是很方便的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1152178.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

“ 1+2+N “打造“北斗+智慧城市”,让城市生活更美好

10月31日是世界城市日。世界城市日是联合国首个以城市为主题的国际日&#xff0c;也是第一个由中国政府倡议并成功设立的国际日&#xff0c;出自2010年10月31日上海世博会高峰论坛上发布的《上海宣言》中的倡议。世界城市日秉承了中国2010年上海世博会“城市&#xff0c;让生活…

前端知识与基础应用#2

标签的分类 关于标签我们可以分为 &#xff1a; 单标签&#xff1a;img, br hr 双标签&#xff1a;a&#xff0c;h,div 按照属性可分为&#xff1a; 块儿标签&#xff08;自己独自占一行&#xff09;&#xff1a;h1-h6, p,div 行内&#xff08;内联&#xff09;标签&#xff08…

【1】C语言实现顺序表

文章目录 Ⅰ 概念及结构1. 静态顺序表2. 动态顺序表 Ⅱ 基本操作实现1. 定义顺序表2. 初始化顺序表3. 销毁顺序表4. 输出顺序表5. 扩容顺序表6. 尾插法插入数据7. 头插法插入数据8. 尾删法删除数据9. 头删法删除数据10. 顺序表查找11. 在指定位置 pos 插入数据12. 删除指定位置…

【算法】动态规划之LeetCode 53.最大子数组和

目录 文章目录 **目录**&#x1f4d1;前言1.题目描述2. 动态规划法 &#x1f4d1;文章末尾 &#x1f4d1;前言 本文主要是leetcode题解析&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是青衿&#x1f947; ☁…

联想方案服务斩获CCF技术发明奖,助力云原生技术发展

10月27日&#xff0c;中国计算机学会&#xff08;CCF&#xff09;公布了我国计算机科技领域最具权威性的科技奖项——2023年度“CCF科技成果奖”评选结果&#xff0c;共有41个项目荣获2023年度CCF科技成果奖。由联想集团与上海交通大学等共同研究开发的《面向互联网服务的云原生…

【机器学习合集】模型设计之网络宽度和深度设计 ->(个人学习记录笔记)

文章目录 网络宽度和深度设计1. 什么是网络深度1.1 为什么需要更深的模型浅层学习的缺陷深度网络更好拟合特征学习更加简单 2. 基于深度的模型设计2.1 AlexNet2.2 AlexNet工程技巧2.3 VGGNet 3. 什么是网络宽度3.1 为什么需要足够的宽度 4. 基于宽度模型的设计4.1 经典模型的宽…

错误 LNK1112 模块计算机类型“x64”与目标计算机类型“X86”冲突

这个错误表明你在进行链接时&#xff0c;模块的计算机类型与目标计算机类型冲突。 在这里&#xff0c;“x64”代表64位系统&#xff0c;“X86”代表32位系统。 要解决这个问题&#xff0c;你需要确保你的所有模块&#xff08;包括库文件和依赖项&#xff09;都是与你的目标计…

《算法通关村—队列基本特征和实现问题解析》

《算法通关村—队列基本特征和实现问题解析》 队列的基本特征 队列&#xff08;Queue&#xff09;是一种常见的数据结构&#xff0c;具有以下基本特征&#xff1a; 先进先出&#xff08;FIFO&#xff09;&#xff1a;队列中的元素按照它们被添加到队列的顺序排列&#xff0c;…

Matlab论文插图绘制模板第123期—水平正负柱状图

在之前的文章中&#xff0c;分享了很多Matlab柱状图的绘制模板&#xff1a; 进一步&#xff0c;再来看一种特殊的柱状图&#xff1a;水平正负柱状图。 先来看一下成品效果&#xff1a; 特别提示&#xff1a;本期内容『数据代码』已上传资源群中&#xff0c;加群的朋友请自行下…

人到中年应该怎么交朋友

听人劝、吃饱饭,奉劝各位小伙伴,不要订阅该文所属专栏。 作者:不渴望力量的哈士奇(哈哥),十余年工作经验, 跨域学习者,从事过全栈研发、产品经理等工作,现任研发部门 CTO 。荣誉:2022年度博客之星Top4、博客专家认证、全栈领域优质创作者、新星计划导师,“星荐官共赢计…

Doris数据库FE——FeServer

FeServer feServer new FeServer(Config.rpc_port);feServer.start();FeServer&#xff08;Doris frontend thrift server&#xff09;职责是负责FE和BE之间通信。如下即为初始化中关于FeServer类的构造函数和start函数的具体代码。其start函数流程和构建thrift server行为一致…

springboot网上商城购物系统

第1章 绪论 1.1背景及意义 随着社会的快速发展&#xff0c;计算机的影响是全面且深入的。人们生活水平的不断提高&#xff0c;日常生活中人们对网上商城购物系统方面的要求也在不断提高&#xff0c;购物的人数更是不断增加&#xff0c;使得网上商城购物系统的开发成为必需而且…

synchronized 同步锁的思考

经过前面的分析&#xff0c;我们大概对同步锁有了一些基本的认识&#xff0c;同步锁的本质就是实现多线程的互斥&#xff0c;保证同一时刻只有一个线程能够访问加了同步锁的代码&#xff0c;使得线程安全性得到保证。下面我们思考一下&#xff0c;为了达到这个目的&#xff0c;…

毕业工作之后,没有在学校中考试排名这种方式,那如何确定自己是不是一直在退步还是在进步

在职场中衡量自己是否在进步或者退步&#xff0c;相较于学校里通过考试排名来判断要复杂得多。因为职场的评价标准更为多样&#xff0c;同时还涉及到个人职业发展、工作满意度等方面。下面是一些你可以用来判断自己是否在进步的方法&#xff1a; 1. 目标设定与达成 给自己设定…

【数据结构与算法】二叉树基础OJ--下(巩固提高)

前言&#xff1a; 上一篇文章我们讲到二叉树基础oj题目的练习&#xff0c;此篇文章是完成基础oj练习的完结篇。 传送-->【数据结构与算法】二叉树基础OJ -- 上 (巩固提高)-CSDN博客 &#x1f4a5;&#x1f388;个人主页:​​​​​​Dream_Chaser&#xff5e; &#x1f388…

分类预测 | Matlab实现SSA-ELM麻雀优化算法优化极限学习机分类预测

分类预测 | Matlab实现SSA-ELM麻雀优化算法优化极限学习机分类预测 目录 分类预测 | Matlab实现SSA-ELM麻雀优化算法优化极限学习机分类预测分类效果基本描述程序设计参考资料 分类效果 基本描述 1.MATLAB实现SSA-ELM麻雀优化算法优化极限学习机分类预测&#xff08;Matlab完整…

微信小程序云开发如何实现多条件多字段模糊查询

之前的博文中&#xff0c;已经跟大家介绍过&#xff0c;微信小程序云开发如何实现单条件单字段的模糊查询&#xff0c;这个是很常见的业务需求。在一些更复杂的场景下&#xff0c;我们需要实现多条件多字段的模糊查询&#xff0c;比如同时兼容对商品名称、类别、产地等多条件的…

Canvas录制视频

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;正逐渐往全干发展 &#x1f4c3;个人状态&#xff1a; 研发工程师&#xff0c;现效力于中国工业软件事业 &#x1f680;人生格言&#xff1a; 积跬步…

AI:41-基于基于深度学习的YOLO模型的玉米病害检测

🚀 本文选自专栏:AI领域专栏 从基础到实践,深入了解算法、案例和最新趋势。无论你是初学者还是经验丰富的数据科学家,通过案例和项目实践,掌握核心概念和实用技能。每篇案例都包含代码实例,详细讲解供大家学习。 📌📌📌本专栏包含以下学习方向: 机器学习、深度学…

idea使用MyBatisX插件

1.MyBatisX功能 (1).实现mapper和xml的跳转 (2).自动生成java文件&#xff0c;比如mapper、service、dao、pojo 2.安装MyBatisX插件 install后然后重启idea即可 3.使用MyBatieX实现mapper和xml跳转 &#xff08;1&#xff09;.点击mapper中的红色图标即可跳转到对应的xml方…