www.9778.com 29

第七章 鼠标(CHECKER2)

CHECKER2程序包含一个键盘接口,内容与CHECKER1完全相同。利用←、→、↑、↓四个方向键可以在25个矩形之间移动鼠标指针。Home键把鼠标指针移动到左上角的矩形;End键使鼠标指针落到右下角的矩形。空格键和回车键都可以切换X形标记。

C语言Windows程序设计 -> 第十一天 -> 使用鼠标

 

鼠标的使用同样是通过获取Windows鼠标消息来获取用户当前的鼠标状态的。

一、鼠标的介绍
    鼠标是计算机的输入设备之一, 在图形化的操作系统上,
鼠标的使用使一些复杂的操作变得简单, 随着科技的进步,
鼠标的种类也越来越多,
按接口类型可分为串行鼠标、PS/2鼠标、总线鼠标、USB鼠标(多为光电鼠标)四种。按其工作原理及其内部结构的不同可以分为机械式,光机式和光电式。
    
    这里我们不讨论鼠标的硬件构造,
更多关于鼠标的硬件知识请自行查阅相关资料。
    
    1>. 鼠标所在的位置
        在Windows系统下, 用户移动鼠标时,
在屏幕上一般会以一个斜式的箭头来表示鼠标当前的位置,
这个箭头实际上是一个位图格式的小图标, 称为”鼠标指针”,
鼠标指针具有一个单像素精度的”热点”(hot spot), 当鼠标样式为箭头时,
这个”热点”就是鼠标箭头的顶点, 还有一些样式是”十”字样式,
这样的指针”热点”位于”十”字的中心位置,
热点在显示设备上指示了一个精确的位置。 当我们去捕获鼠标指针的位置时,
实际上是指鼠标指针的这个”热点”所在的像素单元的位置。
    
    2>. 鼠标的术语
        ①. 单击 : 按下鼠标按键, 然后松开;
        ②. 双击 : 连续快速的按下鼠标同一个按键然后松开;
        ③. 拖动 : 保持按键按下状态, 并移动鼠标。
        
        现在我们常见的三键鼠标, 三个按键分布称为左键、中键和右键,
其中左键的标识符简写为LBUTTON, 中键的标识符为MBUTTON,
右键的标识符为RBUTTON。 双键鼠标只有左键和右键, 单键鼠标只有左键。
        
    3>. 鼠标的样式
        Windows系统为鼠标提供了几种默认的鼠标样式, 如:
箭头、沙漏、十字瞄准等,
在以前学习的过程中实际上我们已经接触了使用默认的鼠标样式,
回忆这行代码:

        wndclass.hCursor = LoadCursor( NULL, IDC_ARROW ) ;

       
这样就是使用一个默认的斜式箭头作为鼠标的指针样式, 斜式箭头样式的标识符为
IDC_ARROW, 这些标识符定义在
WINUSER.H 头文件中,
此外还有以下标识符及其对应的样式:

www.9778.com 1

二、使用鼠标的简单示例
    1>. 示例一: 获取鼠标指针位置
        在这个示例中演示如何获取鼠标的位置,
先说下相关的消息标识符以及函数。
        消息标识符: WM_MOUSEMOVE
当鼠标指针在客户区内移动或鼠标指针经过客户区窗口时会得到这个消息。
        获取鼠标位置的函数: GetCursorPos 该函数的原型: BOOL
GetCursorPos(LPPOINT lpPoint) ;
        代码片段:

www.9778.com 2

 1     switch(message)
 2     {
 3     case WM_PAINT:        //处理重绘消息
 4         hdc = BeginPaint( hwnd, &ps ) ;
 5         wsprintf( szBuffer,  "屏幕坐标:(%i, %i)", pt.x, pt.y );
 6         TextOut( hdc, 10, 10, szBuffer, lstrlen(szBuffer) ) ;
 7         ScreenToClient( hwnd, &pt ) ;        //将相对于屏幕的坐标转换为相对于窗口客户区的坐标
 8         wsprintf( szBuffer,  "客户区坐标:(%i, %i)", pt.x, pt.y );
 9         TextOut( hdc, 10, 30, szBuffer, lstrlen(szBuffer) ) ;
10         EndPaint( hwnd, &ps ) ;
11         return 0 ;
12 
13     case WM_MOUSEMOVE:        //处理鼠标移动时发来的消息
14         GetCursorPos(&pt) ;
15         InvalidateRect( hwnd, NULL, TRUE ) ;
16         return 0 ;

www.9778.com 3

   
完整的示例代码:

www.9778.com 4View Code – GetCursorPosition

www.9778.com 5

 1 #include<windows.h>
 2 
 3 LRESULT CALLBACK WndProc( HWND, UINT, WPARAM, LPARAM ) ;
 4 
 5 int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow )
 6 {
 7     static TCHAR szAppName[] = TEXT( "UseMouse_Demo" ) ;
 8     HWND hwnd ;
 9     MSG msg ;
10     WNDCLASS wndclass ;
11 
12     wndclass.hInstance        = hInstance ;
13     wndclass.lpfnWndProc    = WndProc ;
14     wndclass.lpszClassName    = szAppName ;
15     wndclass.style            = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS ;
16     wndclass.hbrBackground    = (HBRUSH) GetStockObject( WHITE_BRUSH ) ;
17     wndclass.hCursor        = LoadCursor( NULL, IDC_ARROW ) ;
18     wndclass.hIcon            = LoadIcon( NULL, IDI_APPLICATION ) ;
19     wndclass.cbClsExtra        = 0 ;
20     wndclass.cbWndExtra        = 0 ;
21     wndclass.lpszMenuName    = 0 ;
22 
23     if( !RegisterClass(&wndclass) )
24     {
25         MessageBox( NULL, TEXT("错误, 无法注册窗口类."), TEXT("错误"), MB_OK ) ;
26         return 0 ;
27     }
28 
29     hwnd = CreateWindow( szAppName, TEXT("获取鼠标指针位置 - Demo"),
30         WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
31         CW_USEDEFAULT, CW_USEDEFAULT,
32         NULL, NULL, hInstance, NULL ) ;
33 
34     ShowWindow( hwnd, iCmdShow ) ;
35     UpdateWindow( hwnd ) ;
36 
37     while( GetMessage(&msg, NULL, 0, 0) )
38     {
39         TranslateMessage( &msg ) ;
40         DispatchMessage( &msg ) ;
41     }
42 
43     return msg.wParam ;
44 }
45 
46 LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )
47 {
48     HDC hdc ;
49     PAINTSTRUCT ps ;
50     static POINT pt ;
51     TCHAR szBuffer[128] ;
52 
53     switch(message)
54     {
55     case WM_PAINT:
56         hdc = BeginPaint( hwnd, &ps ) ;
57         wsprintf( szBuffer,  "屏幕坐标:(%i, %i)", pt.x, pt.y );
58         TextOut( hdc, 10, 10, szBuffer, lstrlen(szBuffer) ) ;
59         ScreenToClient( hwnd, &pt ) ;
60         wsprintf( szBuffer,  "客户区坐标:(%i, %i)", pt.x, pt.y );
61         TextOut( hdc, 10, 30, szBuffer, lstrlen(szBuffer) ) ;
62         EndPaint( hwnd, &ps ) ;
63         return 0 ;
64 
65     case WM_MOUSEMOVE:
66         GetCursorPos(&pt) ;
67         InvalidateRect( hwnd, NULL, TRUE ) ;
68         return 0 ;
69 
70     case WM_DESTROY:
71         PostQuitMessage(0) ;
72         return 0 ;
73     }
74 
75     return DefWindowProc( hwnd, message, wParam, lParam ) ;
76 }

