7.1 鼠标的基础知识
功能 | 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_LBUTTONDBLCLK |
②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隐藏或显示鼠标指针。
【效果图】

/*------------------------------------------------------------
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 [只按下Ctrl键]; }else{ [Shift和Ctrl都没被按下]; } | case WM_LBUTTONDOWN: //未按Shift时,直接处理左键 if (!(wParam & MK_SHIFT)) [这里处理左键]; return 0; } //注意,这里没有return。 //用户按下了鼠标左键+Shift,执行以下代码,模拟右键。 case WM_RBUTTONDOWN: [这里处理右键]; return 0; ★注意:双键鼠标也是可以正常处理的。单键鼠标可以通过按住鼠标左键+Shift,来模拟鼠标右键的功能。 |
注意:GetKeyState可以通过VK_LBUTTON、VK_RBUTTON、VK_SHIFT、VK_CONTROL等获取鼠标当前状态。但鼠标或键盘未被按下的键不能使用GetKeyState。只有被按下时才会报告其按下状态。(while(GetKeyState(VK_LBUTTON)>=0))是错误的代码。
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处理后,产生下一条消息。 | Not used | HTSYSMENU | |
2、默认处理后,再产生下条消息 | WM_NCLBUTTONDBLCLK | ||
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);
}
7.4.3 在击中测试中使用子窗口
(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 | |
垂直位置 | ||
宽度 | ||
高度 | ||
父窗口句柄 | hwnd | |
菜单句柄/子ID | (HMENU)(y<<8 | x)(唯一标识子窗口的数值) | |
实例句柄 | hInstance | (HINSTANCE)GetWindowLong(hwnd,GWL_INSTANCE) |
额外参数 |
【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);
}
7.4.4 子窗口和键盘
(1)单击子窗口时,得到输入焦点的是父窗口,而不是子窗口。(记这点很重要)。鼠标但键盘消息只给被具有焦点的窗口接收。(父子窗口如何共享键盘消息呢?如,当按空格或回车键时,接收消息的是子窗口,要反转选中状态。按方向键时,接收消息的是父窗口,要将焦点移到另一个子窗口上?方法:父窗口直接将焦点转移给子窗口,让子窗口接管键盘消息,子窗体先处理回车键和空格键等键盘消息。但遇到方向键时,将消息通过SendMessage返给父窗口,注意这个过程不必将焦点还给父窗口。整个过程,即父窗口将键盘消息让给子窗口去接管,子窗口处理自己兴趣的消息,把不处理的消息重新分发给父窗口)
(2)父窗口中操作子窗口
操作 | 函数 |
获取子窗口ID的方法 | idChild = GetWindowLong(hwndChild,GWL_ID); idChild = GetDlgCtrlID(hwndChild) |
获取子窗口的句柄 | hwndChild = GetDlgItem(hwndParent,idChild) |
/*------------------------------------------------------------
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);
}