WINDOWS程序设计KEYVIEW程序,SCROLLWINDOW发送WM_PAINT消息问题,WM_KEYDOWN和WM_CHAR发送WM_PAINT问题

书上的原来的程序是这样

#include <windows.h>


LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
	PSTR szCmdLine, int iCmdShow)
{
	static TCHAR szAppName[] = TEXT("KeyView1");
	HWND         hwnd;
	MSG          msg;
	WNDCLASS     wndclass;


	wndclass.style = CS_HREDRAW | CS_VREDRAW;
	wndclass.lpfnWndProc = WndProc;
	wndclass.cbClsExtra = 0;
	wndclass.cbWndExtra = 0;
	wndclass.hInstance = hInstance;
	wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
	wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wndclass.lpszMenuName = NULL;
	wndclass.lpszClassName = szAppName;


	if (!RegisterClass(&wndclass))
	{
		MessageBox(NULL, TEXT("This program requires Windows NT!"),
			szAppName, MB_ICONERROR);
		return 0;
	}


	hwnd = CreateWindow(szAppName, TEXT("Keyboard Message Viewer #1"),
		WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT, CW_USEDEFAULT,
		CW_USEDEFAULT, CW_USEDEFAULT,
		NULL, NULL, hInstance, NULL);


	ShowWindow(hwnd, iCmdShow);
	UpdateWindow(hwnd);


	while (GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	return msg.wParam;
}


LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static int   cxClientMax, cyClientMax, cxClient, cyClient, cxChar, cyChar;
	static int   cLinesMax, cLines;
	static PMSG  pmsg;//PMSG就是MSG的指针形式
	static RECT  rectScroll;
	static RECT  rectTest;
	static TCHAR szTop[] = TEXT("Message        Key       Char     ")
		                   TEXT("Repeat Scan Ext ALT Prev Tran");
	static TCHAR szUnd[] = TEXT("_______        ___       ____     ")
		                   TEXT("______ ____ ___ ___ ____ ____");


	static TCHAR* szFormat[2] = {


	TEXT("%-13s %3d %-15s%c%6u %4d %3s %3s %4s %4s"),//虚拟按键输出格式
	TEXT("%-13s            0x%04X%1s%c %6u %4d %3s %3s %4s %4s") };//字元代码输出格式


	static TCHAR* szYes = TEXT("Yes");
	static TCHAR* szNo = TEXT("No");
	static TCHAR* szDown = TEXT("Down");
	static TCHAR* szUp = TEXT("Up");
	static TCHAR* szMessage[] = {
						 TEXT("WM_KEYDOWN"),       TEXT("WM_KEYUP"),
						 TEXT("WM_CHAR"),          TEXT("WM_DEADCHAR"),
						 TEXT("WM_SYSKEYDOWN"),    TEXT("WM_SYSKEYUP"),
						 TEXT("WM_SYSCHAR"),       TEXT("WM_SYSDEADCHAR") };
	HDC hdc;
	int i, iType;
	PAINTSTRUCT ps;
	TCHAR szBuffer[128], szKeyName[32];
	TEXTMETRIC tm;
	switch (message)
	{
	case WM_CREATE:
	case WM_DISPLAYCHANGE://显示器的分辨率改变后响应该消息
		//获得客户区的最大宽度和最大高度,这里是最大化的时候的客户区的最大宽度和最大高度
		cxClientMax = GetSystemMetrics(SM_CXMAXIMIZED);
		cyClientMax = GetSystemMetrics(SM_CYMAXIMIZED);
		
		
		
		//Get character size for fixed-pitch font
		hdc = GetDC(hwnd);
		SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));//设置等宽字体
		GetTextMetrics(hdc, &tm);
		cxChar = tm.tmAveCharWidth;
		cyChar = tm.tmHeight;
		ReleaseDC(hwnd, hdc);



		//Allocate memory for display lines
		if (pmsg)
			free(pmsg);
		cLinesMax = cyClientMax / cyChar;       //计算最多要接收多少个消息
		pmsg = malloc(cLinesMax * sizeof(MSG)); //pmsg存放这些消息
		cLines = 0;


		//fall through
	case WM_SIZE:
		if (message == WM_SIZE)        //如果程序触发的是WM_SIZE消息,就获取客户区的宽度,高度,
		{                              //如果程序是从WM_DISPLAYCHANGE运行到这里的,if就不需要执行
			cxClient = LOWORD(lParam);
			cyClient = HIWORD(lParam);
		}
		//Calculate scrolling rectangle
		rectScroll.left = 0;
		rectScroll.right = cxClient;
		rectScroll.top = cyChar;
		rectScroll.bottom = cyChar * (cyClient / cyChar);  ///


		rectTest.left= 0;
		rectTest.top = cyChar;
		rectTest.right = cxClient / 2;
		rectTest.bottom = cyClient / 2;

		InvalidateRect(hwnd, NULL, TRUE);
		return 0;
	case WM_KEYDOWN:
	case WM_KEYUP:
	case WM_CHAR:
	case WM_DEADCHAR:
	case WM_SYSKEYDOWN:
	case WM_SYSKEYUP:
	case WM_SYSCHAR:
	case WM_SYSDEADCHAR:
		//Rearrange storage array
		for (int i = cLinesMax - 1; i > 0; i--)                        //接收到消息后每个消息后移,保证位置越靠前的消息是最新的
		{                                                              //若i>=cLinesMax 则这些消息都不会被显示
			pmsg[i] = pmsg[i - 1];
		}
		//Store new message
		pmsg[0].hwnd = hwnd;
		pmsg[0].message = message;
		pmsg[0].wParam = wParam;
		pmsg[0].lParam = lParam;
		


		cLines = min(cLines + 1, cLinesMax);
		//Scroll up the display
		ScrollWindow(hwnd, 0, -cyChar, &rectScroll, &rectScroll);
		//UpdateWindow(hwnd);
		//ScrollWindow(hwnd, 0, -cyChar, NULL, NULL);
		break;//i.e.,call DefWindowProc so Sys messages work
	case WM_PAINT:
	{
		
		hdc = BeginPaint(hwnd, &ps);
		SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));
		SetBkMode(hdc, TRANSPARENT); ///将背景模式设置为透明的,这样画下划线的时候,空白的部分就不会用背景色,这里是白色来填充
		TextOut(hdc, 0, 0, szTop, lstrlen(szTop));
		TextOut(hdc, 0, 0, szUnd, lstrlen(szUnd));
		
		for (int i = 0; i < min(cLines, cyClient / cyChar - 1); i++)
		{
			iType = pmsg[i].message == WM_CHAR ||
				pmsg[i].message == WM_SYSCHAR ||
				pmsg[i].message == WM_DEADCHAR ||
				pmsg[i].message == WM_SYSDEADCHAR;

			GetKeyNameText(pmsg[i].lParam, szKeyName, sizeof(szKeyName) / sizeof(TCHAR));
			TextOut(hdc, 0, (cyClient / cyChar - 1 - i) * cyChar, szBuffer,           //从下往上显示
				wsprintf(szBuffer, szFormat[iType],
					szMessage[pmsg[i].message - WM_KEYFIRST],
					pmsg[i].wParam,
					(PTSTR)(iType ? TEXT(" ") : szKeyName),
					(TCHAR)(iType ? pmsg[i].wParam : ' '),
					LOWORD(pmsg[i].lParam),
					HIWORD(pmsg[i].lParam) & 0xFF,
					0x01000000 & pmsg[i].lParam ? szYes : szNo,
					0x20000000 & pmsg[i].lParam ? szYes : szNo,
					0x40000000 & pmsg[i].lParam ? szDown : szUp,
					0x80000000 & pmsg[i].lParam ? szUp : szDown));
		}
		
		/*
		if (cLines == 0)
		{
			for (int i = 0; i < min(cLines, cyClient / cyChar - 1); i++)
			{
				iType = pmsg[i].message == WM_CHAR ||
					pmsg[i].message == WM_SYSCHAR ||
					pmsg[i].message == WM_DEADCHAR ||
					pmsg[i].message == WM_SYSDEADCHAR;

				GetKeyNameText(pmsg[i].lParam, szKeyName, sizeof(szKeyName) / sizeof(TCHAR));
				TextOut(hdc, 0, (cyClient / cyChar - 1 - i) * cyChar, szBuffer,           //从下往上显示
					wsprintf(szBuffer, szFormat[iType],
						szMessage[pmsg[i].message - WM_KEYFIRST],
						pmsg[i].wParam,
						(PTSTR)(iType ? TEXT(" ") : szKeyName),
						(TCHAR)(iType ? pmsg[i].wParam : ' '),
						LOWORD(pmsg[i].lParam),
						HIWORD(pmsg[i].lParam) & 0xFF,
						0x01000000 & pmsg[i].lParam ? szYes : szNo,
						0x20000000 & pmsg[i].lParam ? szYes : szNo,
						0x40000000 & pmsg[i].lParam ? szDown : szUp,
						0x80000000 & pmsg[i].lParam ? szUp : szDown));
			}
		}
		else
		{
			int temp = pmsg[0].message;
			iType = pmsg[0].message == WM_CHAR ||
				pmsg[0].message == WM_SYSCHAR ||
				pmsg[0].message == WM_DEADCHAR ||
				pmsg[0].message == WM_SYSDEADCHAR;

			GetKeyNameText(pmsg[0].lParam, szKeyName, sizeof(szKeyName) / sizeof(TCHAR));
			TextOut(hdc, 0, (cyClient / cyChar - 1 - 0) * cyChar, szBuffer,           //从下往上显示
				wsprintf(szBuffer, szFormat[iType],
					szMessage[pmsg[0].message - WM_KEYFIRST],
					pmsg[0].wParam,
					(PTSTR)(iType ? TEXT(" ") : szKeyName),
					(TCHAR)(iType ? pmsg[0].wParam : ' '),
					LOWORD(pmsg[0].lParam),
					HIWORD(pmsg[0].lParam) & 0xFF,
					0x01000000 & pmsg[0].lParam ? szYes : szNo,
					0x20000000 & pmsg[0].lParam ? szYes : szNo,
					0x40000000 & pmsg[0].lParam ? szDown : szUp,
					0x80000000 & pmsg[0].lParam ? szUp : szDown));

		}
		*/
		EndPaint(hwnd, &ps);
		return 0;
	}
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	}
    

	return DefWindowProc(hwnd, message, wParam, lParam);
}

我的问题是既然我按下一个按键产生多个消息,又由于窗口过程每次只处理一个消息那是不是绘制的时候就好多都是重复绘制的,因为无效区域里面只要绘制一个消息就可以了,若不考虑其他需要重新绘制的情况,可以改成了注释中的写法,但是不行出现了以下结果。
在这里插入图片描述
可以看到本应该出现的WM_KEYDOWN消息没有出现,经过一番查找之后,使用了VS2019的SPY++才找到原因,
在这里插入图片描述
如图所示实际上在按下一个按键的时候,例如‘A’,虽然发送了三个消息,但是却只发送了两次WM_PAINT,一次是在WM_KEYDOWN和WM_CHAR发出之后,另一次是在WM_KEYUP发出之后,这样的话,就能解释了。改动之后的程序由于只输出最新的那个消息,在第一个WM_PAINT消息接收到之后由于WM_CHAR是最新的,所以只输出了WM_CHAR,而不输出WM_KEYDOWN,同时也可以看到ScrollWindow确实向上滚动了一行。这样的结果在原书的程序中也可以得到体现,当输入一个按键后,例如’A’,若仔细看的话 可以发现KEYDOWN和CHAR消息是同时出现的,KEYUP消息后出现。有趣的是若"同时"按下两个按键会出现以下现象:
在这里插入图片描述
若用SPY++可以看到:
在这里插入图片描述

但是还有一个问题:WM_PAINT消息是哪来的?
回到之前的滚动条程序中若删除ScrollWindow函数之后的UpdateWindow函数,程序仍能正常滚动
删除UpdateWindow前的消息响应
在这里插入图片描述

消息UpdateWindow后的消息响应在这里插入图片描述
可以看到基本没有差别***(或许是由于按下方向键和WM_PAINT消息之间没有其他消息)***但这样足以说明ScrollWindow消息会发送WM_PAINT消息


版权声明:本文为qq_45992231原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。