www.9778.com 6

       
说一下整体的思路, 要即时跟踪获取鼠标在屏幕中的坐标,
首先要捕获鼠标的移动消息 WM_MOUSEMOVE,
当Windows向我们发来这个消息时就代码鼠标在进行移动,
随后我们对这个鼠标移动消息进行处理, 调用 GetCursorPos(&pt) ;
这个函数获取鼠标现在的位置,
获取到鼠标位置后为了能够立即在窗口中显示出来, 再调用 InvalidateRect( hwnd, NULL, TRUE ) ;
使整个客户区变成无效状态, 从而引发 WM_PAINT 需要重绘客户区内容的消息,
在处理重绘消息时输出刚刚获得的鼠标指针坐标位置。
        
        关于 ScreenToClient( hwnd, &pt )
;
:
          
 这个函数的功能是将屏幕坐标(相对于整个屏幕左上角的坐标)转换成相对于窗口客户区的坐标,
屏幕坐标与窗口客户区坐标的含义如图所示:

www.9778.com 7

            GetCursorPos获得的鼠标位置是屏幕坐标,
如果想知道他在窗口客户区内的相对位置就需要调用ScreenToClient函数将其转化为客户区坐标。
          此外还有一个WIndows函数是将窗口客户区坐标转成屏幕坐标的,
函数为: ClientToScreen( hwnd, &pt ) ;

          获取鼠标指针的位置还有其他的方法, 这里只是其中的一种。
    
    
    2>. 示例二: 处理鼠标左键单击事件
        鼠标左键在客户区被单击时发来的消息: WM_LBUTTONDOWN

www.9778.com 8

 1     switch(message)
 2     {
 3     case WM_PAINT:
 4         hdc = BeginPaint( hwnd, &ps ) ;
 5         EndPaint( hwnd, &ps ) ;
 6         return 0 ;
 7 
 8     case WM_LBUTTONDOWN:    //处理鼠标左键单击被按下时产生的消息
 9         x = LOWORD( lParam ) ;    //获取鼠标位置x坐标信息
10         y = HIWORD( lParam ) ;    //获取鼠标位置y坐标信息
11         wsprintf( szBuffer,  "鼠标左键被单击, 击中位置: (%i, %i)", x, y );
12         MessageBox( hwnd, szBuffer, TEXT("鼠标动作"), MB_OK ) ;
13         return 0 ;
14 
15     case WM_DESTROY:
16         PostQuitMessage(0) ;
17         return 0 ;
18     }

www.9778.com 9

   
完整的示例代码:

www.9778.com 10View Code – WM_LBUTTONDOWN

