?? 見招拆招windows程序設計(五).txt
字號:
WndProc endp
END START
字符消息
前面討論了利用位移狀態信息把按鍵消息翻譯為字符消息的方法,并且提到,僅利用轉換狀態信息還不夠,因為還需要知道與國家/地區有關的鍵盤配置。由于這個原因,您不應該試圖把按鍵消息翻譯為字符代碼。Windows會為您完成這一工作,在前面我們曾看到過以下的程序代碼:
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
這是WinMain中典型的消息循環。GetMessage函數用隊列中的下一個消息填入msg結構的字段。DispatchMessage以此消息為參數呼叫適當的窗口消息處理程序。
在這兩個函數之間是TranslateMessage函數,它將按鍵消息轉換為字符消息。如果消息為WM_KEYDOWN或者WM_SYSKEYDOWN,并且按鍵與位移狀態相組合產生一個字符,則TranslateMessage把字符消息放入消息隊列中。此字符消息將是GetMessage從消息隊列中得到的按鍵消息之后的下一個消息。
四類字符消息
字符消息可以分為四類,如表6-9所示。
表6-9
字符
死字符
非系統字符 WM_CHAR WM_DEADCHAR
系統字符 WM_SYSCHAR WM_SYSDEADCHAR
WM_CHAR和WM_DEADCHAR消息是從WM_KEYDOWN得到的;而WM_SYSCHAR和WM_SYSDEADCHAR消息是從WM_SYSKEYDOWN 消息得到的(我將簡要地討論一下什么是死字符)。
有一個好消息:在大多數情況下,Windows程序會忽略除WM_CHAR之外的任何消息。伴隨四個字符消息的lParam參數與產生字符代碼消息的按鍵消息之lParam參數相同。不過,參數wParam不是虛擬鍵碼。實際上,它是ANSI或Unicode字符代碼。
這些字符消息是我們將文字傳遞給窗口消息處理程序時遇到的第一個消息。它們不是唯一的消息,其它消息伴隨以0結尾的整個字符串。窗口消息處理程序是如何知道該字符是8位的ANSI字符還是16位的Unicode寬字符呢?很簡單:任何與您用RegisterClassA(RegisterClass的ANSI版)注冊的窗口類別相聯系的窗口消息處理程序,都會獲得含有ANSI字符代碼的消息。如果窗口消息處理程序用RegisterClassW(RegisterClass的寬字符版)注冊,那么傳遞給窗口消息處理程序的消息就帶有Unicode字符代碼。如果程序用RegisterClass注冊窗口類別,那么在UNICODE標識符被定義時就呼叫RegisterClassW,否則呼叫RegisterClassA。
除非在程序寫作的時候混合了ANSI和Unicode的函數與窗口消息處理程序,用WM_CHAR消息(及其它三種字符消息)說明的字符代碼將是:
(TCHAR) wParam
同一個窗口消息處理程序可能會用到兩個窗口類別,一個用RegisterClassA注冊,而另一個用RegisterClassW注冊。也就是說,窗口消息處理程序可能會獲得一些ANSI字符代碼消息和一些Unicode字符代碼消息。如果您的窗口消息處理程序需要曉得目前窗口是否處理Unicode消息,則它可以呼叫:
fUnicode = IsWindowUnicode (hwnd) ;
如果hwnd的窗口消息處理程序獲得Unicode消息,那么變量fUnicode將為TRUE,這表示窗口是用RegisterClassW注冊的窗口類別。
消息順序
因為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中定義的控制字符,它們用于描述這些鍵的字符代碼。
有時Windows程序將Ctrl與字母鍵的組合用作菜單快捷鍵(我將后面一期討論),此時,不會將字母鍵轉換成字符消息。
處理控制字符
處理按鍵和字符消息的基本規則是:如果需要讀取輸入到窗口的鍵盤字符,那么您可以處理WM_CHAR消息。如果需要讀取光標鍵、功能鍵、Delete、Insert、Shift、Ctrl以及Alt鍵,那么您可以處理WM_KEYDOWN消息。
但是Tab鍵怎么辦?Enter、Backspace和Escape鍵又怎么辦?傳統上,這些鍵都產生表6-13列出的ASCII控制字符。但是在Windows中,它們也產生虛擬鍵碼。這些鍵應該在處理WM_CHAR或者在處理WM_KEYDOWN期間處理嗎?
經過10年的考慮(回顧這些年來我寫過的Windows程序代碼),我更喜歡將Tab、Enter、Backspace和Escape鍵處理成控制字符,而不是虛擬鍵。我通常這樣處理WM_CHAR:
case WM_CHAR:
//其它行程序
switch (wParam)
{
case '\b': // backspace
//其它行程序
break ;
case '\t': // tab
//其它行程序
break ;
case '\n': // linefeed
//其它行程序
break ;
case '\r': // carriage return
//其它行程序
break ;
default: // character codes
//其它行程序
break ;
}
return 0 ;
死字符消息
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就是這樣的程序。
鍵盤消息和字符集
本章剩下的范例程序有缺陷。它們不能在所有版本的Windows下都正常執行。這些缺陷不是特意引過程序代碼中的;事實上,您也許永遠不會遇到這些缺陷。只有在不同的鍵盤語言和鍵盤布局間切換,以及在多字節字符集的遠東版Windows下執行程序時,這些問題才會出現-所以我不愿將它們稱為「錯誤」。
不過,如果程序使用Unicode編譯并在Windows NT下執行,那么程序會執行得更好。
KEYVIEW1程序
了解鍵盤國際化問題的第一步,就是檢查Windows傳遞給窗口消息處理程序的鍵盤內容和字符消息。程序6-2所示的KEYVIEW1會對此有所幫助。該程序在顯示區域顯示Windows向窗口消息處理程序發送的8種不同鍵盤消息的全部信息。
程序6-2 KEYVIEW1
KEYVIEW1.ASM
;MASMPlus 代碼模板 - 普通的 Windows 程序代碼
.386
.Model Flat, StdCall
Option Casemap :None
Include windows.inc
Include user32.inc
Include kernel32.inc
Include gdi32.inc
Include libc.inc
includelib gdi32.lib
IncludeLib user32.lib
IncludeLib kernel32.lib
IncludeLib msvcrt.lib
include macro.asm
WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
WndProc PROTO :DWORD,:DWORD,:DWORD,:DWORD
.DATA
szAppName db "KeyView1",0
szTop DB "Message Key Char Repeat Scan Ext ALT Prev Tran",0
szUnd DB "_______ ___ ____ ______ ____ ___ ___ ____ ____",0
szFormat0 DB "%-13s %3d %-15s",0
szFormat1 db "%-13s 0x%04X%1s%c",0
szYes DB " Yes",0
szNo DB " No ",0
szDown DB " Down",0
szUp DB " Up ",0
szMessage DB "WM_KEYDOWN ",0,
"WM_KEYUP ",0,
"WM_CHAR ",0,
"WM_DEADCHAR ",0,
"WM_SYSKEYDOWN ",0,
"WM_SYSKEYUP ",0,
"WM_SYSCHAR ",0,
"WM_SYSDEADCHAR",0
.DATA?
cLinesMax DD ?
cLines DD ?
pmsg DD ?
rectScroll RECT <>
hInstance dd ?
cxClientMax dd ?
cyClientMax dd ?
cxClient dd ?
cyClient dd ?
cxChar dd ?
cyChar dd ?
.CODE
START: ;從這里開始執行
invoke GetModuleHandle,NULL
mov hInstance,eax
invoke WinMain,hInstance,NULL,NULL,SW_SHOWDEFAULT
invoke ExitProcess,0
WinMain proc hInst:DWORD,hPrevInst:DWORD,CmdLine:DWORD,iCmdShow:DWORD
LOCAL wndclass :WNDCLASSEX
LOCAL msg :MSG
local hWnd :HWND
mov wndclass.cbSize,sizeof WNDCLASSEX
mov wndclass.style,CS_HREDRAW or CS_VREDRAW
mov wndclass.lpfnWndProc,offset WndProc
mov wndclass.cbClsExtra,0
mov
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -