《Windows程序设计》读书笔七 鼠标

Posted sesiria

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《Windows程序设计》读书笔七 鼠标相关的知识,希望对你有一定的参考价值。

第七章 鼠标

7.1 鼠标的基础知识

fMouse = GetSystemMetrics(SM_MOUSEPRESENT); //判断是否使用鼠标


cButtons = GetSystemMetrics(SM_CMOUSEBUTTONS); //判断安装鼠标的个数


GetSystemMetrics(SM_SWAPBUTTON); //判断鼠标按钮是否被切换


SystemParametersInfo获得鼠标相关的参数信息


7.1.1 一些基本术语

IDC_ARROW

IDC_CROSS

IDC_WAIT


wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); //加载鼠标指针图标


LBUTTON, MBUTTON, RBUTTON


7.1.2 鼠标的复数形式是什么?

7.2 客户区鼠标消息

只要鼠标点击了窗口,即使非活动也能收到消息。


x = LOWORD(lParam);

y = HIWORD(lParam);

参数wParam表示鼠标按钮Shift和Ctrl的状态


wParam & MK_SHIFT  当收到WM_LBUTTONDOWN 表示按下了左键的同时又按下了SHIFT键钮。


7.2.1 简单的鼠标处理示例

#include <windows.h>

#define MAXPOINTS	1000

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); //window procedure.

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
	PSTR szCmdLine, int iCmdShow)
{
	static		TCHAR szAppName[] = TEXT("Connect");
	HWND		hwnd;
	MSG			msg;
	WNDCLASS	wndClass;		//The window Class

	wndClass.style = CS_HREDRAW | CS_VREDRAW;
	wndClass.lpfnWndProc = WndProc;// assign the window procedure to windows class.
	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;

	//Register the Window Class to the Windows System. 
	if (!RegisterClass(&wndClass))
	{
		MessageBox(NULL, TEXT("This program require Windows NT!"),
			szAppName, MB_ICONERROR);
		return 0;
	}

	//This function will generate an WM_CREATE message.
	hwnd = CreateWindow(szAppName,		//Window class name
		TEXT("Connect-the-Points Mouse Demo"),		//Window caption
		WS_OVERLAPPEDWINDOW,			//Window Style
		CW_USEDEFAULT,					//initial x position
		CW_USEDEFAULT,					//initial y position
		CW_USEDEFAULT,					//initial x size
		CW_USEDEFAULT,					//initial y size
		NULL,							//parent window handle
		NULL,							//window menu handle
		hInstance,						//program instance handle
		NULL);							//creation parameters

	ShowWindow(hwnd, iCmdShow);
	UpdateWindow(hwnd);	//This function will generate a WM_PAINT message.

						/* The message loop for this program.
						if received the WM_QUIT message, the function will return 0.*/
	while (GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	return msg.wParam;

}

//define the Window Procedure WndProc
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static POINT	pt[MAXPOINTS];
	static int		iCount;
	HDC				hdc;
	int				i, j;
	PAINTSTRUCT		ps;

	switch (message) //get the message
	{
	case WM_LBUTTONDOWN:
		iCount = 0;
		InvalidateRect(hwnd, NULL, TRUE);
		return 0;

	case WM_MOUSEMOVE:
		if (wParam & MK_LBUTTON && iCount < 1000)
		{
			pt[iCount].x = LOWORD(lParam);
			pt[iCount++].y = HIWORD(lParam);

			hdc = GetDC(hwnd);
			SetPixel(hdc, LOWORD(lParam), HIWORD(lParam), 0);
			ReleaseDC(hwnd, hdc);
		}
		return 0;

	case WM_LBUTTONUP:
		InvalidateRect(hwnd, NULL, FALSE);
		return 0;

	case WM_PAINT:
		hdc = BeginPaint(hwnd, &ps);
		
		SetCursor(LoadCursor(NULL, IDC_WAIT));
		ShowCursor(TRUE);

		for(i = 0; i < iCount - 1; i++)
			for (j = i + 1; j < iCount; j++)
			{
				MoveToEx(hdc, pt[i].x, pt[i].y, NULL);
				LineTo(hdc, pt[j].x, pt[j].y);
			}

		ShowCursor(FALSE);
		SetCursor(LoadCursor(NULL, IDC_ARROW));

		EndPaint(hwnd, &ps);
		return 0;
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	}
	return  DefWindowProc(hwnd, message, wParam, lParam);
}

运行结果如下



7.2.2 处理Shift键

if (wParam & MK_SHIFT)
{
    if(wParam & MK_CONTROL)
    {
        //press shift+ctrl
    }else
    {
        //press shift
    }
}
else
{
    if(wParam & MK_CONTROL)
    {
        //press ctrl
    }else
    {
        //neither press shift nor ctrl
    }
}


使用左键配合Shift键等效右键

case WM_LBUTTONDOWN:
    if(!(wParam & MK_SHIFT))
    {
        //process for left button down.
        return 0;
    }
case WM_RBUTTONDOWN:
    // process for the right button down.
    return 0;



7.2.3 鼠标双击

wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;


用户双击以后窗口收到的消息如下:

WM_LBUTTONDOWN

WM_LBUTTONUP

WM_LBUTTONDBLCLK  //如果未定义双击 则为 WM_LBUTTONDOWN

WM_LBUTTONUP


7.3 非客户区鼠标消息

非客户区域包括 标题栏,菜单和窗口滚动条

系统一般不需要用户处理非客户区鼠标消息,交给DefWindowProc即可


wParam 表示非客户区鼠标移动或单击的位置 其值是 HT_ 为首的一些值

lParam 低位包含x坐标,高位包含y坐标 都是屏幕坐标

ScreenToClient(hwnd, &pt);

ClientToScreen(hwnd, &pt);


7.3.1 击中测试消息

WM_NCHITTEST  非客户区击中测试 优先级高于其他一切鼠标消息

lParam  表示屏幕坐标 x, y   wParam 无用

DefWindowProc处理改消息会产生wParam的值

HTCLIENT  客户区

HTNOWHERE 不再客户区

HTTRANSPARENT 被另一个窗口覆盖的窗口

HTERROR  使DefWindowProc产生一个警示声


例如捕捉WM_SYSKEYDOWN 使所有系统函数键盘失效

case  WM_NCHITTEST:

    return (LRESULT) HITNOWHERE;

可以阻止系统向窗口发送的所有客户区和非客户区鼠标消息。此时所有鼠标按钮操作都失效


7.3.2 消息引发消息

例如双击系统标题栏图标关闭程序

产生WM_NCHITTEST消息  ,  DefWindowProc处理 返回HTSYSMENU, 同时添加WM_NCLBUTTONDBLCLK消息

然后DefWindowProc又处理该消息,参数wParam为HTSYSMENU。 系统会在消息队列家一个WM_SYSCOMMAND消息,其中参数是SC_CLOSE.

然后DefWindowProc又处理该消息,并向窗口发送WM_CLOSE

如果程序在结束前想等待用户确认,可以捕捉WM_CLOSE消息。 否则DefWindowProc捕捉该消息以后会像窗口发送WM_DESTROY:

而WM_DESTROY一般做如下处理:

case WM_DESTROY:
PostQuitMessage(0);
return 0;
}

会是系统在消息队列中加一条WM_QUIT消息,  而消息循环GetMessage捕捉到该消息会返回0.从而程序退出。


7.4 程序中的击中测试

一般是对传递到窗口过程的x,y的一些计算 其中x,y的值在lParam中

7.4.1 一个假想的例子

7.4.2 一个简单的程序

#include <windows.h>