www.9778.com 11

 1 #include<windows.h>
 2 
 3 LRESULT CALLBACK WndProc( HWND, UINT, WPARAM, LPARAM ) ;
 4 
 5 int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow )
 6 {
 7     static TCHAR szAppName[] = TEXT( "UseMouse_Demo" ) ;
 8     HWND hwnd ;
 9     MSG msg ;
10     WNDCLASS wndclass ;
11 
12     wndclass.hInstance        = hInstance ;
13     wndclass.lpfnWndProc    = WndProc ;
14     wndclass.lpszClassName    = szAppName ;
15     wndclass.style            = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS ;
16     wndclass.hbrBackground    = (HBRUSH) GetStockObject( WHITE_BRUSH ) ;
17     wndclass.hCursor        = LoadCursor( NULL, IDC_ARROW ) ;
18     wndclass.hIcon            = LoadIcon( NULL, IDI_APPLICATION ) ;
19     wndclass.cbClsExtra        = 0 ;
20     wndclass.cbWndExtra        = 0 ;
21     wndclass.lpszMenuName    = 0 ;
22 
23     if( !RegisterClass(&wndclass) )
24     {
25         MessageBox( NULL, TEXT("错误, 无法注册窗口类."), TEXT("错误"), MB_OK ) ;
26         return 0 ;
27     }
28 
29     hwnd = CreateWindow( szAppName, TEXT("处理鼠标单击事件 - Demo"),
30         WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
31         CW_USEDEFAULT, CW_USEDEFAULT,
32         NULL, NULL, hInstance, NULL ) ;
33 
34     ShowWindow( hwnd, iCmdShow ) ;
35     UpdateWindow( hwnd ) ;
36 
37     while( GetMessage(&msg, NULL, 0, 0) )
38     {
39         TranslateMessage( &msg ) ;
40         DispatchMessage( &msg ) ;
41     }
42 
43     return msg.wParam ;
44 }
45 
46 LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )
47 {
48     HDC hdc ;
49     PAINTSTRUCT ps ;
50     static POINT pt ;
51     TCHAR szBuffer[128] ;
52     static int x, y ;
53 
54     switch(message)
55     {
56     case WM_PAINT:
57         hdc = BeginPaint( hwnd, &ps ) ;
58         EndPaint( hwnd, &ps ) ;
59         return 0 ;
60 
61     case WM_LBUTTONDOWN:
62         x = LOWORD( lParam ) ;
63         y = HIWORD( lParam ) ;
64         wsprintf( szBuffer,  "鼠标左键被单击, 击中位置: (%i, %i)", x, y );
65         MessageBox( hwnd, szBuffer, TEXT("鼠标动作"), MB_OK ) ;
66         return 0 ;
67 
68     case WM_DESTROY:
69         PostQuitMessage(0) ;
70         return 0 ;
71     }
72 
73     return DefWindowProc( hwnd, message, wParam, lParam ) ;
74 }

www.9778.com 12

   
这个示例演示的是当鼠标在客户区按下时弹出一个对话框,
对话框的内容是鼠标被按下时鼠标指针的位置信息, 可以看到, 这里我们没有使用
GetCursorPos 函数来获取鼠标指针的位置, 而是通过

        x = LOWORD( lParam ) ;    //获取鼠标位置x坐标信息
        y = HIWORD( lParam ) ;    //获取鼠标位置y坐标信息

   
来获取的, 参数lParam包含了鼠标指针的位置信息,
其中低位字节表示x坐标, 高位字节表示y坐标, 利用LOWORDHIWORD宏可以取得这些坐标值,
这里获取的坐标指的是相对于窗口客户区的坐标。

 

三、客户区鼠标消息
    与键盘消息不同, 在键盘消息中,
Windows只把键盘消息发送到当前具有输入焦点的窗口,
而鼠标消息无论窗口是否获取焦点, 只要鼠标经过客户区,
或者在客户区内被单击窗口过程都会收到鼠标消息,
被点击(包括双击/单击/拖动)的窗口将变成活动窗口。与客户区消息相对应的称为非客户区消息,
非客户区消息是指鼠标指针在窗口内并在在客户区外的移动或单击/双击等,
非客户区包括窗口的标题栏、菜单栏、滚动条、窗口的边框,
这些将在后面进行讨论, 这里先说客户区鼠标消息。
    
    1>. 鼠标单击
        鼠标在客户区单击时各个鼠标按键所产生的消息如下:

鼠标按键 按下时产生的消息 释放时产生的消息
左键 WM_LBUTTONDOWN WM_LBUTTONUP
中键 WM_MBUTTONDOWN WM_MBUTTONUP
右键 WM_RBUTTONDOWN WM_RBUTTONUP

        
        

 

 

    示例2中已经演示了一个处理鼠标左键单击的示例,
对于中键和右键处理的方法是相同的,
只要等待Windows发来消息然后处理这些消息就行了。
        
    2>. wParam参数中的内容
        参数wParam中的值表示了鼠标按钮、Shift键和Ctrl键的状态。
将wParam与”鼠标键”标识符进行按位与(&)运算可以得到鼠标按键与鼠标键的状态,
以前缀MK_为开头的标识符称为”鼠标键”,
有如下鼠标键:

            #define MK_LBUTTON          0x0001            //按下左键
            #define MK_RBUTTON          0x0002            //按下右键
            #define MK_MBUTTON          0x0010            //按下中键
            #define MK_SHIFT            0x0004            //按下Shift键
            #define MK_CONTROL          0x0008            //按下Ctrl键

       
例如, 当接收到 WM_LBUTTONDOWN 消息时, 若

            wParam & MK_SHIFT 

       
的值为TRUE(非零), 则表示按下左键的同时也按下了Shift键。
        例如:

www.9778.com 13

        case WM_LBUTTONDOWN:
            if( wParam & MK_CONTROL )
            {
                MessageBox( hwnd, TEXT("Ctrl键与鼠标左键同时被按下!"), TEXT("鼠标动作"), MB_OK ) ;
                return 0 ;
            }
            return 0 ;

www.9778.com 14

       
只有当鼠标左键与键盘的Ctrl键同时被按下时我们弹出个对话框说明”Ctrl键与鼠标左键同时被按下!”,
否则什么也不做。
        
        3>. 鼠标双击
            双击对两次击中的位置以及时间间隔都有一定要求,
只有当两次快速的单击在物理位置上靠的很近并且时间间隔很短的情况下才算双击。
            
            如果想让窗口过程接收鼠标双击消息,
需要在注册窗口类(RegisterClass)时, 初始化wndclass中的style成员的属性中再加上CS_DBLCLKS标识符:

                wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS ;

      
     如果在窗口类的style成员中没有包含 CS_DBLCLKS 标识符,
那么即使当用户双击时不会产生双击消息, 而是产生一串如下的消息:

                WM_LBUTTONDOWN
                WM_LBUTTONUP
                WM_LBUTTONDOWN
                WM_LBUTTONUP

      
     由于用户在连续两次按下鼠标左键时需要一定时间, 即使这个时间比较短暂,
但是在这个过程中程序还是有可能收到其他消息的,
比如用户在快速的两次单击中手的微微抖动就会在其中插入一个WM_MOUSEMOVE的消息,
这里暂时忽略其中插入的消息, 假设消息就是连续的这些。
第七章 鼠标(CHECKER2)。            
            当窗口类的style成员只中包含CS_DBLCLKS标识符后,
