第七章 鼠标
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函数
表示每个增量值能滚动多少行