#define DIVISIONS	5

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); //window procedure.

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
	PSTR szCmdLine, int iCmdShow)
{
	static		TCHAR szAppName[] = TEXT("Checker1");
	HWND		hwnd;
	MSG			msg;
	WNDCLASS	wndClass;		//The window Class

	wndClass.style = CS_HREDRAW | CS_VREDRAW;
	wndClass.lpfnWndProc = WndProc;// assign the window procedure to windows class.
	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;

	//Register the Window Class to the Windows System. 
	if (!RegisterClass(&wndClass))
	{
		MessageBox(NULL, TEXT("This program require Windows NT!"),
			szAppName, MB_ICONERROR);
		return 0;
	}

	//This function will generate an WM_CREATE message.
	hwnd = CreateWindow(szAppName,		//Window class name
		TEXT("Checker1 Mouse Hit-Test Demo"),		//Window caption
		WS_OVERLAPPEDWINDOW,			//Window Style
		CW_USEDEFAULT,					//initial x position
		CW_USEDEFAULT,					//initial y position
		CW_USEDEFAULT,					//initial x size
		CW_USEDEFAULT,					//initial y size
		NULL,							//parent window handle
		NULL,							//window menu handle
		hInstance,						//program instance handle
		NULL);							//creation parameters

	ShowWindow(hwnd, iCmdShow);
	UpdateWindow(hwnd);	//This function will generate a WM_PAINT message.

						/* The message loop for this program.
						if received the WM_QUIT message, the function will return 0.*/
	while (GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	return msg.wParam;

}

//define the Window Procedure WndProc
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static BOOL		fState[DIVISIONS][DIVISIONS];
	static int		cxBlock, cyBlock;
	HDC				hdc;
	int				x, y;
	PAINTSTRUCT		ps;
	RECT			rect;

	switch (message) //get the message
	{
	case WM_SIZE:
		cxBlock = LOWORD(lParam) / DIVISIONS;
		cyBlock = HIWORD(lParam) / DIVISIONS;
		return 0;

	case WM_LBUTTONDOWN:
		x = LOWORD(lParam) / cxBlock;
		y = HIWORD(lParam) / cyBlock;

		if (x < DIVISIONS && y < DIVISIONS)
		{
			fState[x][y] ^= 1;

			rect.left = x * cxBlock;
			rect.top = y * cyBlock;
			rect.right = (x + 1) * cxBlock;
			rect.bottom = (y + 1) * cyBlock;

			InvalidateRect(hwnd, &rect, FALSE);
		}
		else
			MessageBeep(0);
		return 0;

	case WM_PAINT:
		hdc = BeginPaint(hwnd, &ps);
		
		for (x = 0; x < DIVISIONS; x++)
			for (y = 0; y < DIVISIONS; y++)
			{
				Rectangle(hdc, x * cxBlock, y * cyBlock,
						 (x + 1) * cxBlock, (y + 1) * cyBlock);

				if (fState[x][y])
				{
					MoveToEx(hdc, x * cxBlock, y * cyBlock, NULL);
					LineTo	(hdc, (x + 1) * cxBlock, (y + 1) * cyBlock);
					MoveToEx(hdc, x * cxBlock, (y + 1) * cyBlock, NULL);
					LineTo	(hdc, (x + 1) * cxBlock, y * cyBlock);
				}
			}
		EndPaint(hwnd, &ps);
		return 0;
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	}
	return  DefWindowProc(hwnd, message, wParam, lParam);
}

运行结果,用户点击屏幕上相应的区块,会绘制一个×, 再次点击×会消失。



7.4.3 使用键盘模仿鼠标操作

增加显示计数器

ShowCursor(TRUE);

减少显示计数器

ShowCursor(FALSE);

获取鼠标指针位置

GetCursorPos(&pt); //用户没有安装鼠标但windows仍然保留鼠标指针的位置,可用此函数获得

也可以设置鼠标指针的位置

SetCursorPos(x, y);

坐标都是屏幕坐标


7.4.4 在Checker中增加键盘接口

#include <windows.h>

#define DIVISIONS	5

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); //window procedure.

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
	PSTR szCmdLine, int iCmdShow)
{
	static		TCHAR szAppName[] = TEXT("Checker2");
	HWND		hwnd;
	MSG			msg;
	WNDCLASS	wndClass;		//The window Class

	wndClass.style = CS_HREDRAW | CS_VREDRAW;
	wndClass.lpfnWndProc = WndProc;// assign the window procedure to windows class.
	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;

	//Register the Window Class to the Windows System. 
	if (!RegisterClass(&wndClass))
	{
		MessageBox(NULL, TEXT("This program require Windows NT!"),
			szAppName, MB_ICONERROR);
		return 0;
	}

	//This function will generate an WM_CREATE message.
	hwnd = CreateWindow(szAppName,		//Window class name
		TEXT("Checker2 Mouse Hit-Test Demo"),		//Window caption
		WS_OVERLAPPEDWINDOW,			//Window Style
		CW_USEDEFAULT,					//initial x position
		CW_USEDEFAULT,					//initial y position
		CW_USEDEFAULT,					//initial x size
		CW_USEDEFAULT,					//initial y size
		NULL,							//parent window handle
		NULL,							//window menu handle
		hInstance,						//program instance handle
		NULL);							//creation parameters

	ShowWindow(hwnd, iCmdShow);
	UpdateWindow(hwnd);	//This function will generate a WM_PAINT message.

						/* The message loop for this program.
						if received the WM_QUIT message, the function will return 0.*/
	while (GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	return msg.wParam;

}

//define the Window Procedure WndProc
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static BOOL		fState[DIVISIONS][DIVISIONS];
	static int		cxBlock, cyBlock;
	HDC				hdc;
	int				x, y;
	PAINTSTRUCT		ps;
	POINT			point;
	RECT			rect;

	switch (message) //get the message
	{
	case WM_SIZE:
		cxBlock = LOWORD(lParam) / DIVISIONS;
		cyBlock = HIWORD(lParam) / DIVISIONS;
		return 0;

	case WM_SETFOCUS:
		ShowCursor(TRUE);
		return 0;

	case WM_KILLFOCUS:
		ShowCursor(FALSE);
		return 0;

	case WM_KEYDOWN:
		GetCursorPos(&point);
		ScreenToClient(hwnd, &point);

		x = max(0, min(DIVISIONS - 1, point.x / cxBlock));
		y = max(0, min(DIVISIONS - 1, point.y / cyBlock));

		switch (wParam)
		{
		case VK_UP:
			y--;
			break;

		case VK_DOWN:
			y++;
			break;

		case VK_LEFT:
			x--;
			break;

		case VK_RIGHT:
			x++;
			break;

		case VK_HOME:
			x = y = 0;
			break;

		case VK_END:
			x = y = DIVISIONS - 1;
			break;

		case VK_RETURN:
		case VK_SPACE:
			SendMessage(hwnd, WM_LBUTTONDOWN, MK_LBUTTON,
					MAKELONG(x * cxBlock, y * cyBlock));
			break;
		}
		x = (x + DIVISIONS) % DIVISIONS;
		y = (y + DIVISIONS) % DIVISIONS;

		point.x = x * cxBlock + cxBlock / 2;
		point.y = y * cyBlock + cyBlock / 2;

		ClientToScreen(hwnd, &point);
		SetCursorPos(point.x, point.y);
		return 0;

	case WM_LBUTTONDOWN:
		x = LOWORD(lParam) / cxBlock;
		y = HIWORD(lParam) / cyBlock;

		if (x < DIVISIONS && y < DIVISIONS)
		{
			fState[x][y] ^= 1;

			rect.left = x * cxBlock;
			rect.top = y * cyBlock;
			rect.right = (x + 1) * cxBlock;
			rect.bottom = (y + 1) * cyBlock;

			InvalidateRect(hwnd, &rect, FALSE);
		}
		else
			MessageBeep(0);
		return 0;

	case WM_PAINT:
		hdc = BeginPaint(hwnd, &ps);
		
		for (x = 0; x < DIVISIONS; x++)
			for (y = 0; y < DIVISIONS; y++)
			{
				Rectangle(hdc, x * cxBlock, y * cyBlock,
						 (x + 1) * cxBlock, (y + 1) * cyBlock);

				if (fState[x][y])
				{
					MoveToEx(hdc, x * cxBlock, y * cyBlock, NULL);
					LineTo	(hdc, (x + 1) * cxBlock, (y + 1) * cyBlock);
					MoveToEx(hdc, x * cxBlock, (y + 1) * cyBlock, NULL);
					LineTo	(hdc, (x + 1) * cxBlock, y * cyBlock);
				}
			}
		EndPaint(hwnd, &ps);
		return 0;
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	}
	return  DefWindowProc(hwnd, message, wParam, lParam);
}

现在可以使用方向键来移动鼠标,并且通过按space或者enter来模拟鼠标左键



7.4.5 在击中测试中使用子窗口

利用子窗口 来绘制屏幕上的各种小区域,每个小区域有自己的击中测试。子窗口将整个客户区划分成几个更小的矩形区域。每个子窗口都有属于自己的句柄,窗口过程和客户区,并处理自己的鼠标消息。lParam的坐标是子窗口客户区左上角的。而不是父窗口的客户区。如果子窗口使用不同的窗口类,每个子窗口都会有自己的窗口过程。


7.4.6 CHECKER程序中的子窗口

#include <windows.h>

#define DIVISIONS	5
TCHAR szChildClass[] = TEXT("Checker3_Child");

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); //window procedure.
LRESULT CALLBACK ChildWndProc(HWND, UINT, WPARAM, LPARAM); //sub window procedure.

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
	PSTR szCmdLine, int iCmdShow)
{
	static		TCHAR szAppName[] = TEXT("Checker3");
	HWND		hwnd;
	MSG			msg;
	WNDCLASS	wndClass;		//The window Class

	wndClass.style			= CS_HREDRAW | CS_VREDRAW;
	wndClass.lpfnWndProc	= WndProc;// assign the window procedure to windows class.
	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;

	//Register the Window Class to the Windows System. 
	if (!RegisterClass(&wndClass))
	{
		MessageBox(NULL, TEXT("This program require Windows NT!"),
			szAppName, MB_ICONERROR);
		return 0;
	}

	//we create a sub windows use the new wndclass.
	wndClass.lpfnWndProc	= ChildWndProc;
	wndClass.cbWndExtra		= sizeof(long);
	wndClass.hIcon			= NULL;
	wndClass.lpszClassName	= szChildClass;

	//Register the sub class
	RegisterClass(&wndClass);

	//This function will generate an WM_CREATE message.
	hwnd = CreateWindow(szAppName,		//Window class name
		TEXT("Checker3 Mouse Hit-Test Demo"),		//Window caption
		WS_OVERLAPPEDWINDOW,			//Window Style
		CW_USEDEFAULT,					//initial x position
		CW_USEDEFAULT,					//initial y position
		CW_USEDEFAULT,					//initial x size
		CW_USEDEFAULT,					//initial y size
		NULL,							//parent window handle
		NULL,							//window menu handle
		hInstance,						//program instance handle
		NULL);							//creation parameters

	ShowWindow(hwnd, iCmdShow);
	UpdateWindow(hwnd);	//This function will generate a WM_PAINT message.

						/* The message loop for this program.
						if received the WM_QUIT message, the function will return 0.*/
	while (GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	return msg.wParam;

}

//define the Window Procedure WndProc
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static HWND hwndChild[DIVISIONS][DIVISIONS]; //The array to store the window handle of the sub window.
	int		cxBlock, cyBlock, x, y;

	switch (message) //get the message
	{
	case WM_CREATE:
		for (x = 0; x < DIVISIONS; x++)
			for (y = 0; y < DIVISIONS; y++)
				hwndChild[x][y] = CreateWindow(szChildClass, NULL,
					WS_CHILDWINDOW | WS_VISIBLE,
					0, 0, 0, 0,
					hwnd, (HMENU)(y << 8 | x),
					(HINSTANCE)GetWindowLong(hwnd, GWL_HINSTANCE),
					NULL);
		return 0;

	case WM_SIZE:
		cxBlock = LOWORD(lParam) / DIVISIONS;
		cyBlock = HIWORD(lParam) / DIVISIONS;

		for (x = 0; x < DIVISIONS; x++)
			for (y = 0; y < DIVISIONS; y++)
				MoveWindow(hwndChild[x][y], x * cxBlock, y * cyBlock,
					cxBlock, cyBlock, TRUE);
		return 0;

	case WM_LBUTTONDOWN:
		MessageBeep(0);
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	}
	return  DefWindowProc(hwnd, message, wParam, lParam);
}

LRESULT CALLBACK ChildWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	HDC			hdc;
	PAINTSTRUCT	ps;
	RECT		rect;

	switch (message)
	{
	case WM_CREATE:
		SetWindowLong(hwnd, 0, 0); //on/off flag;
		return 0;

	case WM_LBUTTONDOWN:
		SetWindowLong(hwnd, 0, 1 ^ GetWindowLong(hwnd, 0));
		InvalidateRect(hwnd, NULL, FALSE);
		return 0;

	case WM_PAINT:
		hdc = BeginPaint(hwnd, &ps);

		GetClientRect(hwnd, &rect);
		Rectangle(hdc, 0, 0, rect.right, rect.bottom);

		if (GetWindowLong(hwnd, 0))
		{
			MoveToEx (hdc, 0,			0, NULL);
			LineTo	 (hdc, rect.right,	rect.bottom);
			MoveToEx (hdc, 0,			rect.bottom, NULL);
			LineTo   (hdc, rect.right,	0);
		}

		EndPaint(hwnd, &ps);
		return 0;
	}
	return DefWindowProc(hwnd, message, wParam, lParam);
}

使用子窗口来代替之前的各个矩形区域,省去了坐标的计算。每个窗口有自己的窗口函数来处理各种消息。


在调试该程序的时候,我不小心把主窗口类的名字填成和子窗口类一样了,之后程序创建了1+25个“主”窗口,造成了假死状态。调查了好久才发现这样的低级错误。囧

在创建带有子窗口的程序时,特别要小心不要把窗口类的名字和子窗口类的名字搞混了。


wndClass.lpszClassName




对于子窗口类和主窗口类 有4个字段不同

lpfnWndProc  子窗口函数

cbWndExtra  字段被设定为4个字节 sizeof(long) 通知windows在内部结构中给基于这个窗口类的每个窗口预留4个字节的额外空间。用户可以利用这些空间为每个窗口保持不同信息

hIcon 字段设置为NULL, 子窗口不需要图标

pszClassName 子窗口类的名称,!!!!!非常重要,不要和主窗口类搞混

下表是创建主窗口和子窗口调用参数的对照表 CreateWindow


(HMENU)(y<<8 | x) 子窗口的ID,用来标识子窗口的数值。在处理对话框的子窗口控件时,子ID显得更加重要。

(HINSTANCE)GetWindowLong(hwnd, GWL_HINSTANCE); 获得父窗口的hInstance参数


使用MoveWindow 移动每个子窗口的位置


7.4.7 子窗口和键盘

#include <windows.h>

#define DIVISIONS	5
TCHAR szChildClass[] = TEXT("Checker3_Child");
int idFocus = 0;		//It is used to store the sub window ID.

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); //window procedure.
LRESULT CALLBACK ChildWndProc(HWND, UINT, WPARAM, LPARAM); //sub window procedure.

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
	PSTR szCmdLine, int iCmdShow)
{
	static		TCHAR szAppName[] = TEXT("Checker3");
	HWND		hwnd;
	MSG			msg;
	WNDCLASS	wndClass;		//The window Class

	wndClass.style			= CS_HREDRAW | CS_VREDRAW;
	wndClass.lpfnWndProc	= WndProc;// assign the window procedure to windows class.
	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;

	//Register the Window Class to the Windows System. 
	if (!RegisterClass(&wndClass))
	{
		MessageBox(NULL, TEXT("This program require Windows NT!"),
			szAppName, MB_ICONERROR);
		return 0;
	}

	//we create a sub windows use the new wndclass.
	wndClass.lpfnWndProc	= ChildWndProc;
	wndClass.cbWndExtra		= sizeof(long);
	wndClass.hIcon			= NULL;
	wndClass.lpszClassName	= szChildClass;

	//Register the sub class
	RegisterClass(&wndClass);

	//This function will generate an WM_CREATE message.
	hwnd = CreateWindow(szAppName,		//Window class name
		TEXT("Checker3 Mouse Hit-Test Demo"),		//Window caption
		WS_OVERLAPPEDWINDOW,			//Window Style
		CW_USEDEFAULT,					//initial x position
		CW_USEDEFAULT,					//initial y position
		CW_USEDEFAULT,					//initial x size
		CW_USEDEFAULT,					//initial y size
		NULL,							//parent window handle
		NULL,							//window menu handle
		hInstance,						//program instance handle
		NULL);							//creation parameters

	ShowWindow(hwnd, iCmdShow);
	UpdateWindow(hwnd);	//This function will generate a WM_PAINT message.

						/* The message loop for this program.
						if received the WM_QUIT message, the function will return 0.*/
	while (GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	return msg.wParam;

}

//define the Window Procedure WndProc
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static HWND hwndChild[DIVISIONS][DIVISIONS]; //The array to store the window handle of the sub window.
	int		cxBlock, cyBlock, x, y;

	switch (message) //get the message
	{
	case WM_CREATE:
		for (x = 0; x < DIVISIONS; x++)
			for (y = 0; y < DIVISIONS; y++)
				hwndChild[x][y] = CreateWindow(szChildClass, NULL,
					WS_CHILDWINDOW | WS_VISIBLE,
					0, 0, 0, 0,
					hwnd, (HMENU)(y << 8 | x),
					(HINSTANCE)GetWindowLong(hwnd, GWL_HINSTANCE),
					NULL);
		return 0;

	case WM_SIZE:
		cxBlock = LOWORD(lParam) / DIVISIONS;
		cyBlock = HIWORD(lParam) / DIVISIONS;

		for (x = 0; x < DIVISIONS; x++)
			for (y = 0; y < DIVISIONS; y++)
				MoveWindow(hwndChild[x][y], x * cxBlock, y * cyBlock,
					cxBlock, cyBlock, TRUE);
		return 0;

	case WM_LBUTTONDOWN:
		MessageBeep(0);
		return 0;

	case WM_SETFOCUS:
		//On set-focus message, set focus to child window
		SetFocus(GetDlgItem(hwnd, idFocus));
		return 0;

	case WM_KEYDOWN:
		x = idFocus & 0xFF;
		y = idFocus >> 8;

		switch (wParam)
		{
		case VK_UP:		y--;					break;
		case VK_DOWN:	y++;					break;
		case VK_LEFT:	x--;					break;
		case VK_RIGHT:	x++;					break;
		case VK_HOME:	x = y = 0;				break;
		case VK_END:	x = y = DIVISIONS - 1;	break;
		default:		return 0;
		}

		x = (x + DIVISIONS) % DIVISIONS;
		y = (y + DIVISIONS) % DIVISIONS;

		idFocus = y << 8 | x;
		SetFocus(GetDlgItem(hwnd, idFocus));	//set the focus to the sub window.
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	}
	return  DefWindowProc(hwnd, message, wParam, lParam);
}

LRESULT CALLBACK ChildWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	HDC			hdc;
	PAINTSTRUCT	ps;
	RECT		rect;

	switch (message)
	{
	case WM_CREATE:
		SetWindowLong(hwnd, 0, 0); //on/off flag;
		return 0;

	case WM_KEYDOWN:
		//send most key processes to the parent window
		if (wParam != VK_RETURN && wParam != VK_SPACE)
		{
			SendMessage(GetParent(hwnd), message, wParam, lParam);
			return 0;
		}
		//For return and space, fall through to goggle the squre
	case WM_LBUTTONDOWN:
		SetWindowLong(hwnd, 0, 1 ^ GetWindowLong(hwnd, 0));
		SetFocus(hwnd);
		InvalidateRect(hwnd, NULL, FALSE);
		return 0;
		//For focus messages, invalidate the window for repaint

	case WM_SETFOCUS:
		idFocus = GetWindowLong(hwnd, GWL_ID);

		//fall through

	case WM_KILLFOCUS:
		InvalidateRect(hwnd, NULL, TRUE);
		return 0;

	case WM_PAINT:
		hdc = BeginPaint(hwnd, &ps);

		GetClientRect(hwnd, &rect);
		Rectangle(hdc, 0, 0, rect.right, rect.bottom);

		if (GetWindowLong(hwnd, 0))
		{
			MoveToEx (hdc, 0,			0, NULL);
			LineTo	 (hdc, rect.right,	rect.bottom);
			MoveToEx (hdc, 0,			rect.bottom, NULL);
			LineTo   (hdc, rect.right,	0);
		}

		// Draw the "focus" rectangle
		if (hwnd == GetFocus())
		{
			rect.left += rect.right / 10;
			rect.right -= rect.left;
			rect.top += rect.bottom / 10;
			rect.bottom -= rect.top;

			SelectObject(hdc, GetStockObject(NULL_BRUSH));
			SelectObject(hdc, CreatePen(PS_DASH, 0, 0));
			Rectangle(hdc, rect.left, rect.top, rect.right, rect.bottom);
			DeleteObject(SelectObject(hdc, GetStockObject(BLACK_PEN)));
		}

		EndPaint(hwnd, &ps);
		return 0;
	}
	return DefWindowProc(hwnd, message, wParam, lParam);
}
运行结果如下

idChild = GetWindowLong(hwndChild, GWL_ID); 获得子窗口ID

或者这样

idChild = GetDlgCtrlID(hwnChild);


知道子窗口的ID还能获得子窗口的句柄

hwndChild = GetDlgItem(hwnd, idChild);


在整个程序失去焦点以后重新获得焦点的时候,默认不会自动聚焦子窗口。所以要添加主窗口的聚焦响应让其自动聚焦到子窗口上。

子窗口在接收到鼠标左键消息的时候会自动响应聚焦


7.5 捕获鼠标

7.5.1 设计一个矩形


#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); //window procedure.


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
	PSTR szCmdLine, int iCmdShow)
{
	static		TCHAR szAppName[] = TEXT("BlockOut1");
	HWND		hwnd;
	MSG			msg;
	WNDCLASS	wndClass;		//The window Class

	wndClass.style			= CS_HREDRAW | CS_VREDRAW;
	wndClass.lpfnWndProc	= WndProc;// assign the window procedure to windows class.
	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;

	//Register the Window Class to the Windows System. 
	if (!RegisterClass(&wndClass))
	{
		MessageBox(NULL, TEXT("This program require Windows NT!"),
			szAppName, MB_ICONERROR);
		return 0;
	}

	//This function will generate an WM_CREATE message.
	hwnd = CreateWindow(szAppName,		//Window class name
		TEXT("Mouser Button Demo"),		//Window caption
		WS_OVERLAPPEDWINDOW,			//Window Style
		CW_USEDEFAULT,					//initial x position
		CW_USEDEFAULT,					//initial y position
		CW_USEDEFAULT,					//initial x size
		CW_USEDEFAULT,					//initial y size
		NULL,							//parent window handle
		NULL,							//window menu handle
		hInstance,						//program instance handle
		NULL);							//creation parameters

	ShowWindow(hwnd, iCmdShow);
	UpdateWindow(hwnd);	//This function will generate a WM_PAINT message.

						/* The message loop for this program.
						if received the WM_QUIT message, the function will return 0.*/
	while (GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	return msg.wParam;

}

void DrawBoxOutLine(HWND hwnd, POINT ptBeg, POINT ptEnd)
{
	HDC hdc;

	hdc = GetDC(hwnd);
	//Pixel is the inverse of the screen color. 
	//So we could erase the drawing the previous time.
	SetROP2(hdc, R2_NOT); 
	SelectObject(hdc, GetStockObject(NULL_BRUSH));
	Rectangle(hdc, ptBeg.x, ptBeg.y, ptEnd.x, ptEnd.y);

	ReleaseDC(hwnd, hdc);
}

//define the Window Procedure WndProc
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static BOOL		fBlocking, fValidBox;
	static POINT	ptBeg, ptEnd, ptBoxBeg, ptBoxEnd;
	HDC				hdc;
	PAINTSTRUCT		ps;

	switch (message) //get the message
	{
	case WM_LBUTTONDOWN:
		ptBeg.x = ptEnd.x = LOWORD(lParam);
		ptBeg.y = ptEnd.y = HIWORD(lParam);
		
		DrawBoxOutLine(hwnd, ptBeg, ptEnd);

		SetCursor(LoadCursor(NULL, IDC_CROSS));
		fBlocking = TRUE;
		return 0;

	case WM_MOUSEMOVE:
		if (fBlocking)
		{
			SetCursor(LoadCursor(NULL, IDC_CROSS));

			DrawBoxOutLine(hwnd, ptBeg, ptEnd);

			ptEnd.x = LOWORD(lParam);
			ptEnd.y = HIWORD(lParam);

			DrawBoxOutLine(hwnd, ptBeg, ptEnd);
		}
		return 0;

	case WM_LBUTTONUP:
		if (fBlocking)
		{
			DrawBoxOutLine(hwnd, ptBeg, ptEnd);

			ptBoxBeg = ptBeg;
			ptBoxEnd.x = LOWORD(lParam);
			ptBoxEnd.y = HIWORD(lParam);

			SetCursor(LoadCursor(NULL, IDC_ARROW));
			fBlocking = FALSE;
			fValidBox = TRUE;

			InvalidateRect(hwnd, NULL, TRUE);
		}
		return 0;

	case WM_CHAR:
		if (fBlocking & (wParam == TEXT('\\x1B'))) //i.e. Escape
		{
			DrawBoxOutLine(hwnd, ptBeg, ptEnd);

			SetCursor(LoadCursor(NULL, IDC_ARROW));

			fBlocking = FALSE;
		}
		return 0;

	case WM_PAINT:
		hdc = BeginPaint(hwnd, &ps);

		if (fValidBox)
		{
			SelectObject(hdc, GetStockObject(BLACK_BRUSH));
			Rectangle(hdc, ptBoxBeg.x, ptBoxBeg.y,
				ptBoxEnd.x, ptBoxEnd.y);
		}

		if (fBlocking)
		{
			SetROP2(hdc, R2_NOT);
			SelectObject(hdc, GetStockObject(NULL_BRUSH));
			Rectangle(hdc, ptBeg.x, ptBeg.y, ptEnd.x, ptEnd.y);
		}

		EndPaint(hwnd, &ps);
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	}
	return  DefWindowProc(hwnd, message, wParam, lParam);
}


一个类似画图的简单程序

但是该程序有问题,一旦用户在鼠标按下的过程中将鼠标移出客户区,再移回窗口。程序会以为鼠标仍然在按下的状态。

可以做一些修改

将WM_MOUSEMOVE 消息的代码该一下,在鼠标回客户区以后就会自动判断绘图完成了

	case WM_MOUSEMOVE:
		if (fBlocking)
		{
			if (wParam & MK_LBUTTON)
			{
				SetCursor(LoadCursor(NULL, IDC_CROSS));

				DrawBoxOutLine(hwnd, ptBeg, ptEnd);

				ptEnd.x = LOWORD(lParam);
				ptEnd.y = HIWORD(lParam);

				DrawBoxOutLine(hwnd, ptBeg, ptEnd);

			}
			else
			{
				SendMessage(hwnd, WM_LBUTTONUP, wParam, lParam);
			}

		}
		return 0;



7.5.2 捕获解决方案

SetCapture(hwnd); //鼠标捕获器

之后windows会将所有鼠标消息都发给HWND窗口。鼠标消息总是以客户区消息形式出现,即使鼠标位于非客户区。

如果想释放捕获鼠标就使用

ReleaseCapture();

一切恢复正常

为了防止混乱应在在鼠标在客户区按下时才捕获鼠标,当释放按钮时,应该同时停止捕获。


7.5.3 Blockout2 程序

使用了鼠标捕获这样在按下左键的同时鼠标移出客户区仍然能够捕获鼠标消息

#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); //window procedure.


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
	PSTR szCmdLine, int iCmdShow)
{
	static		TCHAR szAppName[] = TEXT("BlockOut2");
	HWND		hwnd;
	MSG			msg;
	WNDCLASS	wndClass;		//The window Class

	wndClass.style			= CS_HREDRAW | CS_VREDRAW;
	wndClass.lpfnWndProc	= WndProc;// assign the window procedure to windows class.
	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;

	//Register the Window Class to the Windows System. 
	if (!RegisterClass(&wndClass))
	{
		MessageBox(NULL, TEXT("This program require Windows NT!"),
			szAppName, MB_ICONERROR);
		return 0;
	}

	//This function will generate an WM_CREATE message.
	hwnd = CreateWindow(szAppName,		//Window class name
		TEXT("Mouser Button & Capture Demo"),		//Window caption
		WS_OVERLAPPEDWINDOW,			//Window Style
		CW_USEDEFAULT,					//initial x position
		CW_USEDEFAULT,					//initial y position
		CW_USEDEFAULT,					//initial x size
		CW_USEDEFAULT,					//initial y size
		NULL,							//parent window handle
		NULL,							//window menu handle
		hInstance,						//program instance handle
		NULL);							//creation parameters

	ShowWindow(hwnd, iCmdShow);
	UpdateWindow(hwnd);	//This function will generate a WM_PAINT message.

						/* The message loop for this program.
						if received the WM_QUIT message, the function will return 0.*/
	while (GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	return msg.wParam;

}

void DrawBoxOutLine(HWND hwnd, POINT ptBeg, POINT ptEnd)
{
	HDC hdc;

	hdc = GetDC(hwnd);
	//Pixel is the inverse of the screen color. 
	//So we could erase the drawing the previous time.
	SetROP2(hdc, R2_NOT); 
	SelectObject(hdc, GetStockObject(NULL_BRUSH));
	Rectangle(hdc, ptBeg.x, ptBeg.y, ptEnd.x, ptEnd.y);

	ReleaseDC(hwnd, hdc);
}

//define the Window Procedure WndProc
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static BOOL		fBlocking, fValidBox;
	static POINT	ptBeg, ptEnd, ptBoxBeg, ptBoxEnd;
	HDC				hdc;
	PAINTSTRUCT		ps;

	switch (message) //get the message
	{
	case WM_LBUTTONDOWN:
		ptBeg.x = ptEnd.x = LOWORD(lParam);
		ptBeg.y = ptEnd.y = HIWORD(lParam);
		
		DrawBoxOutLine(hwnd, ptBeg, ptEnd);

		SetCapture(hwnd);
		SetCursor(LoadCursor(NULL, IDC_CROSS));
		fBlocking = TRUE;
		return 0;

	case WM_MOUSEMOVE:
		if (fBlocking)
		{
			SetCursor(LoadCursor(NULL, IDC_CROSS));

			DrawBoxOutLine(hwnd, ptBeg, ptEnd);

			ptEnd.x = LOWORD(lParam);
			ptEnd.y = HIWORD(lParam);

			DrawBoxOutLine(hwnd, ptBeg, ptEnd);
		
		}
		return 0;

	case WM_LBUTTONUP:
		if (fBlocking)
		{
			DrawBoxOutLine(hwnd, ptBeg, ptEnd);

			ptBoxBeg = ptBeg;
			ptBoxEnd.x = LOWORD(lParam);
			ptBoxEnd.y = HIWORD(lParam);

			ReleaseCapture();
			SetCursor(LoadCursor(NULL, IDC_ARROW));
			fBlocking = FALSE;
			fValidBox = TRUE;

			InvalidateRect(hwnd, NULL, TRUE);
		}
		return 0;

	case WM_CHAR:
		if (fBlocking & (wParam == TEXT('\\x1B'))) //i.e. Escape
		{
			DrawBoxOutLine(hwnd, ptBeg, ptEnd);
			ReleaseCapture();
			SetCursor(LoadCursor(NULL, IDC_ARROW));

			fBlocking = FALSE;
		}
		return 0;

	case WM_PAINT:
		hdc = BeginPaint(hwnd, &ps);

		if (fValidBox)
		{
			SelectObject(hdc, GetStockObject(BLACK_BRUSH));
			Rectangle(hdc, ptBoxBeg.x, ptBoxBeg.y,
				ptBoxEnd.x, ptBoxEnd.y);
		}

		if (fBlocking)
		{
			SetROP2(hdc, R2_NOT);
			SelectObject(hdc, GetStockObject(NULL_BRUSH));
			Rectangle(hdc, ptBeg.x, ptBeg.y, ptEnd.x, ptEnd.y);
		}

		EndPaint(hwnd, &ps);
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	}
	return  DefWindowProc(hwnd, message, wParam, lParam);
}



7.6 鼠标的滚轮

对SYSMETS程序的进一步改进,增加了鼠标的各种操作和滚轮支持


sysmets.h 文件代码

#define NUMLINES ((int)(sizeof sysmetrics / sizeof sysmetrics[0]))

struct
{
	int		iIndex;
	TCHAR*	szLabel;
	TCHAR*	szDesc;
}

sysmetrics[] =
{
	SM_CXSCREEN, TEXT("SM_CXSCREEN"),
	TEXT("Screen width in pixels"),
	SM_CYSCREEN, TEXT("SM_CYSCREEN"),
	TEXT("Screen height in pixels"),
	SM_CXVSCROLL, TEXT("SM_CXVSCROLL"),
	TEXT("Vertical scroll width"),
	SM_CYHSCROLL, TEXT("SM_CYHSCROLL"),
	TEXT("Horizontal scroll height"),
	SM_CYCAPTION, TEXT("SM_CYCAPTION"),
	TEXT("Caption bar height"),
	SM_CXBORDER, TEXT("SM_CXBORDER"),
	TEXT("Window border width"),
	SM_CYBORDER, TEXT("SM_CYBORDER"),
	TEXT("Window border height"),
	SM_CXFIXEDFRAME, TEXT("SM_CXFIXEDFRAME"),
	TEXT("Dialog window frame width"),
	SM_CYFIXEDFRAME, TEXT("SM_CYFIXEDFRAME"),
	TEXT("Dialog window frame height"),
	SM_CYVTHUMB, TEXT("SM_CYVTHUMB"),
	TEXT("Vertical scroll thumb height"),
	SM_CXHTHUMB, TEXT("SM_CXHTHUMB"),
	TEXT("Horizontal scroll thumb width"),
	SM_CXICON, TEXT("SM_CXICON"),
	TEXT("Icon width"),
	SM_CYICON, TEXT("SM_CYICON"),
	TEXT("Icon height"),
	SM_CXCURSOR, TEXT("SM_CXCURSOR"),
	TEXT("Cursor width"),
	SM_CYCURSOR, TEXT("SM_CYCURSOR"),
	TEXT("Cursor height"),
	SM_CYMENU, TEXT("SM_CYMENU"),
	TEXT("Menu bar height"),
	SM_CXFULLSCREEN, TEXT("SM_CXFULLSCREEN"),
	TEXT("Full screen client area width"),
	SM_CYFULLSCREEN, TEXT("SM_CYFULLSCREEN"),
	TEXT("Full screen client area height"),
	SM_CYKANJIWINDOW, TEXT("SM_CYKANJIWINDOW"),
	TEXT("Kanji window height"),
	SM_MOUSEPRESENT, TEXT("SM_MOUSEPRESENT"),
	TEXT("Mouse present flag"),
	SM_CYVSCROLL, TEXT("SM_CYVSCROLL"),
	TEXT("Vertical scroll arrow height"),
	SM_CXHSCROLL, TEXT("SM_CXHSCROLL"),
	TEXT("Horizontal scroll arrow width"),
	SM_DEBUG, TEXT("SM_DEBUG"),
	TEXT("Debug version flag"),
	SM_SWAPBUTTON, TEXT("SM_SWAPBUTTON"),
	TEXT("Mouse buttons swapped flag"),
	SM_CXMIN, TEXT("SM_CXMIN"),
	TEXT("Minimum window width"),
	SM_CYMIN, TEXT("SM_CYMIN"),
	TEXT("Minimum window height"),
	SM_CXSIZE, TEXT("SM_CXSIZE"),
	TEXT("Min/Max/Close button width"),
	SM_CYSIZE, TEXT("SM_CYSIZE"),
	TEXT("Min/Max/Close button height"),
	SM_CXSIZEFRAME, TEXT("SM_CXSIZEFRAME"),
	TEXT("Window sizing frame width"),
	SM_CYSIZEFRAME, TEXT("SM_CYSIZEFRAME"),
	TEXT("Window sizing frame height"),
	SM_CXMINTRACK, TEXT("SM_CXMINTRACK"),
	TEXT("Minimum window tracking width"),
	SM_CYMINTRACK, TEXT("SM_CYMINTRACK"),
	TEXT("Minimum window tracking height"),
	SM_CXDOUBLECLK, TEXT("SM_CXDOUBLECLK"),
	TEXT("Double click x tolerance"),
	SM_CYDOUBLECLK, TEXT("SM_CYDOUBLECLK"),
	TEXT("Double click y tolerance"),
	SM_CXICONSPACING, TEXT("SM_CXICONSPACING"),
	TEXT("Horizontal icon spacing"),
	SM_CYICONSPACING, TEXT("SM_CYICONSPACING"),
	TEXT("Vertical icon spacing"),
	SM_MENUDROPALIGNMENT, TEXT("SM_MENUDROPALIGNMENT"),
	TEXT("Left or right menu drop"),
	SM_PENWINDOWS, TEXT("SM_PENWINDOWS"),
	TEXT("Pen extensions installed"),
	SM_DBCSENABLED, TEXT("SM_DBCSENABLED"),
	TEXT("Double-Byte Char Set enabled"),
	SM_CMOUSEBUTTONS, TEXT("SM_CMOUSEBUTTONS"),
	TEXT("Number of mouse buttons"),
	SM_SECURE, TEXT("SM_SECURE"),
	TEXT("Security present flag"),
	SM_CXEDGE, TEXT("SM_CXEDGE"),
	TEXT("3-D border width"),
	SM_CYEDGE, TEXT("SM_CYEDGE"),
	TEXT("3-D border height"),
	SM_CXMINSPACING, TEXT("SM_CXMINSPACING"),
	TEXT("Minimized window spacing width"),
	SM_CYMINSPACING, TEXT("SM_CYMINSPACING"),
	TEXT("Minimized window spacing height"),
	SM_CXSMICON, TEXT("SM_CXSMICON"),
	TEXT("Small icon width"),
	SM_CYSMICON, TEXT("SM_CYSMICON"),
	TEXT("Small icon height"),
	SM_CYSMCAPTION, TEXT("SM_CYSMCAPTION"),
	TEXT("Small caption height"),
	SM_CXSMSIZE, TEXT("SM_CXSMSIZE"),
	TEXT("Small caption button width"),
	SM_CYSMSIZE, TEXT("SM_CYSMSIZE"),
	TEXT("Small caption button height"),
	SM_CXMENUSIZE, TEXT("SM_CXMENUSIZE"),
	TEXT("Menu bar button width"),
	SM_CYMENUSIZE, TEXT("SM_CYMENUSIZE"),
	TEXT("Menu bar button height"),
	SM_ARRANGE, TEXT("SM_ARRANGE"),
	TEXT("How minimized windows arranged"),
	SM_CXMINIMIZED, TEXT("SM_CXMINIMIZED"),
	TEXT("Minimized window width"),
	SM_CYMINIMIZED, TEXT("SM_CYMINIMIZED"),
	TEXT("Minimized window height"),
	SM_CXMAXTRACK, TEXT("SM_CXMAXTRACK"),
	TEXT("Maximum draggable width"),
	SM_CYMAXTRACK, TEXT("SM_CYMAXTRACK"),
	TEXT("Maximum draggable height"),
	SM_CXMAXIMIZED, TEXT("SM_CXMAXIMIZED"),
	TEXT("Width of maximized window"),
	SM_CYMAXIMIZED, TEXT("SM_CYMAXIMIZED"),
	TEXT("Height of maximized window"),
	SM_NETWORK, TEXT("SM_NETWORK"),
	TEXT("Network present flag"),
	SM_CLEANBOOT, TEXT("SM_CLEANBOOT"),
	TEXT("How system was booted"),
	SM_CXDRAG, TEXT("SM_CXDRAG"),
	TEXT("Avoid drag x tolerance"),
	SM_CYDRAG, TEXT("SM_CYDRAG"),
	TEXT("Avoid drag y tolerance"),
	SM_SHOWSOUNDS, TEXT("SM_SHOWSOUNDS"),
	TEXT("Present sounds visually"),
	SM_CXMENUCHECK, TEXT("SM_CXMENUCHECK"),
	TEXT("Menu check-mark width"),
	SM_CYMENUCHECK, TEXT("SM_CYMENUCHECK"),
	TEXT("Menu check-mark height"),
	SM_SLOWMACHINE, TEXT("SM_SLOWMACHINE"),
	TEXT("Slow processor flag"),
	SM_MIDEASTENABLED, TEXT("SM_MIDEASTENABLED"),
	TEXT("Hebrew and Arabic enabled flag"),
	SM_MOUSEWHEELPRESENT, TEXT("SM_MOUSEWHEELPRESENT"),
	TEXT("Mouse wheel present flag"),
	SM_XVIRTUALSCREEN, TEXT("SM_XVIRTUALSCREEN"),
	TEXT("Virtual screen x origin"),
	SM_YVIRTUALSCREEN, TEXT("SM_YVIRTUALSCREEN"),
	TEXT("Virtual screen y origin"),
	SM_CXVIRTUALSCREEN, TEXT("SM_CXVIRTUALSCREEN"),
	TEXT("Virtual screen width"),
	SM_CYVIRTUALSCREEN, TEXT("SM_CYVIRTUALSCREEN"),
	TEXT("Virtual screen height"),
	SM_CMONITORS, TEXT("SM_CMONITORS"),
	TEXT("Number of monitors"),
	SM_SAMEDISPLAYFORMAT, TEXT("SM_SAMEDISPLAYFORMAT"),
	TEXT("Same color format flag")
};

sysmets.cpp

#include <windows.h>
#include "sysmets.h"


LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); //window procedure.

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
	PSTR szCmdLine, int iCmdShow)
{
	static		TCHAR szAppName[] = TEXT("SysMets");
	HWND		hwnd;
	MSG			msg;
	WNDCLASS	wndClass;		//The window Class

	wndClass.style			= CS_HREDRAW | CS_VREDRAW;
	wndClass.lpfnWndProc	= WndProc;// assign the window procedure to windows class.
	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;

	//Register the Window Class to the Windows System. 
	if (!RegisterClass(&wndClass))
	{
		MessageBox(NULL, TEXT("This program require Windows NT!"),
			szAppName, MB_ICONERROR);
		return 0;
	}

	//This function will generate an WM_CREATE message.
	hwnd = CreateWindow(szAppName,		//Window class name
		TEXT("Get System Metrics"),		//Window caption
		WS_OVERLAPPEDWINDOW | WS_VSCROLL | WS_HSCROLL,			//Window Style
		CW_USEDEFAULT,					//initial x position
		CW_USEDEFAULT,					//initial y position
		CW_USEDEFAULT,					//initial x size
		CW_USEDEFAULT,					//initial y size
		NULL,							//parent window handle
		NULL,							//window menu handle
		hInstance,						//program instance handle
		NULL);							//creation parameters

	ShowWindow(hwnd, iCmdShow);
	UpdateWindow(hwnd);	//This function will generate a WM_PAINT message.

						/* The message loop for this program.
						if received the WM_QUIT message, the function will return 0.*/
	while (GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	return msg.wParam;

}

//define the Window Procedure WndProc
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static int	cxChar, cxCaps, cyChar, cxClient, cyClient, iMaxWidth;
	static int	iDeltaPerLine, iAccumDelta; //for mouse wheel logic
	HDC			hdc;
	int			i, x, y, iVertPos, iHorzPos, iPaintBeg, iPaintEnd;
	PAINTSTRUCT	ps;
	SCROLLINFO	si;
	TCHAR		szBuffer[10];
	TEXTMETRIC	tm;
	ULONG		ulScrollLines;		//for mouse wheel logic

	switch (message) //get the message
	{
	case WM_CREATE:
		hdc = GetDC(hwnd);

		GetTextMetrics(hdc, &tm);
		cxChar = tm.tmAveCharWidth;
		cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2)* cxChar / 2;
		cyChar = tm.tmHeight + tm.tmExternalLeading;
		ReleaseDC(hwnd, hdc);

		//Save the width of the three colums
		iMaxWidth = 40 * cxChar + 22 * cxCaps;

		//fall through for mouse wheel information

	case WM_SETTINGCHANGE:
		SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &ulScrollLines, 0);

		//ulScrollLines usually equals 3 or 0 (for no scrolling)
		//WHEEL_DELTA equals 120, so iDeltaPerLine will be 40

		if (ulScrollLines)
			iDeltaPerLine = WHEEL_DELTA / ulScrollLines;
		else
			iDeltaPerLine = 0;
		return 0;

	case WM_SIZE:
		cxClient = LOWORD(lParam);
		cyClient = HIWORD(lParam);

		//Set vertical scroll bar range and page size

		si.cbSize = sizeof(si);
		si.fMask = SIF_RANGE | SIF_PAGE;
		si.nMin = 0;
		si.nMax = NUMLINES - 1;
		si.nPage = cyClient / cyChar;
		SetScrollInfo(hwnd, SB_VERT, &si, TRUE);

		//Set the horizontal scroll bar range and page size
		si.cbSize = sizeof(si);
		si.fMask = SIF_RANGE | SIF_PAGE;
		si.nMin = 0;
		si.nMax = 2 + iMaxWidth / cxChar;
		si.nPage = cxClient / cxChar;
		SetScrollInfo(hwnd, SB_HORZ, &si, TRUE);
		return 0;

	case WM_VSCROLL:
		//Get all the vertical scroll bar information

		si.cbSize = sizeof(si);
		si.fMask = SIF_ALL;
		GetScrollInfo(hwnd, SB_VERT, &si);
		//Save the position for comparison later on

		iVertPos = si.nPos;

		switch (LOWORD(wParam))
		{
		case SB_TOP:
			si.nPos = si.nMin;
			break;
		case SB_BOTTOM:
			si.nPos = si.nMax;
			break;
		case SB_LINEUP:
			si.nPos -= 1;
			break;
		case SB_LINEDOWN:
			si.nPos += 1;
			break;
		case SB_PAGEUP:
			si.nPos -= si.nPage;
			break;
		case SB_PAGEDOWN:
			si.nPos += si.nPage;
			break;
		case SB_THUMBPOSITION:
			si.nPos = si.nTrackPos;
			break;
		default:
			break;
		}

		//Set the position and then retrieve it. Due to adjustments
		//By Windows it may not be the same as the value set.

		si.fMask = SIF_POS;
		SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
		GetScrollInfo(hwnd, SB_VERT, &si);
		// if the position has changed, scroll the window and update it
		if (si.nPos != iVertPos)
		{
			ScrollWindow(hwnd, 0, cyChar*(iVertPos - si.nPos),
				NULL, NULL);
			UpdateWindow(hwnd); //instead of the invalidateRect() it will update the rect immediately.
		}
		return 0;

	case WM_HSCROLL:
		// Get all the horizental scroll bar information

		si.cbSize = sizeof(si);
		si.fMask = SIF_ALL;
		//Save the position for comparison later on

		GetScrollInfo(hwnd, SB_HORZ, &si);
		iHorzPos = si.nPos;
		switch (LOWORD(wParam))
		{
		case SB_LINELEFT:
			si.nPos -= 1;
			break;
		case SB_LINERIGHT:
			si.nPos += 1;
			break;
		case SB_PAGELEFT:
			si.nPos -= si.nPage;
			break;
		case SB_PAGERIGHT:
			si.nPos += si.nPage;
			break;
		case SB_THUMBPOSITION:
			si.nPos = si.nTrackPos;
			break;
		default:
			break;
		}

		//Set the Position and then retrieve it. Due to adjustments
		//by Windows it may not be the same as the value set.
		si.fMask = SIF_POS;
		SetScrollInfo(hwnd, SB_HORZ, &si, TRUE);
		GetScrollInfo(hwnd, SB_HORZ, &si);

		//If the position has been changed, scroll the window
		if (si.nPos != iHorzPos)
		{
			ScrollWindow(hwnd, cxChar* (iHorzPos - si.nPos), 0,
				NULL, NULL);
		}
		return 0;
	case WM_KEYDOWN:
		switch(wParam)
		{
		case VK_HOME:
			SendMessage(hwnd, WM_VSCROLL, SB_TOP, 0);
			break;
		case VK_END:
			SendMessage(hwnd, WM_VSCROLL, SB_BOTTOM, 0);
			break;
		case VK_PRIOR:
			SendMessage(hwnd, WM_VSCROLL, SB_PAGEUP, 0);
			break;
		case VK_NEXT:
			SendMessage(hwnd, WM_VSCROLL, SB_PAGEDOWN, 0);
			break;
		case VK_UP:
			SendMessage(hwnd, WM_VSCROLL, SB_LINEUP, 0);
			break;
		case VK_DOWN:
			SendMessage(hwnd, WM_VSCROLL, SB_LINEDOWN, 0);
			break;
		case VK_LEFT:
			SendMessage(hwnd, WM_HSCROLL, SB_PAGEUP, 0);
			break;
		case VK_RIGHT:
			SendMessage(hwnd, WM_HSCROLL, SB_PAGEDOWN, 0);
			break;
		}
		return 0;

	case WM_MOUSEWHEEL:
		if (iDeltaPerLine == 0)
			break;
		iAccumDelta += (short)HIWORD(wParam); //120 or -120

		while (iAccumDelta >= iDeltaPerLine)
		{
			SendMessage(hwnd, WM_VSCROLL, SB_LINEUP, 0);
			iAccumDelta -= iDeltaPerLine;
		}

		while (iAccumDelta <= -iDeltaPerLine)
		{
			SendMessage(hwnd, WM_VSCROLL, SB_LINEDOWN, 0);
			iAccumDelta += iDeltaPerLine;
		}
		return 0;

	case WM_PAINT:
		hdc = BeginPaint(hwnd, &ps);
		//Get the vertical scroll bar position

		si.cbSize = sizeof(si);;
		si.fMask = SIF_POS;
		GetScrollInfo(hwnd, SB_VERT, &si);
		iVertPos = si.nPos;

		//Get horizontal scroll bar position
		GetScrollInfo(hwnd, SB_HORZ, &si);
		iHorzPos = si.nPos;

		//Find painting limits

		iPaintBeg = max(0, iVertPos + ps.rcPaint.top / cyChar);
		iPaintEnd = min(NUMLINES - 1,
			iVertPos + ps.rcPaint.bottom / cyChar);

		for (i = iPaintBeg; i <= iPaintEnd; ++i)
		{
			x = cxChar * (1 - iHorzPos);
			y = cyChar * (i - iVertPos);
			TextOut(hdc, x, y,
				sysmetrics[i].szLabel,
				lstrlen(sysmetrics[i].szLabel));

			TextOut(hdc, x + 22 * cxCaps, y,
				sysmetrics[i].szDesc,
				lstrlen(sysmetrics[i].szDesc));

			SetTextAlign(hdc, TA_RIGHT | TA_TOP);

			TextOut(hdc, x + 22 * cxCaps + 40 * cxChar, y, szBuffer,
				wsprintf(szBuffer, TEXT("%5d"),
					GetSystemMetrics(sysmetrics[i].iIndex)));
			SetTextAlign(hdc, TA_LEFT | TA_TOP);
		}
		EndPaint(hwnd, &ps);
		return 0;
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	}
	return  DefWindowProc(hwnd, message, wParam, lParam);
}


可以使用鼠标滚轮来操作垂直滚动条

滚动鼠标产生WM_MOUSEWHEEL消息

wParam的地位是一些按键消息

wParam的高位时一个增量


在WM_CREATE 和WM_SETTINGCHANGE 消息调用了参数为 SPI_GETWHEELSCROLLLINES的SystemParametersInfo函数

表示每个增量值能滚动多少行

以上是关于《Windows程序设计》读书笔七 鼠标的主要内容,如果未能解决你的问题,请参考以下文章

HTML代码片段

《C#高级编程》读书笔记(十九):Windows服务

《windows程序设计》鼠标消息(12)

插入雷蛇鼠标 2 分钟,即可获得 Windows 10 管理员权限?

《Windows程序设计》读书笔六 键盘

《C#高级编程》读书笔记(二十):核心XAML