用户再次双击就会产生这样的一串消息:

                WM_LBUTTONDOWN
                WM_LBUTTONUP
                WM_LBUTTONDBLCLK
                WM_LBUTTONUP

      
     可以看到, 在加入 CS_DBLCLKS 标识符后, 第三个消息
WM_LBUTTONDOWN
只是被简单的替换成了 WM_LBUTTONDBLCLK 消息。
            
            鼠标各个按键双击时第三个消息所对应替换的消息如下:

            #define WM_LBUTTONDBLCLK                0x0203            //左键
            #define WM_MBUTTONDBLCLK                0x0209            //中键
            #define WM_RBUTTONDBLCLK                0x0206            //右键

 

四、非客户区鼠标消息
    非客户区消息几乎与客户区消息完全对应, 只是在标识符中多了一个”NC“字符(noclient),
当鼠标指针在窗口的非客户区移动时(比如标题栏), 窗口过程就会接收到 WM_NCMOUSEMOVE 消息,
在客户区外鼠标按下产生的消息如下:

鼠标按键 按下 释放 第二次按下(双击)
左键 WM_NCLBUTTONDWON WM_NCLBUTTONUP WM_NCLBUTTONDBLCLK
中键 WM_NCMBUTTONDOWN WM_NCMBUTTONUP WM_NCMBUTTONDBLCLK
右键 WM_NCRBUTTONDOWN WM_NCRBUTTONUP WM_NCRBUTTONDBLCLK

 

 

 

  另外与客户区消息不同的是,
这里的 wParam
参数中的值与客户区中的含义有所不同, 这里的 wParam
表示非客户区鼠标移动或单击的位置, 他的值被设定成一些以 HT 开头的标识符中, 表示
击中测试“(Hit Test), 关于击中测试与以HT开头的标识符将在下面讲到。
    
    参数 lParam 中的值依然是鼠标指针的位置信息,
但此时的信息正好与客户区中的 lParam 的坐标信息相反, 客户区中的 lParam
的值是相对于窗口客户区的坐标, 而这里的(非客户区) lParam
中所包含的坐标信息是屏幕坐标, 在上面已经提到过, 使用ScreenToClientClientToScreen可以实现屏幕坐标与客户区坐标之间的转化。
    
    处理非客户区左键单击示例:

www.9778.com 15

    switch(message)
    {
    case WM_PAINT:
        hdc = BeginPaint( hwnd, &ps ) ;
        EndPaint( hwnd, &ps ) ;
        return 0 ;

    case WM_NCLBUTTONDOWN:        //处理非客户区鼠标左键单击事件
        MessageBox( hwnd, TEXT("非客户区鼠标左键被单击"), TEXT("鼠标动作"), MB_OK ) ;
        return 0 ;

    case WM_DESTROY:
        PostQuitMessage(0) ;
        return 0 ;
    }

www.9778.com 16

 

五、关于”击中测试”消息
WM_NCHITTEST

    WM_NCHITTEST表示”非客户区击中测试”,
所谓的击中测试就是测试鼠标当前所在的位置,
这个消息的优先级高于其他所有的客户区和非客户区消息, 参数 lParam
中包含相对于屏幕坐标的x值与y值, wParam 参数另有用途。
www.9778.com,    
    一般来说, WM_NCHITTEST 消息是交给 DefWindowProc
默认的消息处理函数进行处理的, 对于客户区中, Windows会利用 WM_NCHITTEST
消息来产生所有和其他鼠标位置相关的鼠标消息。对于非客户区消息来说,
DefWindowProc 处理 WM_NCHITTEST 消息后返回一个 wParam 值,
这个值可以是任意一个非客户区鼠标消息的 wParam 参数的值, 这个 wParam
值用来判断鼠标的所在的位置。
    
    举例来说, 如果 DefWindowProc 函数在处理 WM_NCHITTEST 消息后返回一个
HTCLIENT , HTCLIENT
表示鼠标在客户区,
这时Windows会将屏幕坐标转换成客户区坐标,并产生一个相关的客户区的鼠标消息;
    当返回值为 HTCAPTION
表示鼠标此时在一个标题栏中,
所以Windows会将此时鼠标的坐标位置转成屏幕坐标并发送相关的非客户区消息。
    
    这些返回的标识符定义在WINUSER.H头文件中, 相关的定义如下:

www.9778.com 17

#define HTERROR             (-2)                    //在屏幕的后面或在窗体之间的线上(使函数DefWindowProc产生一个警示音)
#define HTTRANSPARENT       (-1)                    //在一个被其它窗口覆盖的窗口中
#define HTNOWHERE           0                        //在屏幕背景或窗口之间的分界线
#define HTCLIENT            1                        //在客户区中
#define HTCAPTION           2                        //在标题栏中
#define HTSYSMENU           3                        //在一个窗口菜单栏或子窗口的关闭按钮上
#define HTGROWBOX           4                        //在尺寸框中
#define HTSIZE              HTGROWBOX                //同HTGROWBOX
#define HTMENU              5                        //在菜单区域
#define HTHSCROLL           6                        //在水平滚动条上
#define HTVSCROLL           7                        //在垂直滚动条上
#define HTMINBUTTON         8                        //在最小化按钮上
#define HTMAXBUTTON         9                        //在最大化按钮上
#define HTLEFT              10                        //在窗口的左边框上
#define HTRIGHT             11                        //在窗口的右边框上
#define HTTOP               12                        //在窗口水平边框的上方
#define HTTOPLEFT           13                        //在窗口边框的左上角
#define HTTOPRIGHT          14                        //在窗口边框的右上角
#define HTBOTTOM            15                        //在窗口的水平边框的底部
#define HTBOTTOMLEFT        16                        //在窗口边框的左下角
#define HTBOTTOMRIGHT       17                        //在窗口边框的右下角
#define HTBORDER            18                        //在不具有可变大小边框的窗口的边框上
#define HTREDUCE            HTMINBUTTON                //同HTMINBUTTON
#define HTZOOM              HTMAXBUTTON                //同HTMAXBUTTON
#define HTSIZEFIRST         HTLEFT                    //同HTLEFT
#define HTSIZELAST          HTBOTTOMRIGHT             //同HTBOTTOMRIGHT
#define HTOBJECT            19                        //忽略该标识符, 已废弃
#define HTCLOSE             20                        //在关闭按钮上
#define HTHELP              21                        //在帮助按钮上

www.9778.com 18

   
这样在获取非客户区消息时我们就可以根据 wParam
中的值判断鼠标在窗口的位置了, 像这样:

www.9778.com 19

    case WM_NCLBUTTONDOWN:        //处理非客户区的鼠标左键单击事件
        x = LOWORD( lParam ) ;    //通过lParam获取鼠标位置
        y = HIWORD( lParam ) ;
        switch(wParam)            //通过wParam判断鼠标在窗口的位置
        {
        case HTCAPTION:        //在标题上
            wsprintf( szBuffer,  "鼠标左键在标题栏中被单击, 击中位置: (%i, %i)", x, y ) ;
            MessageBox( hwnd, szBuffer, TEXT("鼠标动作"), MB_OK ) ;
            break ;
        case HTMINBUTTON:    //在最小化按钮上
            wsprintf( szBuffer,  "鼠标左键在最小化按钮上被单击, 击中位置: (%i, %i)", x, y ) ;
            MessageBox( hwnd, szBuffer, TEXT("鼠标动作"), MB_OK ) ;
            break ;
        case HTMAXBUTTON:    //在最大化按钮上
            wsprintf( szBuffer,  "鼠标左键在最大化按钮上被单击, 击中位置: (%i, %i)", x, y ) ;
            MessageBox( hwnd, szBuffer, TEXT("鼠标动作"), MB_OK ) ;
            break ;
        }
        return 0 ;

www.9778.com 20

   
首先捕获 鼠标左键在非客户区的单击事件, 然后再通过 wParam
判断鼠标在窗口的位置, 这里获取鼠标位置是通过 LOWORD 和 HIWORD 宏完成的,
还有两个功能相同的宏也可以用来获取lParam中的鼠标信息, 他们是 GET_X_LPARAM 宏和 GET_Y_LPARAM, 不过这两个宏是定义在
WINDOWSX.H 头文件中的,
如果要使用这两个宏需要将 WINDOWSX.H 包含进来。
    举例:

    xPos = GET_X_LPARAM(lParam) ;
    yPos = GET_Y_LPARAM(lParam) ;

 

 


 

转自:

  老长时间没有更新Windows程序设计的内容了,今天看了一段Windows程序设计文件操作的内容,同时想起鼠标操作还没有更新过

www.9778.com 21www.9778.com 22

