滑鼠訊息與鍵盤訊息
1,虛擬鍵(VK_*)
鍵盤上每一個鍵對應一個掃描碼,掃描碼是OEM廠商制定的,不同的廠商生產的鍵盤同樣一個按鍵的掃描碼都有可能出現不一致的情況,為了擺脫由於系統裝置不一致的情況,通過鍵盤驅動程式將掃描碼對映為統一的虛擬鍵碼錶示,從而達到所有的裝置都有一個統一的虛擬鍵,比如回車鍵的虛擬鍵是VK_RETURN。
Windows定義的虛擬鍵都定義在WinUser.h這個標頭檔案裡面,都是以VK_作為字首。
2,啟用/關閉視窗對鍵盤的訊息
啟用/關閉訊息:WM_SETFOCUS/WM_KILLFOCUS
建立游標:CreateCaret(...)
設定游標位置:SetCaretPos(…)
在視窗中顯示游標:ShowCaret(…)
銷燬游標:DestroyCaret()
3,鍵盤訊息
1)字元訊息
系統字元訊息
WM_SYSCHAR:系統字元
WM_SYSDEADCHAR:系統死字元
非系統按鍵訊息
WM_CHAR:非系統字元
WM_DEADCHAR:非系統死字元
2)按鍵訊息
系統按鍵訊息:與ALT鍵相組合的組合鍵(無論使用者處理否,都需要最後呼叫DefWindowProc(hWnd,iMessage,wParam,lParam))
WM_SYSKEYDOWN
WM_SYSKEYUP
非系統按鍵訊息:
WM_KEYDOWN
WM_KEYUP
注意:
a) 除Print鍵之外都有“按下”訊息。
b) 所有鍵都存在“彈起”訊息。
c) 根據MSDN說明,只有下面這些鍵才會產生字元訊息:
- 任何字元鍵
- 回退鍵(BACKSPACE)
- 回車鍵(carriage return)
- ESC
- SHIFT + ENTER (linefeed 換行)
- TAB
我們是怎麼收到WM_CHAR的呢?就是因為我們在訊息迴圈時呼叫了TranslateMessage對鍵盤訊息進行翻譯,
如果訊息為WM_KEYDOWN或者WM_SYSKEYDOWN,並且按鍵與位移狀態相組合產生一個字元,則TranslateMessage把字元訊息放入訊息佇列中。此字元訊息將是GetMessage從訊息佇列中得到的按鍵訊息之後的下一個訊息。
在我們處理這個訊息時,對應的wParam不是虛擬鍵,而是ANSI或Unicode字元程式碼,一般情況下我們可以這樣用: (TCHAR)wParam;
4,訊息順序
因為TranslateMessage函式從WM_KEYDOWN和WM_SYSKEYDOWN訊息產生了字元訊息,所以字元訊息是夾在按鍵訊息之間傳遞給視窗訊息處理程式的。例如,如果Caps Lock未開啟,而使用者按下再釋放A鍵,則視窗訊息處理程式將接收到如表6-10所示的三個訊息:
表6-10
訊息 |
按鍵或者程式碼 |
WM_KEYDOWN |
「A」的虛擬鍵碼(0x41) |
WM_CHAR |
「a」的字元程式碼(0x61) |
WM_KEYUP |
「A」的虛擬鍵碼(0x41) |
如果您按下Shift鍵,再按下A鍵,然後釋放A鍵,再釋放Shift鍵,就會輸入大寫的A,而視窗訊息處理程式會接收到五個訊息,如表6-11所示:
表6-11
訊息 |
按鍵或者程式碼 |
WM_KEYDOWN |
虛擬鍵碼VK_SHIFT (0x10) |
WM_KEYDOWN |
「A」的虛擬鍵碼(0x41) |
WM_CHAR |
「A」的字元程式碼(0x41) |
WM_KEYUP |
「A」的虛擬鍵碼(0x41) |
WM_KEYUP |
虛擬鍵碼VK_SHIFT(0x10) |
Shift鍵本身不產生字元訊息。
如果使用者按住A鍵,以使自動重複產生一系列的按鍵,那麼對每條WM_KEYDOWN訊息,都會得到一條字元訊息,如表6-12所示:
表6-12
訊息 |
按鍵或者程式碼 |
WM_KEYDOWN |
「A」的虛擬鍵碼(0x41) |
WM_CHAR |
「a」的字元程式碼(0x61) |
WM_KEYDOWN |
「A」的虛擬鍵碼(0x41) |
WM_CHAR |
「a」的字元程式碼(0x61) |
WM_KEYDOWN |
「A」的虛擬鍵碼(0x41) |
WM_CHAR |
「a」的字元程式碼(0x61) |
WM_KEYDOWN |
「A」的虛擬鍵碼(0x41) |
WM_CHAR |
「a」的字元程式碼(0x61) |
WM_KEYUP |
「A」的虛擬鍵碼(0x41) |
如果某些WM_KEYDOWN訊息的重複計數大於1,那麼相應的WM_CHAR訊息將具有同樣的重複計數。
組合使用Ctrl鍵與字母鍵會產生從0x01(Ctrl-A)到0x1A(Ctrl-Z)的ASCII控制程式碼,其中的某些控制程式碼也可以由表6-13列出的鍵產生:
表6-13
按鍵 |
字元程式碼 |
產生方法 |
ANSI C控制字元 |
Backspace |
0x08 |
Ctrl-H |
\b |
Tab |
0x09 |
Ctrl-I |
\t |
Ctrl-Enter |
0x0A |
Ctrl-J |
\n |
Enter |
0x0D |
Ctrl-M |
\r |
Esc |
0x1B |
Ctrl-[ |
最右列給出了在ANSI C中定義的控制字元,它們用於描述這些鍵的字元程式碼。
我們一般可以這樣處理WM_CARH訊息:
case WM_CHAR:
{
switch (wParam)
{
case 0x08:
// Process a backspace.
break;
case 0x0A:
// Process a linefeed.
break;
case 0x1B:
// Process an escape.
break;
case 0x09:
// Process a tab.
break;
case 0x0D:
// Process a carriage return.
break;
default:
// Process displayable characters.
break;
}
}
我們可以在WM_CHAR裡面判斷當前是否有指定的鍵被按下:
BOOL bIsCtrl = (::GetAsyncKeyState(VK_CONTROL) & 0x8000); (MFC原始碼 afxcolordialog.cpp 460行)
或
BOOL bIsCtrl = (::GetKeyState(VK_CONTROL) & 0x8000);
下面我解釋一下鍵盤訊息的lParam引數,這個引數在MSDN上面都可以查到,只是英文,我這裡作一些簡單的說明:(以WM_KEYDOWN為例)
WPARAM:虛擬鍵值,VT_*等值。
LPARAM:根據其不同的位數表示的含義不同可以分以下幾部分:
(1) 重複計數位(0 - 15 位):表示訊息按鍵資料。一般情況下為1,當鍵一直按下,視窗過程就會連續收到W_KEYDOWN訊息,但有可能視窗過程來不及處理這些按鍵訊息,那麼Windows就會把幾個按鍵訊息組合成一個,並增加重複計數。比如你處理WM_KEYDOWN時Sleep(200),那麼得到的這個數字就可能大於1,一般可以這樣來得到這個計數:
DWORD count = (((DWORD)lParam) & 0x0000FFFF);
(2) OEM掃描碼(16~23位):OEM掃描碼是鍵盤傳送的碼值,由於此域是裝置相關的,因而此值往往被忽略。
(3) 擴充套件鍵標誌(24位):擴充套件鍵標誌在有Alt鍵(或Ctrl鍵)按下時為1,否則為0。
(4) 保留位(25~28位):保留位是系統預設保留的,一般不用。
(5) 關聯碼(29位):關聯碼用來記錄某鍵與Alt鍵的組合狀態,若按下Alt,當WM_SYSKEYDOWN訊息送到某個啟用的視窗時,其值為1,否則為0。
(6) 鍵的先前狀態(位30):鍵的先前狀態用於記錄先前某鍵的狀態,對於WM_SYSKEYUP訊息,其值始終為1。
(7) 轉換狀態(31位):轉換狀態的訊息是始終按著某鍵所產生的訊息,若某鍵原來是按下的,則其先前狀態為0。轉換狀態指示鍵被按下還是被鬆開。當鍵被按下時,對應於者WM_SYSKEYDOWN訊息,其值始終為0,當鍵被鬆開時,其轉換狀態為1,對應於WM_SYSKEYUP訊息,其值始終為1。
5,死字元訊息
Windows程式經常忽略WM_DEADCHAR和WM_SYSDEADCHAR訊息,但您應該明確地知道死字元是什麼,以及它們工作的方式。
在某些非U.S.英語鍵盤上,有些鍵用於給字母加上音調。因為它們本身不產生字元,所以稱之為「死鍵」。例如,使用德語鍵盤時,對於U.S.鍵盤上的+/=鍵,德語鍵盤的對應位置就是一個死鍵,未按下Shift鍵時它用於標識銳音,按下Shift鍵時則用於標識抑音。
當使用者按下這個死鍵時,視窗訊息處理程式接收到一個wParam等於音調本身的ASCII或者Unicode程式碼的WM_DEADCHAR訊息。當使用者再按下可以帶有此音調的字母鍵(例如A鍵)時,視窗訊息處理程式會接收到WM_CHAR訊息,其中wParam等於帶有音調的字母「a」的ANSI程式碼。
因此,使用者程式不需要處理WM_DEADCHAR訊息,原因是WM_CHAR訊息已含有程式所需要的所有資訊。Windows的做法甚至還設計了內部錯誤處理。如果在死鍵之後跟有不能帶此音調符號的字母(例如「s」),那麼視窗訊息處理程式將在一行接收到兩條WM_CHAR訊息-前一個訊息的wParam等於音調符號本身的ASCII程式碼(與傳遞到WM_DEADCHAR訊息的wParam值相同),第二個訊息的wParam等於字母s的ASCII程式碼。
當然,要感受這種做法的運作方式,最好的方法就是實際操作。您必須載入使用死鍵的外語鍵盤,例如前面講過的德語鍵盤。您可以這樣設定:在「控制檯」中選擇「鍵盤」,然後選擇「語系」頁面標籤。然後您需要一個應用程式,該程式可以顯示它接收的每一個鍵盤訊息的詳細資訊。下面的KEYVIEW1就是這樣的程式。
——————————————————————————————————
怎麼判斷髮來訊息時候,是一次新的按鍵,還是原來的鍵已經被按下連續傳送的?
if(lparam&0x40000000)
新按鍵
解釋如下:
0x40000000是16進位制,轉成2進位制是:01000000000000000000000000000000 注意第30位是1哦
而 if(lParam&0x40000000)判斷lParam&0x40000000是否為0,你想想,如果lParam&0x40000000
為0那就證明lParam的第30位是0。為什麼?因為只有lParam的第30位是0,才能和0x40000000
的第30位的1通過位與運算得到0,否則,結果就非0。
所以做按位與運算就能知道lParam的第30位是什麼內容了。
if(lParam&0x40000000){
.
.
.
}
其實這段程式,是說當lParam的第30位是1的時候,執行以下程式碼。
同上。
詳細一點,第 30 位是表示這個鍵的之前狀態(previous state)是否按下。也就是說,發這個訊息來之前,這個鍵是按下的還是鬆開的。因為在Windows裡面,你按下某個鍵的時候,系統會發送一個訊息;而你按住某個鍵的時候,系統也會不斷地傳送這個訊息。為了區分這個訊息是使用者按下某個鍵還是按住某個鍵而產生的,就要用到這一位了。在做遊戲時應該就要用到這個功能了~~~不過具體該位是用 1 還是 0 表示之前是鬆開狀態的就不清楚了,樓主自己試試。