Windows 程序设计--- 鼠标(1)

最近学习了一下Windows 程序设计这本书中的鼠标章节,现在总结一下学习的内容。大家可以一起参考。我将分为两个部分进行介绍。有关鼠标更具体的学习可以参考微软官方文档鼠标输入

1.鼠标的基础知识

1.0 定义:鼠标带有一个或者多个按钮的定位设备。区别于键盘:输入和管理文本功能。鼠标主要用来绘制和处理图形对象。我们常用的鼠标是两个按钮+一个滚轮,其实滚轮中间也有一个按钮。
1.1 操作系统中与鼠标有关的API函数:
①获取电脑是否连接鼠标:

fMouse = GetSystemMetrics(SM_MOUSEPRESENT)
// fMouse:连接了鼠标时为TRUE,否则为0。

②确定鼠标按钮数

cButtons = GetSystemMetrics(SM_CMOUSEBUTTONS)
// 如果没有鼠标则cButtons 为0。

③设置鼠标的其他信息

BOOL
WINAPI
SystemParametersInfoW(
    _In_ UINT uiAction,
    _In_ UINT uiParam,
    _Pre_maybenull_ _Post_valid_ PVOID pvParam,
    _In_ UINT fWinIni);

例如获取鼠标滚轮信息:

SystemParametersInfo(SPI_GETWHEELSCROLLLINES,0,&uScrollLine,0);

1.2 一些和鼠标有关的术语:
①鼠标指针:位图格式的小图标。具有一个单像素精度的"热点"(hot spot),它在显示设备上指示了一个精确位置。我们平时知道的鼠标位置,说的就是热点的位置了。
Windows 在Winuser.h中为我们提供了几种常用的指针。最常用的是斜向箭头,称为IDC_ARROW。然后还有IDC_CROSS(➕ 状)、IDC_WAIT(⌛状,表示等待)等,具体查看Winuser.h头文件。我们的创建窗口类时,默认指定为IDC_ARROW。

ws.hCursor = LoadCursor(NULL, IDC_ARROW);

②单击:按下鼠标按钮,然后松开。
③双击:连续两次快速按下按钮并且松开。
④拖动:保持按下按钮,并且移动鼠标。

2.客户区鼠标消息

2.0 介绍:与键盘不同的是键盘只会把消息发送到具有指定焦点的窗口上。但是鼠标不同,当鼠标被单击或者经过窗口时,即使该窗口是非活动窗口或不带输入焦点,窗口过程依然会处理鼠标消息。
Windows 定义了21种鼠标消息。但是其中11种消息与客户区无关,称为"非客户区消息"。Windows应用程序经常忽略这类消息。
2.1 客户区鼠标消息:

按钮按下释放第二次按下按钮
左键WM_LBUTTONDOWNWM_LBUTTONUPWM_LBUTTONDBLCLK
中键WM_MBUTTONDOWNWM_MBUTTONUPWM_MBUTTONDBLCLK
右键WM_RBUTTONDOWNWM_RBUTTONUPWM_RBUTTONDBLCLK

注意:只有当窗口定义成了接受鼠标消息时,窗口过程才能处理DBLICK。
对于所有的客户区消息来说:
参数LParam:包含了鼠标的位置信息。LOWORD(LParam):低位字表示x坐标,HIWORD(LParam)表示y坐标。
参数wParam:表示鼠标按钮、SHIFT键和CTRL键的状态。在WinUser.h头文件中定义了位掩码来测试参数wParam。前缀MK_代表"鼠标键"(Mouse Key).

/*
 * Key State Masks for Mouse Messages
 */
#define MK_LBUTTON          0x0001
#define MK_RBUTTON          0x0002
#define MK_SHIFT            0x0004
#define MK_CONTROL          0x0008
#define MK_MBUTTON          0x0010
#if(_WIN32_WINNT >= 0x0500)
#define MK_XBUTTON1         0x0020
#define MK_XBUTTON2         0x0040
#endif /* _WIN32_WINNT >= 0x0500 */