文字,这里先就写一点关于鼠标文字吧。

  1 /*---------------------------------------------
  2 CHECKER2.C -- Mouse Hit-Test Demo Program No.2
  3               (c) Charles Petzold, 1998
  4 ---------------------------------------------*/
  5 
  6 #include <Windows.h>
  7 
  8 #define DIVISIONS 5
  9 
 10 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
 11 
 12 int WINAPI WinMain( __in HINSTANCE hInstance
 13                     , __in_opt HINSTANCE hPrevInstance
 14                     , __in LPSTR lpCmdLine
 15                     , __in int nShowCmd )
 16 {
 17     static TCHAR szAppName[] = TEXT("Checker2");
 18     HWND hwnd;
 19     MSG msg;
 20     WNDCLASS wndclass;
 21 
 22     wndclass.style = CS_HREDRAW | CS_VREDRAW;
 23     wndclass.lpfnWndProc = WndProc;
 24     wndclass.cbClsExtra = 0;
 25     wndclass.cbWndExtra = 0;
 26     wndclass.hInstance = hInstance;
 27     wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
 28     wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
 29     wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
 30     wndclass.lpszMenuName = NULL;
 31     wndclass.lpszClassName = szAppName;
 32 
 33     if (!RegisterClass(&wndclass))
 34     {
 35         MessageBox(NULL, TEXT("Program requires Windows NT!")
 36             , szAppName, MB_ICONERROR);
 37         return 0;
 38     }
 39 
 40     hwnd = CreateWindow(szAppName, TEXT("Checker2 Mouse Hit-Test Demo")
 41         , WS_OVERLAPPEDWINDOW
 42         , CW_USEDEFAULT, CW_USEDEFAULT
 43         , CW_USEDEFAULT, CW_USEDEFAULT
 44         , NULL, NULL, hInstance, NULL);
 45 
 46     ShowWindow(hwnd, nShowCmd);
 47     UpdateWindow(hwnd);
 48 
 49     while (GetMessage(&msg, NULL, 0, 0))
 50     {
 51         TranslateMessage(&msg);
 52         DispatchMessage(&msg);
 53     }
 54 
 55     return msg.wParam;
 56 }
 57 
 58 LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
 59 {
 60     static BOOL fState[DIVISIONS][DIVISIONS];
 61     static int cxBlock, cyBlock;
 62     HDC hdc;
 63     int x, y;
 64     PAINTSTRUCT ps;
 65     POINT point;
 66     RECT rect;
 67 
 68     switch (message)
 69     {
 70     case WM_SIZE:
 71         cxBlock = LOWORD(lParam) / DIVISIONS;
 72         cyBlock = HIWORD(lParam) / DIVISIONS;
 73         return 0;
 74 
 75     case WM_SETFOCUS:
 76         ShowCursor(TRUE);
 77         return 0;
 78 
 79     case WM_KILLFOCUS:
 80         ShowCursor(FALSE);
 81         return 0;
 82 
 83     case WM_KEYDOWN:
 84         GetCursorPos(&point);
 85         ScreenToClient(hwnd, &point);
 86 
 87         x = max(0, min(DIVISIONS - 1, point.x / cxBlock));
 88         y = max(0, min(DIVISIONS - 1, point.y / cyBlock));
 89 
 90         switch (wParam)
 91         {
 92         case VK_UP:
 93             --y;
 94             break;
 95 
 96         case VK_DOWN:
 97             ++y;
 98             break;
 99 
100         case VK_LEFT:
101             --x;
102             break;
103 
104         case VK_RIGHT:
105             ++x;
106             break;
107 
108         case VK_HOME:
109             x = y = 0;
110             break;
111 
112         case VK_END:
113             x = y = DIVISIONS - 1;
114             break;
115 
116         case VK_RETURN:
117         case VK_SPACE:
118             SendMessage(hwnd, WM_LBUTTONDOWN, MK_LBUTTON, MAKELONG(x * cxBlock, y * cyBlock));
119             break;
120         }
121 
122         x = (x + DIVISIONS) % DIVISIONS;
123         y = (y + DIVISIONS) % DIVISIONS;
124 
125         point.x = x * cxBlock + cxBlock / 2;
126         point.y = y * cyBlock + cyBlock / 2;
127 
128         ClientToScreen(hwnd, &point);
129         SetCursorPos(point.x, point.y);
130         return 0;
131 
132     case WM_LBUTTONDOWN:
133         x = LOWORD(lParam) / cxBlock;
134         y = HIWORD(lParam) / cyBlock;
135 
136         if (x < DIVISIONS && y < DIVISIONS)
137         {
138             fState[x][y] ^= 1;
139 
140             rect.left = x * cxBlock;
141             rect.top = y * cyBlock;
142             rect.right = (x + 1) * cxBlock;
143             rect.bottom = (y + 1) * cyBlock;
144 
145             InvalidateRect(hwnd, &rect, FALSE);
146         }
147         else
148             MessageBeep(0);
149         return 0;
150 
151     case WM_PAINT:
152         hdc = BeginPaint(hwnd, &ps);
153 
154         for (x = 0; x < DIVISIONS; ++x)
155             for (y = 0; y < DIVISIONS; ++y)
156             {
157                 Rectangle(hdc, x * cxBlock, y * cyBlock
158                     , (x + 1) * cxBlock, (y + 1) * cyBlock);
159                 
160                 if (fState[x][y])
161                 {
162                     MoveToEx(hdc, x * cxBlock, y * cyBlock, NULL);
163                     LineTo(hdc, (x + 1) * cxBlock, (y + 1) * cyBlock);
164                     MoveToEx(hdc, x * cxBlock, (y + 1) * cyBlock, NULL);
165                     LineTo(hdc, (x + 1) * cxBlock, y * cyBlock);
166                 }
167             }
168 
169         EndPaint(hwnd, &ps);
170         return 0;
171 
172     case WM_DESTROY:
173         PostQuitMessage(0);
174         return 0;
175     }
176 
177     return DefWindowProc(hwnd, message, wParam, lParam);
178 }

一、鼠标

CHECKER2.C

(1)

在CHECKER2程序中,处理WM_KEYDOWN时利用GetCursorPos判断指针的位置,并利用ScreenToClient将屏幕坐标转换成客户区坐标,然后将坐标值除以矩形块的宽和高,得到x和y。这些x和y的值表示了矩形在5*5数组中的位置。当按下某个键时,鼠标指针可能在客户区也可能不在客户区内,因此x和y必须包含在min和max的宏处理中,保证它们的范围处于0和4之间。

  Windows支持单键、双键和三键鼠标,还可以使用操纵杆或者光笔模拟鼠标。

对于方向键,CHECKER2程序相应的增加或减少x和y的值。若按下回车键或空格键,CHECKER2程序调用SendMessage给自己发送一个WM_LBUTTONDOWN消息。最后,WM_KEYDOWN处理逻辑计算得到指向矩形中心的客户区坐标,并调用ClientToScreen将其转换成屏幕坐标,最后调用SetCursorPos设置指针的位置。

  1、判断系统是否存在鼠标

  在使用鼠标之前必须判断系统中是否存在鼠标,可通过函数GetSystemMetrics来判断鼠标是否存在。

      bMouse=GetSystemMetrics(SM_MOUSEPRESENT);

  若安装了鼠标,则bMouse将返回TRUE,否则就返回0.

  要点:

    Windows98中无论是否安装鼠标,这个函数都将返回TRUE。

  2、判断鼠标的键数

  通过GetSystemMetrics函数还可以确定安装的鼠标的键鼠,只要将传递给函数是参数改为:SM_CMOUSEBUTTONS
即可判断

系统中鼠标的键;如果没有安装鼠标,那么函数将返回0;在Windows98中,无论有没有安装鼠标,这个函数都将返回2.

  3、判断鼠标是否切换过左右手

  通过向GetSystemMetrics函数传递SM_SWAPBUTTON是否进行了这种切换;

(2) windows预定义

  当Windows用户移动鼠标时,在屏幕上将有一个“鼠标光标”的小位图,随着用户的移动而移动。鼠标光标上有一个指向屏幕上精确

位置的单象素“热点”。

  Windows支持预定义的鼠标光标:IDC_ARROW(箭头光标)、IDC_CROSS(十字光标)、IDC_WAIT(光标);箭头光标的热点在

箭头的顶端,十字光标的热点在中心。

  Windows支持用户自定义鼠标光标;在定义窗口类的时候,我们可以指定预定义的鼠标光标为窗口默认鼠标。

  通过下面的语句指定窗口默认光标:

          wndclass.hCursor=LoadCursor(NULL,IDC_ARROW);

  鼠标操作术语:

    单击:按下并松开一个鼠标键

    双击:快速按下并松开鼠标键两次

    拖曳:按住鼠标键并移动鼠标

  鼠标键名:

    左键:LBUTTON

    中键:MBUTTON

    右键:RBUTTON

  双键鼠标只有左键与右键,单键鼠标只有一个左键。

(3)客户区鼠标消息

  鼠标在用户窗口移动、按键窗口都会收到消息;这一点与键盘消息不一样:只有拥有输入焦点的窗口才能接收键盘消息。

  Windows一共定义了21种鼠标消息,其中10种是客户去消息,而有11中是非客户去消息,用户程序经常忽略非客户区消息。当鼠标从窗口移动

时,窗口会接收到WM_MOUSEMOVE消息;当用户在窗口客户去按下鼠标键的时候会接受到如下鼠标消息:

www.9778.com 23

  只有三键鼠标才会接收到中键鼠标消息,只有双键、三键鼠标才能接收到右键消息;仅当定义的窗口类能接收DBLCLK双击消息之后,窗口

才能接收到这些消息。

  对于窗口接收到的鼠标消息,lParam参数的值均含有鼠标的位置:低位字为x坐标,高位字为y坐标,lParam参数为32位,字定义为16位;

当要处理鼠标消息时可以通过:

        x=LOWORD(lParam);

        y=HIWORD(lParam);

  获取当前鼠标消息的热点坐标,x、y的值均是相对于窗口客户区左上角顶点而言;这与通常使用的坐标有点不一样。

  Windows定义wParam参数指示鼠标键及Shift和Ctrl键的状态,在程序中,可以使用WINUSER.H定义的位旗标来测试wParam参数。

www.9778.com 24

  MK代表鼠标消息,可以利用下面的语句测试按键的状态:

        if(wParam&MK_SHIFT)

          statement;

    如果接收到WM_LBUTTONDOWN消息的时候,上面的statement语句能执行,则表示按下左键的时候同时按下了shift键。

  Windows不能在鼠标移动过程中,为热点经过的每个像素都发送WM_MOUSEMOVE消息,窗口接收到消息的速度取决于鼠标移动的速度

以及窗口过程处理移动消息的速度;即Windows不能用未处理的WM_MOUSEMOVE消息来填充消息队列,(为了验证这个特性,可以利用spy

程序来监控鼠标消息和窗口处理消息的情况)

  窗口处理鼠标消息的过程如下:

    非活动窗口——>按下鼠标——》窗口变成活动窗口——》发送鼠标消息到窗口过程,

    活动窗口——》按下鼠标——》发送鼠标消息到窗口过程

  上述过程是一般的过程,但是这个过程有时候会变成其他样子,当有模态窗口存在时,这个过程可能变得不一样,需要

注意。

  窗口接收到的消息也不一定是按下左键/按下右键为接收到第一个鼠标消息;例如我们在第一个窗口按下鼠标左键,而

后将鼠标移动到第二个窗口释放;那么第二个窗口接收到鼠标消息将会先是WM_MOUSEMOVE,然后是WM_LBUTTONUP消息,

因此在处理的时候需要注意。

  例外情况:

www.9778.com 25

 

(4)处理shift和Ctrl键与鼠标组合消息

  前面说过,可以通过wParam参数和位旗标的与运算来查看是否按下了鼠标和shift/ctrl这样的组合消息。

  在鼠标消息中可以通过下面的语句来确定是否按下了shift和Ctrl键

    if(wParam & MK_SHIFT)

    {

        if(wParam & MK_CONTROL)

          {鼠标消息的同时按下了shift和ctrl键}

        else

          {鼠标键的同时按下shift键}

    }

    else

    {

        if(wParam & MK_CONTROL)

          {鼠标消息的同时按下了ctrl键}

        else

          {仅有鼠标消息}

    }

  在用户程序中可以通过利用鼠标和键盘的组合消息来模拟鼠标消息,这样在单键鼠标的情况下就可以实现右键鼠标消息的处理。具体

我们就不用实例代码说明了。

  Windows使用函数GetKeyState通过虚拟键码VK_LBUTTON、VK_RBUTTON、VK_MBUTTON、VK_SHIFT、VK_CONTROL来返回

鼠标键与shift键的状态。如果GetKeySate返回负值,则说明按下了鼠标键或者shift键。因为GetKeyState返回当前正在处理的鼠标键或者shift

键的状态,所以全部状态消息都与相应的消息时同步的。

(5)双击鼠标键

  双击鼠标键是指在短时间内单击两次。

  双击条件:  
1、两次单击发生时鼠标光标热点的距离必须在系统规定的方位之内

        2、两次单击发生的时间间隔在指定的时间范围内;可以在系统控制面板中改变鼠标双击时间间隔。

  如果要使得窗口能接收双击鼠标消息,那么窗口的风格必须有CS_DBLCLKS位旗标。如下所示:

    wndclass.style=CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;

  如果没有设置窗口风格,那么双击时窗口将接收下面一系列的消息:

      www.9778.com 26

  如果设置了窗口风格将接收到:

  www.9778.com 27

  如上所示,这两个消息队列,仅是第三个消息发生了变化。

  如果双击中的第一个单击操作完成某个单击功能,则处理双击消息很容易,那么第二次单击消息则用来完成第一次单击以外的事情;

这里如:

  Windows资源管理器的鼠标的单击选定,而双击打开;
当然可以在系统中设计指向选定而单击打开,不过这不符合大多数人的操

作习惯。

(6)非客户区鼠标消息

  如果鼠标在窗口之内,但是在客户区之外,那么窗口将会接收系统发送的非客户区鼠标消息;窗口的非客户区包括标题栏、菜单栏和

窗口滚动条,一般情况下,用户程序不需要处理非窗口鼠标消息,而将这些消息由DefWindowProc函数处理。

  Windows用“NC”
