第七章 鼠标
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
}
}
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);