[转] 对WM_NCHITTEST消息的了解+代码实例进行演示
这个消息比较实用也很关键,它代表非显示区域命中测试。这个消息优先于所有其他的显示区域和非显示区域鼠标消息。其中lParam参数含有鼠标位置的x和y屏幕坐标,wParam 这里没有用。
Windows应用程序通常把这个消息传送给DefWindowProc,然后Windows用WM_NCHITTEST消息产生与鼠标位置相关的所有其他鼠标消息。通俗的讲从消息产生消息。
case WM_NCHITTEST:
return (LRESULT)HTNOWHERE;
以上代码能禁用窗口的所有显示区域和非显示区域鼠标消息,也就是当鼠标在窗口,这里包括系统菜单图标,缩放按钮,关闭按钮等时,鼠标按键将会失效。
先看一下这个消息的返回值吧:
One of the mouse hit-test enumerated values listed below.
下面列出的鼠标击中测试枚举值之一。
· HTBORDER 在不具有可变大小边框的窗口的边框上。
· HTBOTTOM 在窗口的水平边框的底部。
· HTBOTTOMLEFT 在窗口边框的左下角。
· HTBOTTOMRIGHT 在窗口边框的右下角。
· HTCAPTION 在标题条中。
· HTCLIENT 在客户区中。
· HTERROR 在屏幕背景或窗口之间的分隔线上(与HTNOWHERE相同,除了Windows的DefWndProc函数产生一个系统响声以指明错误)。
· HTGROWBOX 在尺寸框中。
· HTHSCROLL 在水平滚动条上。
· HTLEFT 在窗口的左边框上。
· HTMAXBUTTON 在最大化按钮上。
· HTMENU 在菜单区域。
· HTMINBUTTON 在最小化按钮上。
· HTNOWHERE 在屏幕背景或窗口之间的分隔线上。
· HTREDUCE 在最小化按钮上。
· HTRIGHT 在窗口的右边框上。
· HTSIZE 在尺寸框中。(与HTGROWBOX相同)
· HTSYSMENU 在控制菜单或子窗口的关闭按钮上。
· HTTOP 在窗口水平边框的上方。
· HTTOPLEFT 在窗口边框的左上角。
· HTTOPRIGHT 在窗口边框的右上角。
· HTTRANSPARENT 在一个被其它窗口覆盖的窗口中。
· HTVSCROLL 在垂直滚动条中。
· HTZOOM 在最大化按钮上。
关于这个消息的一个经典应用莫过于如何拖动一个无标题栏的窗体或者说我如何实现在客户区也能拖动此窗体,简单来说就是对WINDOWS进行了欺骗。
一个思考3秒就容易想到的方法是:处理鼠标消息WM_LBUTTONDOWN和WM_LBUTTONUP。在OnLButtonUp函数中计算鼠标位置的变化,调用MoveWindow实现窗口的移动。
PS:拖动标题栏移动窗口的时候,会出现一个矩形框,它提示了窗口移动的当前位置。当鼠标左键放开的时候,窗口就移动到矩形框所在位置。而我们刚才的那个的实现方案中没有这个功能。要实现此功能,我们必须自己来画这些矩形。事实上,我们没有必要自己来做这件事情,因为Windows已经给我们做好了。
试想,如果我能够欺骗Windows,告诉它现在鼠标正在拖动的是标题栏而不是客户区,那么窗口移动操作就由Windows来代劳了。
到这里这个消息就要闪亮登场了,前面说过了WM_NCHITTEST的消息响应函数会根据鼠标当前的坐标来判断鼠标命中了窗口的哪个部位,消息响应函数的返回值指出了部位,例如它可能会返回HTCAPTION,或者HTCLIENT等。
为了便于理解,先描述一下Windows对鼠标键按下的响应流程:
1. 确定鼠标键点击的是哪个窗口。Windows会用表记录当前屏幕上各个窗口的区域坐标,当鼠标驱动程序通知Windows鼠标键按下了,Windows根据鼠标的坐标确定它点击的是哪个窗口。
2. 确定鼠标键点击的是窗口的哪个部位。Windows会向鼠标键点击的窗口发送WM_NCHITTEST消息,来询问鼠标键点击的是窗口的哪个部位。(WM_NCHITTEST的消息响应函数的返回值会通知Windows)。通常来说,WM_NCHITTEST消息是系统来处理的,用户一般不会主动去处理它(也就是说,WM_NCHITTEST的消息响应函数通常采用的是Windows默认的处理函数)。
3. 根据鼠标键点击的部位给窗口发送相应的消息。例如:如果WM_NCHITTEST的消息响应函数的返回值是HTCLIENT,表示鼠标点击的是客户区,则Windows会向窗口发送WM_LBUTTONDOWN消息;如果WM_NCHITTEST的消息响应函数的返回值不是HTCLIENT(可能是HTCAPTION、HTCLOSE、HTMAXBUTTON等),即鼠标点击的是非客户区,Windows就会向窗口发送WM_NCLBUTTONDOWN消息。
这里有必要详细讨论一下:如果WM_NCHITTEST的消息响应函数的返回值是HTCAPTION,即指示了鼠标点击了标题栏,接下去Windows的处理是怎样的?
上面已经提到,接下来,Windows会向窗口发送WM_NCLBUTTONDOWN消息。
MSDN对WM_NCLBUTTONDOWN消息描述如下:
WM_NCLBUTTONDOWN
nHittest = (INT) wParam; // hit-test value
pts = MAKEPOINTS(lParam); // position of cursor
WM_NCLBUTTONDOWN的wParam指示了鼠标点击的窗口部位,lParam指示了当前鼠标的坐标。
如果应用程序没有对该消息响应,则由系统默认处理。
系统默认处理又是怎样的呢?系统发现wParam指示了鼠标点击的是标题栏,就会标识当前窗口处于“拖拽状态”(Windows内部记录了每个窗口的状态信息)。由于标识了“拖拽状态”,则从此刻起到鼠标键放开之前,你的鼠标移动状况完全由Windows跟踪。它根据鼠标的移动,使得窗口作“同步”移动。
注意,这个过程中,窗口不会收到WM_NCMOUSEMOVE消息,因为窗口和鼠标是“同步”移动的,你的鼠标相对于窗口是静止的。
最后再顺路提一下,如果想在右键这个窗体的时候弹出一个菜单, 当完成 MSG_WM_RBUTTONDOWN 这个消息的时候,发现窗体收不到这个消息, 将WM_NCHITTEST消息的实现去掉就可以了,原因是:
因为你在WM_NCHITTEST中处理了鼠标消息,把他定位成HTCAPTION,也就是鼠标在标题栏上,而标题栏属于非客户区(NC);
非客户区的事件消息都是以WM_NC开头的。也就是说,当你的WM_NCHITTEST返回HTCAPTION时,原来可以用WM_LBUTTONUP处理的消息,你只能用WM_NCLBUTTONUP来处理。
1 #include <windows.h> 2 #define DIVISIONS 5 3 LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; 4 int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, 5 PSTR szCmdLine, int iCmdShow) 6 { 7 static TCHAR szAppName [] = TEXT ("RECT") ; 8 HWND hwnd ; 9 MSG msg ; 10 WNDCLASS wndclass ; 11 12 wndclass.style = CS_HREDRAW | CS_VREDRAW|CS_DBLCLKS ;//客户区想要响应双击,则CS_DBLCLKS得注册进去 13 wndclass.lpfnWndProc = WndProc ; 14 wndclass.cbClsExtra = 0 ; 15 wndclass.cbWndExtra = 0 ; 16 wndclass.hInstance = hInstance ; 17 wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; 18 wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; 19 wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; 20 wndclass.lpszMenuName = NULL; 21 wndclass.lpszClassName = szAppName ; 22 23 if (!RegisterClass (&wndclass)) 24 { 25 MessageBox (NULL, TEXT ("This program requires Windows NT!"), 26 szAppName, MB_ICONERROR) ; 27 return 0 ; 28 } 29 30 hwnd = CreateWindow (szAppName, TEXT ("RECT TEST"), 31 WS_OVERLAPPEDWINDOW, 32 CW_USEDEFAULT, CW_USEDEFAULT, 33 CW_USEDEFAULT, CW_USEDEFAULT, 34 NULL, NULL, hInstance, NULL) ; 35 36 ShowWindow (hwnd, iCmdShow) ; 37 UpdateWindow (hwnd) ; 38 39 while (GetMessage (&msg, NULL, 0, 0)) 40 { 41 TranslateMessage (&msg) ; 42 DispatchMessage (&msg) ; 43 } 44 return msg.wParam ; 45 } 46 static int cxClient, cyClient; 47 static BOOL state[DIVISIONS][DIVISIONS]; 48 LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) 49 { 50 51 HDC hdc; 52 HPEN hpen; 53 PAINTSTRUCT ps ; 54 RECT rect; 55 int i,j; 56 int x,y; 57 //INT a; 58 //char buf[10]; 59 char szAppName[]="RECTANGLE"; 60 LRESULT b; 61 62 switch (message) 63 { 64 case WM_CREATE: 65 //a=GetDoubleClickTime(); 66 //itoa(a,buf,10); 67 //MessageBoxA(hwnd,buf,"hi",0);// 得到鼠标双击的间隔 68 return 0 ; 69 70 71 case WM_SIZE: 72 cxClient = LOWORD (lParam)/DIVISIONS ; 73 cyClient = HIWORD (lParam)/DIVISIONS ; 74 //if(IsIconic(hwnd)) 75 //MessageBox(hwnd,TEXT("窗口已然最小化"),TEXT("情况"),0); 76 //if(IsZoomed(hwnd)) 77 //MessageBox(hwnd,TEXT("窗口已然最大化"),TEXT("情况"),0); 78 return 0 ; 79 case WM_LBUTTONDBLCLK: 80 POINT point; 81 point.x=LOWORD(lParam); 82 point.y=HIWORD(lParam); 83 PostMessage(hwnd,WM_NCLBUTTONDBLCLK,HTCAPTION,MAKELPARAM(point.x,point.y));//实行欺骗 84 //SetWindowPos(hwnd, 85 return 0; 86 case WM_LBUTTONDOWN: 87 { 88 x=LOWORD(lParam); 89 y=HIWORD(lParam); 90 i=j=0; 91 while(!(x<i*cxClient)) 92 { 93 ++i; 94 } 95 while(!(y<j*cyClient)) 96 { 97 ++j; 98 } 99 if(state[i-1][j-1]==TRUE) 100 { 101 state[i-1][j-1]=FALSE; 102 } 103 else 104 { 105 state[i-1][j-1]=TRUE; 106 } 107 InvalidateRect(hwnd,NULL,TRUE); 108 POINT point; 109 point.x=LOWORD(lParam); 110 point.y=HIWORD(lParam); 111 PostMessage(hwnd,WM_NCLBUTTONDOWN,HTCAPTION,MAKELPARAM(point.x,point.y));//实行欺骗 112 } 113 return 0; 114 115 case WM_PAINT: 116 hdc=BeginPaint(hwnd,&ps); 117 GetClientRect(hwnd,&rect); 118 hpen=CreatePen(PS_SOLID,2,RGB(255,0,0)); 119 120 for(j=0;j<DIVISIONS;j++) 121 for(i=0;i<DIVISIONS;i++) 122 { 123 Rectangle(hdc,i*cxClient,j*cyClient,(i+1)*cxClient,(j+1)*cyClient); 124 if(state[i][j]) 125 { 126 SelectObject(hdc,hpen); 127 MoveToEx(hdc,i*cxClient,j*cyClient,NULL); 128 LineTo(hdc,(i+1)*cxClient,(j+1)*cyClient); 129 MoveToEx(hdc,i*cxClient,(j+1)*cyClient,NULL); 130 LineTo(hdc,(i+1)*cxClient,j*cyClient); 131 SelectObject(hdc,GetStockObject(BLACK_PEN)); 132 133 134 } 135 } 136 DeleteObject(hpen); 137 EndPaint(hwnd,&ps); 138 139 return 0 ; 140 //case WM_NCLBUTTONDOWN: 141 //return (LRESULT)HTCAPTION; 142 case WM_NCHITTEST: 143 144 b=DefWindowProc(hwnd,message,wParam,lParam); 145 switch(b) 146 { 147 case HTCLIENT: 148 SetWindowText(hwnd,TEXT("点击的是客户区")); 149 return b; 150 case HTCAPTION: 151 SetWindowText(hwnd,TEXT("点击的是标题栏")); 152 return b; 153 case HTBOTTOM: 154 SetWindowText(hwnd,TEXT("点击的是下边框")); 155 return b; 156 case HTBOTTOMLEFT: 157 SetWindowText(hwnd,TEXT("点击的是左下边框")); 158 return b; 159 case HTCLOSE: 160 SetWindowText(hwnd,TEXT("点击的是关闭按钮")); 161 return b; 162 case HTLEFT: 163 SetWindowText(hwnd,TEXT("点击的是左边框")); 164 return b; 165 case HTMAXBUTTON: 166 SetWindowText(hwnd,TEXT("点击的是最大化按钮")); 167 return b; 168 case HTMINBUTTON: 169 SetWindowText(hwnd,TEXT("点击的是最小化按钮")); 170 return b; 171 case HTRIGHT: 172 SetWindowText(hwnd,TEXT("点击的是右边框")); 173 return b; 174 case HTSYSMENU: 175 SetWindowText(hwnd,TEXT("点击的是系统菜单")); 176 return b; 177 case HTTOP: 178 SetWindowText(hwnd,TEXT("点击的是上边框")); 179 return b; 180 case HTBOTTOMRIGHT: 181 SetWindowText(hwnd,TEXT("点击的是右下边框")); 182 return b; 183 default: 184 return b; 185 } 186 187 188 189 case WM_DESTROY: 190 191 PostQuitMessage (0) ; 192 return 0 ; 193 } 194 return DefWindowProc (hwnd, message, wParam, lParam) ; 195 }