表示非客户区消息,如果鼠标在窗口的非客户区中移动,那么窗口过程接收到WM_NCMOUSEMOVE消息。非客

户区消息如下所示:

www.9778.com 28

  因为非客户区有几个区域,因此我们在处理非客户区消息的时候,还需要分辨是在标题栏、菜单栏还是滚动条的鼠标消息。

  为了实现分辨区域:可以借助wParam、lParam参数来分辨。客户区的鼠标消息和非客户区鼠标消息的wParam、lParam参数是不一样的,wParam

参数指明发送鼠标消息的非客户区位置。当有非客户区鼠标消息时,wParam设定为一HT开始的宏标识符(HT表示命中测试)

  当有非客户区鼠标消息时,lParam参数返回鼠标消息发生时热点的坐标,但是此时的坐标为屏幕坐标,而不是客户区坐标。屏幕坐标以屏幕的左上角顶

点为坐标原点(0,0);当鼠标往右移动时x增加,鼠标往下移动时y增加。

  可以利用Windows函数在屏幕坐标和客户区坐标进行转换:

      ScreenToClient(hwnd,&pt);

      ClientToScreen(hwnd,&pt);

www.9778.com 29

  

(8)命中测试消息

  在Windows中设置了命中测试消息WM_NCHITTEST,此消息优先于所有其他的客户区和非客户区鼠标消息,lParam参数含有鼠标位置的

x和y屏幕坐标,wParam参数没有用。

  Windows应用程序通常把这个消息传递给DefWindowProc,然后Windows用WM_NCHITTEST消息产生基于鼠标位置的其他鼠标消息;

对于非客户区鼠标消息,当处理WM_NCHITTEST消息时,从DefWindowProc返回的值域将成为鼠标消息中的wParam参数,这个值可以是任

意非客户区鼠标消息的wParam值再加上以下内容:

  HTCLIENT:  客户区

  HTNOWHERE:不在窗口中

  HTTRANSPARENT:窗口由另一个窗口覆盖

  HTERROR:使DefWindowProc产生蜂鸣声

  如果DefWindowProc在处理WM_NCHITTEST消息后返回HTCLIENT,那么Windows将把屏幕坐标转换为客户区坐标,并产生客户区鼠标消息。

因为WM_NCHITTEST消息在所有的鼠标消息前处理,因此我们可以利用这一点来禁用所有的鼠标消息:

  在窗口过程函数中我们可以这样做:

    case  WM_NCHITTEST:

      return (LRESULT)WM_NCHITTEST;

   这样就可以禁用所有的窗口鼠标消息。

 

  利用命中测试消息,Windows程序设计了一个消息产生消息的机制,我们可以利用窗口的系统菜单的鼠标双击来分析这个过程:

  

    鼠标双击窗口系统菜单——》WM_NCHITTEST消息——》DefWindowProc处理WM_NCHITTEST消息,后返回——》DefWindowProc将

WM_NCLBUTTONDBLCLK消息放到消息队列,并把wParam设置为HTSYSMENU——》通常用户程序不会处理WM_NCLBUTTONDBLCLK

消息,而把消息交给DefwindowProc函数处理——》DefwindowProc接收到wParam=HTSYSMENU的WM_NCLBUTTONDBLCLK

消息后;将WM_SYSCOMMAND消息放入消息队列中,并设置wParam=SC_CLOSE参数——》窗口过程把这个WM_SYSCOMMAND消息

传给DefWindowProc函数处理——》DefwindowProc通过给窗口发送WM_CLOSE消息。

  如果用户程序不处理WM_CLOSE消息,那么DefWindowProc将处理这个消息;DefwindowProc函数接收到WM_CLOSE消息后,将调用

DestroyWindow函数来处理WM_CLOSE,除了其他处理,DestroyWindow还给窗口过程发送WM_DESTROY消息,窗口过程通常用下列

代码来处理WM_DESTROY消息:

    case WM_DESTROY:

        PostQiutMessage(0);

        retrun 0;

  PostQiutMessage使Windows把WM_QUIT消息放入消息队列中,这个消息永远不会经由窗口过程处理,因为WM_DESTROY消息将

是GetMessage函数返回0,终止消息循环,从而退出程序。

  

(9)捕获鼠标

  有时候,可能在鼠标离开窗口后还想获得鼠标消息,这时候我们就需要捕获鼠标。

  例如:  

    在画图程序中,需要画矩形,我们按下鼠表左键,然后拖曳鼠标到适合大小,接下来释放鼠标,就可以绘出矩形;但是假如在拖曳的过程

中鼠标离开了窗口的客户区,那么原来的窗口就不能接受鼠标释放的消息,这时没有办法确定矩形的大小,就不知道如何绘制矩形了,

  为了处理上面的问题,可以通过鼠标捕获功能来实现。

  Windows提供了函数捕获鼠标,如下所示:

    SetCapture(hwnd);

  在调用这个函数后,Windows会将以后的所有鼠标消息发送给窗口句柄为hwnd的窗口过程,并且鼠标消息都将是客户区消息,即使鼠标在非客户区;

lParam参数将指示鼠标在坐标中的位置。不过LOWORD(lParam)和HIORD(lParam)返回的值可能为正也为负,这与鼠标发送消息的位置有关。

  当要释放鼠标时,调用

    ReleaseCapture(hwnd);

  就可以使消息恢复到捕获前的状态。

  我们知道在窗口1按下鼠标键,并且窗口1鼠标被捕获,然后移动到窗口2,接下来释放鼠标键,那么窗口1将不能接收到鼠标释放消息,而窗口2将接收

鼠标消息,这时将不是捕获鼠标的那个窗口接收鼠标消息,而是由光标下面的窗口来接收鼠标消息;

  为了防止捕获产生的异常状态,只有当鼠标键在窗口的客户区中被按下时才捕获鼠标,当键被释放时才释放鼠标捕获。