例如可以这样使用判断当出现WM_LBUTTONDOWN消息时,是否同时按下SHIFT键:

case WM_LBUTTONDOWN:
	{
	if(wParam & MK_SHIFT)
			// Do Something...
		    MessageBox(hWnd, TEXT("鼠标左键 + SHIFT键 "), TEXT("提示"), MB_OK);
		break;
	}

Windows规定:如果在非活动窗口内按下鼠标左键,Windows会将这个窗口变成活动窗口。并且向该窗口发送WM_LBUTTONDOWN消息。窗口过程再收到WM_LBUTTONDOWN消息时,就能保证安全的保证该窗口是活动窗口。另外即使窗口没有WM_LBUTTONDOWN消息时,也能够处理WM_LBUTTONUP消息。
下面是一个简单例子:

#include <windows.h>

#define MAXPOINTS 1000

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 ("Program requires Windows NT!"), 
                      szAppName, MB_ICONERROR) ;
          return 0 ;
     }
     
     hwnd = CreateWindow (szAppName, TEXT ("Connect-the-Points Mouse Demo"),
                          WS_OVERLAPPEDWINDOW,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          NULL, NULL, hInstance, NULL) ;
     
     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 POINT pt[MAXPOINTS] ;
     static int   iCount ;
     HDC          hdc ;
     int          i, j ;
     PAINTSTRUCT  ps ;

     switch (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) ;
}

2.2 处理Shift键:
在上面的程序中在处理WM_MOUSEMOVE消息时有如下处理: if (wParam & MK_LBUTTON && iCount < 1000)判断是否按下鼠标左键。如果在处理鼠标消息时,需要用判断Shift键和Ctrl键的状态时,可以这样处理:

	if(wParam & MK_SHIFT)
	{
		if (wParam & MK_CONTROL)
		{
			 // 按下Shift + Ctrl 键
			MessageBeep(0);
			MessageBox(hWnd,TEXT("Ctrl+Shift 键被按下"),TEXT("提示"),MB_OK);
		}
		else if (wParam & MK_SHIFT)
		{
			 // 按下Shift键
			MessageBeep(0);
			MessageBox(hWnd, TEXT("Shift 键被按下"), TEXT("提示"), MB_OK);
		}
	}
	else 
	{
		if (wParam & MK_CONTROL)
		{
			MessageBox(hWnd, TEXT("Ctrl 键被按下"), TEXT("提示"), MB_OK);
		}
		else
		{
			MessageBox(hWnd, TEXT("Ctrl 和 shift键都没有被按下"), TEXT("提示"), MB_OK);
	    }
	}

利用虚拟键VK_LBUTTON、VK_RBUTTON、VK_MBUTTON、VK_SHIFT、VK_CONTROL和GetKeyState()函数也能够返回鼠标按钮的状态。

2.3 鼠标双击:
要做到鼠标双击需要达到两个条件:
①物理位置上十分靠近。
②必须发生在特定的时间间隔内。(双击速度)
前面提到窗口过程要处理鼠标双击消息时,窗口类需要包含CS_DBLCLKS风格:

ws.style = CS_HREDRAW | CS_VREDRAW| CS_DBLCLKS;

如果窗口过程没有包含CS_DBLCLKS风格,则窗口过程处理鼠标双击消息顺序如下:
WM_LBUTTONDOWN
WM_LBUTTONUP
WM_LBUTTONDOWN
WM_LBUTTONUP
可以使用GetMessageTime函数来获取WM_LBUTTONDOWN消息之间的时间间隔。

包含CS_DBLCLKS风格时,处理顺序如下:
WM_LBUTTONDOWN
WM_LBUTTONUP
WM_LBUTTONDBLCLK
WM_LBUTTONUP

3.非客户区鼠标消息

到目前为止我们说的10种鼠标消息都是发生在窗口客户区内的移动或者单击。如果鼠标位于窗口客户区以外的区域,鼠标就会产生非客户区消息。窗口的非封闭区域包括其边框、菜单栏、标题栏、滚动条、窗口菜单、最小化按钮和最大化按钮。具体可以参考微软官方文档鼠标输入

