功能 |
GetSysMetrics的 参数 |
返回值 |
判断是否安装鼠标 |
SM_MOUSEPRESENT |
WINNT以上:TRUE己安装。0未安装 Windows98:总是TRUE。 |
鼠标按钮个数 |
SM_CMOUSEBUTTONS |
WINNT以上:0为未安装鼠标 Windows98:有安装鼠标返回按钮个数,没安装鼠标返回2。 |
鼠标按钮是否被切换 |
SM_SWAPBUTTON |
|
设定鼠标其他参数(如双击):用SystemParametrsInfo函数获取或设定
(2)鼠标指针:wndClass.hCursor = LoadCursor(NULL,IDC_ARROW)。
7.2 客户区的鼠标消息
(1)客户区鼠标消息(10个)
事件 |
消息 |
鼠标经过 |
WM_MOUSEMOVE |
左键 |
WM_LBUTTONDOWN、WM_LBUTTONUP、WM_LBUTTONDBLCLK(双击) |
中键 |
WM_MBUTTONDOWN、WM_MBUTTONUP、WM_MBUTTONDBLCLK(第二次按下) |
右键 |
WM_RBUTTONDOWN、WM_RBUTTONUP、WM_RBUTTONDBLCLK |
说明:①双击事件的处理只有窗口类定义接收时,才起作用:即
差异 |
没包含双击事件 |
包含双击事件 |
wndClass.style |
CS_HREDRAW| CS_VREDRAW |
CS_HREDRAW | CS_VREDRAW|CS_DBLCLKS |
消息顺序 |
WM_LBUTTONDOWN WM_LBUTTONUP WM_LBUTTONDOWN WM_LBUTTONUP |
WM_LBUTTONDOWN WM_LBUTTONUP WM_LBUTTONDBLCLK WM_LBUTTONUP |
②MBUTTON只对三键鼠标起作用。单键鼠标只处理LBUTTON消息。
③WM_MOUSEMOVE:消息的个数取决于鼠标硬件和窗口处理的速度。Windows不会为
经过的每个像素位置都产生一个WM_MOUSEMOVE消息。
④WM_LBUTTONDOWN和WM_LBUTTONUP并不一定会成对出现。可能只收到其中的一种
消息。
⑤键盘消息发送到当前具有输入焦点的窗口。鼠标消息发送给被单击的窗或鼠标经
过的窗口,即使该窗口处于非活动或不带输入焦点。两种例外:A、“捕获鼠标”
时,鼠标离开窗口仍继续接收鼠标消息;B、模式对话框处于活动状态时,其他
程序不能接收鼠标消息。
(2)鼠标消息的参数
wParam(按钮、Shift、Ctrl键状态) |
lParam(鼠标位置) |
MK_LBUTTON(0x0001):按下左键 MK_RBUTTON(0x0002):按下右键 MK_SHIFT (0x0004):按下Shift MK_CONTROL(0x0008):按下Ctrl MK_MBUTTON(0x0010):按下中键 ★或运算结果 |
x = LOWORD(lParam); y = HIWORD(lParam); |
eg.当WM_LBUTTONDOWN时,是否同时按下Shift键:wParam & MK_SHIFT。
7.2.1 简单的鼠标处理示例
【Connect程序】
(1)操作方法:
①第一种——在客户区按下左键,略微移动,再松开左键
②第二种——在客户区按下左键,快速移动鼠标。
(2)己知的问题:在客户区外释放左键,Connnect不会连接这些点,因为没收到WM_LBUTTONUP消息。
(3)该程序较耗时,绘制时,鼠标变沙漏形,处理WM_PAINT完后回原来的状态。用SetCursor来切换鼠标。ShowCursor隐藏或显示鼠标指针。
![](https://images0.cnblogs.com/blog2015/772759/201507/191648315639602.png)
/*------------------------------------------------------------ CONNECT.C -- Connect-the-Dots Mouse Demo program (c) Charles Petzold, 1998 ------------------------------------------------------------*/ #define MAXPOINTS 1000 #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("Connect"); 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, // 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); while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; static POINT pt[MAXPOINTS]; static int iCount = 0; switch (message) { case WM_LBUTTONDOWN: iCount = 0; InvalidateRect(hwnd, NULL, TRUE); //每次按下左键时,先消屏 return 0; case WM_MOUSEMOVE: //验证移动时,并不是每个像素都产生WM_MOUSEMOVE消息。 if (wParam & MK_LBUTTON && iCount<MAXPOINTS) //只有按下左键+鼠标移动时,才画点 { 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); //FALSE,不擦除背景,可以保留WM_MOUSEMOVE里画下的点。 return 0; case WM_PAINT: hdc = BeginPaint(hwnd, &ps); SetCursor(LoadCursor(NULL, IDC_WAIT)); ShowCursor(TRUE); //鼠标指针显示为等待状态 for (int i = 0; i < iCount; i++) //每一点而其他各点的连线 for (int 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键
处理过程依赖Shift和Ctrl键的逻辑处理 |
单键鼠标模拟双键鼠标 |
if (wParam & MK_SHIFT) //按下Shift { if (wParam & MK_CONTROL) { [按下Shift + Ctrl键]; } else{ [只按下Shift键]; } }else{ //未按Shift if (wParam & MK_CONTROL) { [只按下Ctrl键]; }else{ [Shift和Ctrl都没被按下]; } } |
case WM_LBUTTONDOWN: //未按Shift时,直接处理左键 if (!(wParam & MK_SHIFT)) { [这里处理左键]; return 0; } //注意,这里没有return。 //用户按下了鼠标左键+Shift,执行以下代码,模拟右键。 case WM_RBUTTONDOWN: [这里处理右键]; return 0;
★注意:双键鼠标也是可以正常处理的。单键鼠标可以通过按住鼠标左键+Shift,来模拟鼠标右键的功能。 |
7.3 非客户区的鼠标消息
(1)非客户区鼠标消息(11个)
事件 |
消息 |
鼠标经过 |
WM_NCMOUSEMOVE |
击中测试 |
WM_NCHITTEST |
左键 |
WM_NCLBUTTONDOWN、WM_NCLBUTTONUP、WM_NCLBUTTONDBLCLK(双击) |
中键 |
WM_NCMBUTTONDOWN、WM_NCMBUTTONUP、WM_NCMBUTTONDBLCLK(第二次按下) |
右键 |
WM_NCRBUTTONDOWN、WM_NCRBUTTONUP、WM_NCRBUTTONDBLCLK |
(2)非客户区鼠标消息的参数
wParam(窗口的哪个部位) (20多个位置,见MSDN) |
lParam(鼠标屏幕坐标) |
HTCLINET 客户区 HTNOWHERE 不在任何窗口 HTRANSPARENT 被另一个窗口覆盖 HTERROR 使函数DefWindowProc产生警示声 …… |
Pt.x =LOWORD(lParam); Pt.y =HIWORD(lParam); //屏幕坐标与客户区坐标转换 ScreenToClient(hwnd,&pt); ClientToScreen(hwnd,&pt); |
7.3.1 击中测试消息:WM_NCHITTEST
(1)消息优先级高于其他鼠标消息
(2)wParam参数如上表所示。
(3)DefWindowProc在处理WM_NCHITTEST后,如果返回HTCLIENT,则会把屏幕坐标转成客户区坐标,并产生一个客户区鼠标消息。
(4)使鼠标按钮操作失效——阻止系统向窗口发送所有客户区和非客户区鼠标消息:
case WM_NCHITTEST:return (LRESULT)HTNOWHERE;
7.3.2 消息引发的消息
(1)Windows利用WM_NCHITTEST来产生其他所有的鼠标消息。
(2)举例:双击系统图标来关闭窗口,由WM_NCHITTEST引发的消息
顺序 |
当前消息 |
wParam |
返回 |
1、双击系统图标,产生WM_NCHITTEST,由DefWindowProc处理后,产生下一条消息。 |
WM_NCHITTEST |
Not used |
HTSYSMENU |
2、默认处理后,再产生下条消息 |
WM_NCLBUTTONDBLCLK |
HTSYSMENU |
|
3、默认处理后,产生下条消息 |
WM_SYSCOMMAND |
SC_CLOSE |
|
4、DefWindowProc调用DestroyWindow处理这条消息,并产生下条消息 |
WM_CLOSE |
|
|
5、默认处理或用户处理,并销毁。 |
WM_DESTROY |
|
|
7.4 程序中的击中测试
7.4.1 一个简单的程序
(1)矩形宽度为cxBlock,高度为cyBlock
(2)WM_LBUTTONDOWN判断鼠标落在哪个矩形内
(3)如果客户区宽度和长度不能被5整除,会在左边和底部出现一个空白区。
(4)点击鼠标时产生一个无效区(矩形),并发送WM_PAINT重新绘制该无效区。
【效果图】
/*------------------------------------------------------------ CHECKER1.C -- Mouse Hit-Test demo Program No.2 (c) Charles Petzold, 1998 ------------------------------------------------------------*/ #include <windows.h> #define DIVISIONS 5 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT("Checker1"); 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, // 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); while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; RECT rect; static cxBlock, cyBlock; static BOOL fState[DIVISIONS][DIVISIONS]; int x, y; switch (message) { case WM_CREATE: return 0; 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; //即fState[x][y] =!fState[x][y]; rect.top = y*cyBlock; rect.bottom = (y + 1)*cyBlock; rect.left = x*cxBlock; rect.right = (x + 1)*cxBlock; InvalidateRect(hwnd, &rect, TRUE); //因用透明绘制,所以要清除背景 } else{ //当宽度和长度不是5个倍数时,会在左或底侧出现个空白区,让其发出错误的声音。 MessageBeep(0); } return 0; case WM_PAINT: hdc = BeginPaint(hwnd, &ps); RECT tmpRect; SelectObject(hdc, GetStockObject(NULL_BRUSH));//透明绘制 for (int x = 0; x < DIVISIONS; x++) for (int y = 0; y < DIVISIONS; y++) { rect.top = y*cyBlock; rect.bottom = (y + 1)*cyBlock; rect.left = x*cxBlock; rect.right = (x + 1)*cxBlock; IntersectRect(&tmpRect, &rect, &ps.rcPaint); if (IsRectEmpty(&tmpRect)) continue;//判断当前矩形区是否在无效区内 Rectangle(hdc, rect.left, rect.top, rect.right, rect.bottom); 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.2 使用键盘模仿鼠标操作
(1)显示或隐藏鼠标
ShowCursor(TRUE);//增加鼠标显示计数,默认0,只有在>=0时,才显示。
ShowCursor(FALSE);//减少鼠标计数,只有在<0时才隐藏。
(2)获取和设置鼠标位置
①Get、Set鼠标位置
GetCursorPos(&pt);//屏幕坐标(因为没有hwnd参数),可与客户区坐标转换
SetCursorPos(&pt);
②GetCursorPos与鼠标消息的参数lParam的差别。
A、GetCursorPos坐标并转成客户区坐标,返回的鼠标当前位置
B、lParam包含的坐标是指产生消息的那一刻的鼠标位置。
(3)在CHECKER中增加键盘接口
①用空格与Enter键模拟鼠标按钮;方向键每按一次切换到另一个矩形。
②Home键回到左上角的矩形,End键鼠标落到右下角矩形。
【Checker2程序】
/*------------------------------------------------------------ CHECKER2.C -- Mouse Hit-Test demo Program No.2 (c) Charles Petzold, 1998 ------------------------------------------------------------*/ #include <windows.h> #define DIVISIONS 5 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT("Checker2"); 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, // 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); while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; RECT rect; static cxBlock, cyBlock; static BOOL fState[DIVISIONS][DIVISIONS]; int x, y; POINT pt; switch (message) { case WM_CREATE: return 0; 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(&pt); //因wParam为虚拟键,lParam为击键的6个字段,没鼠标坐标。 ScreenToClient(hwnd, &pt); //当前选中的矩形 x = max(0, min(DIVISIONS - 1, pt.x / cxBlock)); //限定在[0-4]之间 y = max(0, min(DIVISIONS - 1, pt.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; case VK_SPACE: case VK_RETURN: SendMessage(hwnd, WM_LBUTTONDOWN, MK_LBUTTON, MAKELONG(x*cxBlock, y*cyBlock)); return 0;//也可以break,执行下面代码,将鼠标设置到当前矩形中央位置。 } x = (x + DIVISIONS) % DIVISIONS; //x原区间为[0,4],移到[5,9]区间,防止x--后出现负数区间。 y = (y + DIVISIONS) % DIVISIONS; //设置鼠标位置到矩形中央位置 pt.x = x*cxBlock + cxBlock / 2; pt.y = y*cyBlock + cyBlock / 2; ClientToScreen(hwnd, &pt); //客户区坐标转成屏幕坐标 SetCursorPos(pt.x, pt.y); return 0; case WM_LBUTTONDOWN: //单击的矩形索引 x = LOWORD(lParam) / cxBlock; y = HIWORD(lParam) / cyBlock; if (x < DIVISIONS && y < DIVISIONS) { fState[x][y] ^= 1; //即fState[x][y] =!fState[x][y]; rect.top = y*cyBlock; rect.bottom = (y + 1)*cyBlock; rect.left = x*cxBlock; rect.right = (x + 1)*cxBlock; InvalidateRect(hwnd, &rect, TRUE); //因用透明绘制,所以要清除背景 } else{ //当宽度和长度不是5个倍数时,会在左或底侧出现个空白区,让其发出错误的声音。 MessageBeep(0); } return 0; case WM_PAINT: hdc = BeginPaint(hwnd, &ps); RECT tmpRect; SelectObject(hdc, GetStockObject(NULL_BRUSH));//透明绘制 for (int x = 0; x < DIVISIONS; x++) for (int y = 0; y < DIVISIONS; y++) { rect.top = y*cyBlock; rect.bottom = (y + 1)*cyBlock; rect.left = x*cxBlock; rect.right = (x + 1)*cxBlock; IntersectRect(&tmpRect, &rect, &ps.rcPaint); if (IsRectEmpty(&tmpRect)) continue;//判断当前矩形区是否在无效区内 Rectangle(hdc, rect.left, rect.top, rect.right, rect.bottom); 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); }
(1)包含25个子窗口,两个窗口过程:WndProc和ChildWndProc
(2)子窗口类的cbWndExtra设置4个字节,用来保存该窗口绘画状态,打“X”用1表示。
(3)WndProc中创建子窗口时须调用GetWindowsLong,从注册的窗口结构中提取hInstance.
(4)hwndChild数组为每个子窗口保存了窗口句柄,MoveWindows函数中要用到。
(5)CreateWindows主窗口与子窗口参数的比较
参数 |
主窗口 |
子窗口 |
窗口类 |
“Checker3” |
“Checker3_Child” |
窗口标题 |
“Checker3…” |
NULL |
窗口风格 |
WS_OVERLAPPEDWINDOW |
WS_CHILDWINDOW|WS_VISIBLE |
水平位置 |
CW_USEDEFAULT |
0 |
垂直位置 |
CW_USEDEFAULT |
0 |
宽度 |
CW_USEDEFAULT |
0 |
高度 |
CW_USEDEFAULT |
0 |
父窗口句柄 |
NULL |
hwnd |
菜单句柄/子ID |
NULL |
(HMENU)(y<<8 | x)(唯一标识子窗口的数值) |
实例句柄 |
hInstance |
(HINSTANCE)GetWindowLong(hwnd,GWL_INSTANCE) |
额外参数 |
NULL |
NULL |
【Checker3程序】
/*------------------------------------------------------------ CHECKER3.C -- Mouse Hit-Test demo Program No.3 (c) Charles Petzold, 1998 ------------------------------------------------------------*/ #include <windows.h> #define DIVISIONS 5 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); //主窗口过程 LRESULT CALLBACK ChildWndProc(HWND, UINT, WPARAM, LPARAM); //子窗口的窗口过程 TCHAR szChildClass[] = TEXT("Checker_Child"); //须定义为全局变量,因为WinMain和WndProc中都要用到。 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT("Checker3"); 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; } //子窗口类 wndclass.lpfnWndProc = ChildWndProc; wndclass.cbWndExtra = sizeof(long); //也可以为设置,直接保存在窗体的GWL_USERDATA中 wndclass.hIcon = NULL; wndclass.lpszClassName = szChildClass; RegisterClass(&wndclass); //己经是NT了,不需要再检查了。 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); 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 HWND hwndChild[DIVISIONS][DIVISIONS]; static int cxBlock, cyBlock; switch (message) { case WM_CREATE: for (int x = 0; x < DIVISIONS; x++) for (int y = 0; y < DIVISIONS; y++) { hwndChild[x][y] = CreateWindow(szChildClass, NULL, WS_CHILD | WS_VISIBLE, //没WS_VISIBLE时,创建好窗口,要自己ShowWindow 0, 0, 10, 10, hwnd, (HMENU)(y << 8 | x), (HINSTANCE)GetWindowLong(hwnd, GWL_HINSTANCE), //从主窗体获得hInstance NULL); } return 0; case WM_SIZE: cxBlock = LOWORD(lParam) / DIVISIONS; cyBlock = HIWORD(lParam) / DIVISIONS; for (int x = 0; x < DIVISIONS; x++) for (int 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);//开/关标记,保存cbWndExtra空间 //SetWindowLong(hwnd, GWL_USERDATA, 0);//保存在USERDATA中 return 0; case WM_LBUTTONDOWN: SetWindowLong(hwnd, 0, 1 ^ GetWindowLong(hwnd, 0)); //SetWindowLong(hwnd, GWL_USERDATA, 1 ^ GetWindowLong(hwnd, GWL_USERDATA)); InvalidateRect(hwnd, NULL, FALSE); //因为矩形默认是填充白色的,会覆盖之前绘画,所以用False return 0; case WM_PAINT: hdc = BeginPaint(hwnd, &ps); GetClientRect(hwnd, &rect); Rectangle(hdc, rect.left, rect.top, rect.right, rect.bottom); if (GetWindowLong(hwnd, 0)) //if (GetWindowLong(hwnd, GWL_USERDATA)) { MoveToEx(hdc, rect.left, rect.top, NULL); LineTo(hdc, rect.right, rect.bottom); MoveToEx(hdc, rect.left, rect.bottom, NULL); LineTo(hdc, rect.right, rect.top); } EndPaint(hwnd, &ps); return 0; case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProc(hwnd, message, wParam, lParam); }
(1)单击子窗口时,得到输入焦点的是父窗口,而不是子窗口。(记这点很重要)。鼠标但键盘消息只给被具有焦点的窗口接收。(父子窗口如何共享键盘消息呢?如,当按空格或回车键时,接收消息的是子窗口,要反转选中状态。按方向键时,接收消息的是父窗口,要将焦点移到另一个子窗口上?方法:父窗口直接将焦点转移给子窗口,让子窗口接管键盘消息,子窗体先处理回车键和空格键等键盘消息。但遇到方向键时,将消息通过SendMessage返给父窗口,注意这个过程不必将焦点还给父窗口。整个过程,即父窗口将键盘消息让给子窗口去接管,子窗口处理自己兴趣的消息,把不处理的消息重新分发给父窗口)
(2)父窗口中操作子窗口
操作 |
函数 |
获取子窗口ID的方法 |
idChild = GetWindowLong(hwndChild,GWL_ID); idChild = GetDlgCtrlID(hwndChild) |
获取子窗口的句柄 |
hwndChild = GetDlgItem(hwndParent,idChild) |
设置子窗口为焦点窗口 |
SetFocus(GetDlgItem(hwnd,idFocus)) |
(3)子窗口得到焦点时:WM_SETFOCUS中画虚线框表示
【Checker4】
/*------------------------------------------------------------ CHECKER4.C -- Mouse Hit-Test demo Program No.3 (c) Charles Petzold, 1998 ------------------------------------------------------------*/ #include <windows.h> #define DIVISIONS 5 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); //主窗口过程 LRESULT CALLBACK ChildWndProc(HWND, UINT, WPARAM, LPARAM); //子窗口的窗口过程 TCHAR szChildClass[] = TEXT("Checker_Child"); //须定义为全局变量,因为WinMain和WndProc中都要用到。 int idFocus; //当前选中的矩形(用子窗口ID来标识) int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT("Checker4"); 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; } //子窗口类 wndclass.lpfnWndProc = ChildWndProc; wndclass.cbWndExtra = sizeof(long); wndclass.hIcon = NULL; wndclass.lpszClassName = szChildClass; RegisterClass(&wndclass); //己经是NT了,不需要再检查了。 hwnd = CreateWindow(szAppName, // window class name TEXT("Checker4 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); 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 HWND hwndChild[DIVISIONS][DIVISIONS]; static int cxBlock, cyBlock; int x, y; switch (message) { case WM_CREATE: for (int x = 0; x < DIVISIONS; x++) for (int y = 0; y < DIVISIONS; y++) { hwndChild[x][y] = CreateWindow(szChildClass, NULL, WS_CHILD | WS_VISIBLE, //没WS_VISIBLE时,创建好窗口,要自己ShowWindow 0, 0, 10, 10, hwnd, (HMENU)(y << 8 | x), (HINSTANCE)GetWindowLong(hwnd, GWL_HINSTANCE), //从主窗体获得hInstance NULL); } return 0; case WM_SIZE: cxBlock = LOWORD(lParam) / DIVISIONS; cyBlock = HIWORD(lParam) / DIVISIONS; for (int x = 0; x < DIVISIONS; x++) for (int y = 0; y < DIVISIONS; y++) { MoveWindow(hwndChild[x][y], x*cxBlock, y*cyBlock, cxBlock, cyBlock, TRUE); } return 0; case WM_SETFOCUS: 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)); 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);//开/关标记,保存cbWndExtra空间 return 0; case WM_KEYDOWN: //将绝大部分的键盘消息还给父窗口处理 if (wParam != VK_RETURN && wParam != VK_SPACE) { SendMessage(GetParent(hwnd), message, wParam, lParam); return 0; } //继续执行下去,处理回车和空格键 case WM_LBUTTONDOWN: SetWindowLong(hwnd, 0, 1 ^ GetWindowLong(hwnd, 0)); SetFocus(hwnd); InvalidateRect(hwnd, NULL, FALSE); //因为矩形默认是填充白色的,会覆盖之前绘画,所以用False return 0; case WM_SETFOCUS: idFocus = GetWindowLong(hwnd, GWL_ID); //继续执行下去,用虚线框表示焦点窗口 case WM_KILLFOCUS: InvalidateRect(hwnd, NULL, TRUE); return 0; case WM_PAINT: hdc = BeginPaint(hwnd, &ps); GetClientRect(hwnd, &rect); Rectangle(hdc, rect.left, rect.top, rect.right, rect.bottom); if (GetWindowLong(hwnd, 0)) { MoveToEx(hdc, rect.left, rect.top, NULL); LineTo(hdc, rect.right, rect.bottom); MoveToEx(hdc, rect.left, rect.bottom, NULL); LineTo(hdc, rect.right, rect.top); } if (hwnd == GetFocus()) { rect.left += rect.right / 20; rect.right -= rect.left; rect.top += rect.bottom / 20; rect.bottom -= rect.top; SelectObject(hdc, GetStockObject(NULL_BRUSH)); SelectObject(hdc, CreatePen(PS_DOT, 0, 0)); Rectangle(hdc, rect.left, rect.top, rect.right, rect.bottom); DeleteObject(SelectObject(hdc, GetStockObject(BLACK_PEN))); } EndPaint(hwnd, &ps); return 0; case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProc(hwnd, message, wParam, lParam); }