這個消息比較實用也很關鍵,它代表非顯示區域命中測試。這個消息優先於所有其他的顯示區域和非顯示區域鼠標消息。其中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 }