系统一般不需要用户处理非客户区消息。只需要将消息发送给DefWindowProc函数,由系统来处理。这与键盘中的WM_SYSKEYDOWN、WM_SYSKEYUP、WM_CHAR等消息相似。非客户区消息与客户区消息基本上是对应的。表识符包含了"NC"字样,表示"非客户"(NonClient).
非客户区消息如下:

按钮按下释放第二次按下按钮
左键WM_NCLBUTTONDOWNWM_NCLBUTTONUPWM_NCLBUTTONDBLCLK
中键WM_NCMBUTTONDOWNWM_NCMBUTTONUPWM_NCMBUTTONDBLCLK
右键WM_NCRBUTTONDOWNWM_NCRBUTTONUPWM_NCRBUTTONDBLCLK

非客户区消息的wParam参数与客户区的有一些不同。wParam参数表示非客户区鼠标移动或者单击的位置。它的值被设定成表示一个HT为首的标识符,其中HT表示"击中测试"。
LParam则相同都是表示坐标。

3.1 击中测试消息
我们来学习最后一个鼠标消息叫做WM_NCHITTEST。这个消息的优先级高于其他所有的客户区和非客户区消息。Windows 应用程序通常会把这个消息发送给DefWindowProc函数,然后系统利用WM_NCHITTEST产生其他所有和鼠标位置有关的消息。DefWindowProc函数返回一个可用于鼠标参数的wParam值。这个值可以是任何非客户区消息的wParam值。也可以是如下的值。
HTBORDER
18
在没有大小调整边框的窗口的边框中。
HTBOTTOM
15
在可调整大小的窗口的下水平边框 (用户可以单击鼠标以垂直调整窗口大小) 。
HTBOTTOMLEFT
16
在可调整大小的窗口边框的左下角, (用户可以单击鼠标以) 对角线调整窗口大小。
HTBOTTOMRIGHT
17
在可调整大小的窗口边框的右下角 (用户可以单击鼠标以) 对角线调整窗口大小。
HTCAPTION
2
在标题栏中。
HTCLIENT
1
在工作区中。
HTCLOSE
20
在 “关闭 ”按钮中。
HTERROR
-2
在屏幕背景或窗口之间的分隔线上, (与 HTNOWHERE 相同,只不过 DefWindowProc 函数会发出系统蜂鸣声以指示) 错误。
HTGROWBOX
4
在大小框中, (与 HTSIZE) 相同。
HTHELP
21
在 “帮助 ”按钮中。
HTHSCROLL
6
在水平滚动条中。
HTLEFT
10
在可调整大小的窗口的左边框 (用户可以单击鼠标以水平调整窗口大小) 。
HTMENU
5
在菜单中。
HTMAXBUTTON
9
在 “最大化 ”按钮中。
HTMINBUTTON
8
在 “最小化 ”按钮中。
HTNOWHERE
0
在屏幕背景上或窗口之间的分隔线上。
HTREDUCE
8
在 “最小化 ”按钮中。
HTRIGHT
11
在可调整大小的窗口的右边框 (用户可以单击鼠标以水平调整窗口大小) 。
HTSIZE
4
在大小框中 (与 HTGROWBOX) 相同。
HTSYSMENU
3
在窗口菜单或子窗口的 “关闭 ”按钮中。
HTTOP
12
在窗口的上水平边框中。
HTTOPLEFT
13
在窗口边框的左上角。
HTTOPRIGHT
14
在窗口边框的右上角。
HTTRANSPARENT
-1
在同一线程 (当前由另一个窗口覆盖的窗口中,消息将发送到同一线程中的基础窗口,直到其中一个窗口返回不是 HTTRANSPARENT) 的代码。
HTVSCROLL
7
在垂直滚动条中。
HTZOOM
9
在 “最大化 ”按钮中。

欢迎大家一起学习和交流。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值