最近學習了一下Windows 程式設計這本書中的滑鼠章節,現在總結一下學習的内容。大家可以一起參考。我将分為兩個部分進行介紹。有關滑鼠更具體的學習可以參考微軟官方文檔滑鼠輸入
滑鼠
- 1.滑鼠的基礎知識
- 2.客戶區滑鼠消息
- 3.非客戶區滑鼠消息
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);
例如擷取滑鼠滾輪資訊:
1.2 一些和滑鼠有關的術語:
①滑鼠指針:位圖格式的小圖示。具有一個單像素精度的"熱點"(hot spot),它在顯示裝置上訓示了一個精确位置。我們平時知道的滑鼠位置,說的就是熱點的位置了。
Windows 在Winuser.h中為我們提供了幾種常用的指針。最常用的是斜向箭頭,稱為IDC_ARROW。然後還有IDC_CROSS(➕ 狀)、IDC_WAIT(⌛狀,表示等待)等,具體檢視Winuser.h頭檔案。我們的建立視窗類時,預設指定為IDC_ARROW。
②單擊:按下滑鼠按鈕,然後松開。
③輕按兩下:連續兩次快速按下按鈕并且松開。
④拖動:保持按下按鈕,并且移動滑鼠。
2.客戶區滑鼠消息
2.0 介紹:與鍵盤不同的是鍵盤隻會把消息發送到具有指定焦點的視窗上。但是滑鼠不同,當滑鼠被單擊或者經過視窗時,即使該視窗是非活動視窗或不帶輸入焦點,視窗過程依然會處理滑鼠消息。
Windows 定義了21種滑鼠消息。但是其中11種消息與客戶區無關,稱為"非客戶區消息"。Windows應用程式經常忽略這類消息。
2.1 客戶區滑鼠消息:
按鈕 | 按下 | 釋放 | 第二次按下按鈕 |
---|---|---|---|
左鍵 | WM_LBUTTONDOWN | WM_LBUTTONUP | WM_LBUTTONDBLCLK |
中鍵 | WM_MBUTTONDOWN | WM_MBUTTONUP | WM_MBUTTONDBLCLK |
右鍵 | WM_RBUTTONDOWN | WM_RBUTTONUP | WM_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風格:
如果視窗過程沒有包含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_NCLBUTTONDOWN | WM_NCLBUTTONUP | WM_NCLBUTTONDBLCLK |
中鍵 | WM_NCMBUTTONDOWN | WM_NCMBUTTONUP | WM_NCMBUTTONDBLCLK |
右鍵 | WM_NCRBUTTONDOWN | WM_NCRBUTTONUP | WM_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
在螢幕背景上或視窗之間的分隔線上。
HTREDUCE
8
在 “最小化 ”按鈕中。
HTRIGHT
11
在可調整大小的視窗的右邊框 (使用者可以單擊滑鼠以水準調整視窗大小) 。
HTSIZE
4
在大小框中 (與 HTGROWBOX) 相同。
HTSYSMENU
3
在視窗菜單或子視窗的 “關閉 ”按鈕中。
HTTOP
12
在視窗的上水準邊框中。
HTTOPLEFT
13
在視窗邊框的左上角。
HTTOPRIGHT
14
在視窗邊框的右上角。
HTTRANSPARENT
-1
在同一線程 (目前由另一個視窗覆寫的視窗中,消息将發送到同一線程中的基礎視窗,直到其中一個視窗傳回不是 HTTRANSPARENT) 的代碼。
HTVSCROLL
7
在垂直滾動條中。
HTZOOM
9
在 “最大化 ”按鈕中。
歡迎大家一起學習和交流。