?? 見招拆招windows程序設計(五).txt
字號:
見招拆招Windows程序設計(五) 作者:Zoologist 于2008-1-17上傳
--------------------------------------------------------------------------------
本文相關的例子:下載>>>
在Microsoft Windows中,鍵盤和鼠標是兩個標準的使用者輸入來源,在一些連貫操作中常產生互補作用。當然,鼠標在今天的應用程序中比十年前使用得更為廣泛。甚至在一些應用程序中,我們更習慣于使用鼠標,例如在游戲、畫圖程序、音樂程序以及Web瀏覽器等程序中就是這樣。我曾經在PS上玩即時戰略游戲《紅色警戒》,還有在PSP上玩FPS的《榮譽勛章》,很快就發現這是一場噩夢。我們可以不使用鼠標,但絕對不能從一般的PC中把鍵盤拆掉。你完全可以在沒有鼠標的情況下操作標準的Windows程序,這也是操作系統設計上注意到的細節。
相對于個人計算機的其它組件,鍵盤有非常久遠的歷史,它起源于1874年的第一臺Remington打字機。早期的計算機程序員用鍵盤在Hollerith卡片上打孔,后來在終端機上用鍵盤直接與大型主機溝通。PC上的鍵盤在某些方面進行了擴充,加上了功能鍵、光標移動鍵和單獨的數字鍵盤,但它們的輸入原理基本相同。
有興趣的讀者不妨查閱一下現在的標準鍵盤“QWERT”是怎么來的,不合理的鍵位設計居然是特意為了“降低打字速度”……
鍵盤基礎
您大概已經猜到Windows程序是如何獲得鍵盤輸入的:鍵盤輸入以消息的形式傳遞給程序的窗口消息處理程序。實際上,第一次學習消息時,鍵盤事件就是一個消息如何將不同型態信息傳遞給應用程序的顯例。
Windows用八種不同的消息來傳遞不同的鍵盤事件。這好像太多了,但是(就像我們所看到的一樣)程序可以忽略其中至少一半的消息而不會有任何問題。并且,在大多數情況下,這些消息中包含的鍵盤信息會多于程序所需要的。處理鍵盤的部分工作就是識別出哪些消息是重要的,哪些是不重要的。
忽略鍵盤
雖然鍵盤是Windows程序中使用者輸入的主要來源,但是程序不必對它接收的所有消息都作出響應。Windows本身也能處理許多鍵盤功能。
例如,您可以忽略那些屬于系統功能的按鍵,它們通常用到Alt鍵。程序不必監視這些按鍵,因為Windows會將按鍵的作用通知程序(當然,如果程序想這么做,它也能監視這些按鍵)。雖然呼叫程序菜單的按鍵將通過窗口的窗口消息處理程序,但通常內定的處理方式是將按鍵傳遞給DefWindowProc。最終,窗口消息處理程序將獲得一個消息,表示一個菜單項被選擇了。通常,這是所有窗口消息處理程序需要知道的(在后面一期介紹菜單)。
有些Windows程序使用「鍵盤快捷鍵」來啟動通用菜單項。快捷鍵通常是功能鍵或字母同Ctrl鍵的組合(例如,Ctrl-S用于保存文件)。這些鍵盤快捷鍵與程序菜單一起在程序的資源描述文件中定義。Windows將這些鍵盤快捷鍵轉換為菜單命令消息,您不必自己去進行轉換。
對話框也有鍵盤接口,但是當對話框處于活動狀態時,應用程序通常不必監視鍵盤。鍵盤接口由Windows處理,Windows把關于按鍵作用的消息發送給程序。對話框可以包含用于輸入文字的編輯控件。它們一般是小方框,使用者可以在框中鍵入字符串。Windows處理所有編輯控件邏輯,并在輸入完畢后,將編輯控件的最終內容傳送給程序。
編輯控件不必局限于單獨一行,而且也不限于只在對話框中。一個在程序主窗口內的多行編輯控件就能夠作為一個簡單的文字編輯器了(后面我們會設計一個POPPAD程序)。Windows甚至有一個Rich Text文字編輯控件,允許您編輯和顯示格式化的文字(Rich Edit Controls)。
您將會發現,在開發Windows程序時,可以使用處理鍵盤和鼠標輸入的子窗口控件來將較高層的信息傳遞回父窗口。只要這樣的控件用得夠多,您就不會因處理鍵盤消息而煩惱了。
誰獲得了焦點
與所有的個人計算機硬件一樣,鍵盤必須由在Windows下執行的所有應用程序共享。有些應用程序可能有多個窗口,鍵盤必須由該應用程序內的所有窗口共享。
回想一下,程序用來從消息隊列中檢索消息的MSG結構包括hwnd字段。此字段指出接收消息的窗口控件碼。消息循環中的DispatchMessage函數向窗口消息處理程序發送該消息,此窗口消息處理程序與需要消息的窗口相聯系。在按下鍵盤上的鍵時,只有一個窗口消息處理程序接收鍵盤消息,并且此消息包括接收消息的窗口控件碼。
接收特定鍵盤事件的窗口具有輸入焦點。輸入焦點的概念與活動窗口的概念很相近。有輸入焦點的窗口是活動窗口或活動窗口的衍生窗口(活動窗口的子窗口,或者活動窗口子窗口的子窗口等等)。
通常很容易辨別活動窗口。它通常是頂層窗口-也就是說,它的父窗口句柄是NULL。如果活動窗口有標題列,Windows將突出顯示標題列。如果活動窗口具有對話框架(對話框中很常見的格式)而不是標題列,Windows將突出顯示框架。如果活動窗口目前是最小化的,Windows將在工作列中突出顯示該項,其顯示就像一個按下的按鈕。
如果活動窗口有子窗口,那么有輸入焦點的窗口既可以是活動窗口也可以是其子窗口。最常見的子窗口有類似以下控件:出現在對話框中的下壓按鈕、單選鈕、復選框、滾動條、編輯方塊和清單方塊。子窗口不能自己成為活動窗口。只有當它是活動窗口的衍生窗口時,子窗口才能有輸入焦點。子窗口控件一般通過顯示一個閃爍的插入符號或虛線來表示它具有輸入焦點。
有時輸入焦點不在任何窗口中。這種情況發生在所有程序都是最小化的時候。這時,Windows將繼續向活動窗口發送鍵盤消息,但是這些消息與發送給非最小化的活動窗口的鍵盤消息有不同的形式。
窗口消息處理程序通過攔截WM_SETFOCUS和WM_KILLFOCUS消息來判定它的窗口何時擁有輸入焦點。WM_SETFOCUS指示窗口正在得到輸入焦點,WM_KILLFOCUS表示窗口正在失去輸入焦點。我將在本期的后面詳細說明這些消息。
隊列和同步
當使用者按下并釋放鍵盤上的鍵時,Windows和鍵盤驅動程序將硬件掃描碼轉換為格式消息。然而,這些消息并不保存在消息隊列中。實際上,Windows在所謂的「系統消息隊列」中保存這些消息。系統消息隊列是獨立的消息隊列,它由Windows維護,用于初步保存使用者從鍵盤和鼠標輸入的信息。只有當Windows應用程序處理完前一個使用者輸入消息時,Windows才會從系統消息隊列中取出下一個消息,并將其放入應用程序的消息隊列中。
此過程分為兩步:首先在系統消息隊列中保存消息,然后將它們放入應用程序的消息隊列,其原因是需要同步。就像我們剛才所學的,假定接收鍵盤輸入的窗口就是有輸入焦點的窗口。使用者的輸入速度可能比應用程序處理按鍵的速度快,并且特定的按鍵可能會使焦點從一個窗口切換到另一個窗口,后來的按鍵就輸入到了另一個窗口。但如果后來的按鍵已經記下了目標窗口的地址,并放入了應用程序消息隊列,那么后來的按鍵就不能輸入到另一個窗口。
按鍵和字符
應用程序從Windows接收的關于鍵盤事件的消息可以分為按鍵和字符兩類,這與您看待鍵盤的兩種方式一致。
首先,您可以將鍵盤看作是鍵的集合。鍵盤只有唯一的A鍵,按下該鍵是一次按鍵,釋放該鍵也是一次按鍵。但是鍵盤也是能產生可顯示字符或控制字符的輸入設備。根據Ctrl、 Shift和Caps Lock鍵的狀態,A鍵能產生幾個字符。通常情況下,此字符為小寫a。如果按下Shift鍵或者打開了Caps Lock,則該字符就變成大寫A。如果按下了Ctrl,則該字符為Ctrl-A(它在ASCII中有意義,但在Windows中可能是某事件的鍵盤快捷鍵)。在一些鍵盤上,A按鍵之前可能有「死字符鍵(dead-character key)」或者Shift、Ctrl或者Alt的不同組合,這些組合可以產生帶有音調標記的小寫或者大寫,例如,à、á等等......
對產生可顯示字符的按鍵組合,Windows不僅給程序發送按鍵消息,而且還發送字符消息。有些鍵不產生字符,這些鍵包括shift鍵、功能鍵、光標移動鍵和特殊字符鍵如Insert和Delete。對于這些鍵,Windows只產生按鍵消息。
按鍵消息
當您按下一個鍵時,Windows把WM_KEYDOWN或者WM_SYSKEYDOWN消息放入有輸入焦點的窗口的消息隊列;當您釋放一個鍵時,Windows把WM_KEYUP或者WM_SYSKEYUP消息放入消息隊列中。
表6-1
鍵按下
鍵釋放
非系統鍵 WM_KEYDOWN WM_KEYUP
系統鍵 WM_SYSKEYDOWN WM_SYSKEYUP
通常「down(按下)」和「up(放開)」消息是成對出現的。不過,如果您按住一個鍵使得自動重復功能生效,那么當該鍵最后被釋放時,Windows會給窗口消息處理程序發送一系列WM_KEYDOWN(或者WM_SYSKEYDOWN)消息和一個WM_KEYUP(或者WM_SYSKEYUP)消息。像所有放入隊列的消息一樣,按鍵消息也有時間信息。通過呼叫GetMessageTime,您可以獲得按下或者釋放鍵的相對時間。
系統按鍵與非系統按鍵
WM_SYSKEYDOWN和WM_SYSKEYUP中的「SYS」代表「系統」,它表示該按鍵對Windows比對Windows應用程序更加重要。WM_SYSKEYDOWN和WM_SYSKEYUP消息經常由與Alt相組合的按鍵產生,這些按鍵啟動程序菜單或者系統菜單上的選項,或者用于切換活動窗口等系統功能(Alt-Tab或者Alt-Esc),也可以用作系統菜單快捷鍵(Alt鍵與一個功能鍵相結合,例如Alt-F4用于關閉應用程序)。程序通常忽略WM_SYSKEYUP和WM_SYSKEYDOWN消息,并將它們傳送到DefWindowProc。由于Windows要處理所有Alt鍵的功能,所以您無需攔截這些消息。您的窗口消息處理程序將最后收到關于這些按鍵結果(如菜單選擇)的其它消息。如果您想在自己的窗口消息處理程序中加上攔截系統按鍵的程序代碼(如本期后面的KEYVIEW1和KEYVIEW2程序所作的那樣),那么在處理這些消息之后再傳送到DefWindowProc,Windows就仍然可以將它們用于通常的目的。
但是,請再考慮一下,幾乎所有會影響使用者程序窗口的消息都會先通過使用者窗口消息處理程序。只有使用者把消息傳送到DefWindowProc,Windows才會對消息進行處理。例如,如果您將下面幾行敘述:
case WM_SYSKEYDOWN:
case WM_SYSKEYUP:
caseWM_SYSCHAR:
return 0 ;
加入到一個窗口消息處理程序中,那么當您的程序主窗口擁有輸入焦點時,就可以有效地阻止所有Alt鍵操作(我將在本章的后面討論WM_SYSCHAR),其中包括Alt-Tab、Alt-Esc以及菜單操作。雖然我懷疑您會這么做,但是,我相信您會感到窗口消息處理程序的強大功能。
WM_KEYDOWN和WM_KEYUP消息通常是在按下或者釋放不帶Alt鍵的鍵時產生的,您的程序可以使用或者忽略這些消息,Windows本身并不處理這些消息。
對所有四類按鍵消息,wParam是虛擬鍵代碼,表示按下或釋放的鍵,而lParam則包含屬于按鍵的其它數據。
虛擬鍵碼
虛擬鍵碼保存在WM_KEYDOWN、WM_KEYUP、WM_SYSKEYDOWN和WM_SYSKEYUP消息的wParam參數中。此代碼標識按下或釋放的鍵。
哈,又是「虛擬」,您喜歡這個詞嗎?虛擬指的是假定存在于思想中而不是現實世界中的一些事物,也只有熟練使用DOS匯編語言編寫應用程序的程序寫作者才有可能指出,為什么對Windows鍵盤處理如此基本的鍵碼是虛擬的而不是真實的。
對于早期的程序寫作者來說,真實的鍵碼由實際鍵盤硬件產生。在Windows文件中將這些鍵碼稱為「掃描碼(scan codes)」。在IBM兼容機種上,掃描碼16是Q鍵,17是W鍵,18是E、19是R,20是T,21是Y等等。這時您會發現,掃描碼是依據鍵盤的實際布局的。Windows開發者認為這些代碼過于與設備相關了,于是他們試圖通過定義所謂的虛擬鍵碼,以便經由與設備無關的方式處理鍵盤。其中一些虛擬鍵碼不能在IBM兼容機種上產生,但可能會在其它制造商生產的鍵盤中找到,或者在未來的鍵盤上找到。
您使用的大多數虛擬鍵碼的名稱在WINUSER.H表頭文件中都定義為以VK_開頭。表6-2列出了這些名稱和數值(十進制和十六進制),以及與虛擬鍵相對應的IBM兼容機種鍵盤上的鍵。下表也標出了Windows執行時是否需要這些鍵。下表還按數字順序列出了虛擬鍵碼。
前四個虛擬鍵碼中有三個指的是鼠標鍵:
表6-2
十進制
十六進制
Windows.inc標識符
必需?
IBM兼容鍵盤
1 01 VK_LBUTTON 鼠標左鍵
2 02 VK_RBUTTON 鼠標右鍵
3 03 VK_CANCEL ˇ Ctrl-Break
4 04 VK_MBUTTON 鼠標中鍵
您永遠都不會從鍵盤消息中獲得這些鼠標鍵代碼。在后一期可以看到,我們能夠從鼠標消息中獲得它們。VK_CANCEL代碼是一個虛擬鍵碼,它包括同時按下兩個鍵(Ctrl-Break)。Windows應用程序通常不使用此鍵。
表6-3中的鍵--Backspace、Tab、Enter、Escape和Spacebar-通常用于Windows程序。不過,Windows一般用字符消息(而不是鍵盤消息)來處理這些鍵。
表6-3
十進制
十六進制
Windows.inc標識符
必需?
IBM兼容鍵盤
8 08 VK_BACK ˇ Backspace
9 09 VK_TAB ˇ Tab
12 0C VK_CLEAR Num Lock關閉時的數字鍵盤5
13 0D VK_RETURN ˇ Enter (或者另一個)
16 10 VK_SHIFT ˇ Shift (或者另一個)
17 11 VK_CONTROL ˇ Ctrl (或者另一個)
18 12 VK_MENU ˇ Alt (或者另一個)
19 13 VK_PAUSE Pause
20 14 VK_CAPITAL ˇ Caps Lock
27 1B VK_ESCAPE ˇ Esc
32 20 VK_SPACE ˇ Spacebar
另外,Windows程序通常不需要監視Shift、Ctrl或Alt鍵的狀態。
表6-4列出的前八個碼可能是與VK_INSERT和VK_DELETE一起最常用的虛擬鍵碼:
表6-4
十進制
十六進制
Windows.inc標識符
必需?
IBM兼容鍵盤
33 21 VK_PRIOR ˇ Page Up
34 22 VK_NEXT ˇ Page Down
35 23 VK_END ˇ End
36 24 VK_HOME ˇ Home
37 25 VK_LEFT ˇ 左箭頭
38 26 VK_UP ˇ 上箭頭
39 27 VK_RIGHT ˇ 右箭頭
40 28 VK_DOWN ˇ 下箭頭
41 29 VK_SELECT
42 2A VK_PRINT
43 2B VK_EXECUTE
44 2C VK_SNAPSHOT Print Screen
45 2D VK_INSERT ˇ Insert
46 2E VK_DELETE ˇ Delete
47 2F VK_HELP
注意,許多名稱(例如VK_PRIOR和VK_NEXT)都與鍵上的標志不同,而且也與滾動條中的標識符不統一。Print Screen鍵在平時都被Windows應用程序所忽略。Windows本身響應此鍵時會將視頻顯示的位圖拷貝存放到剪貼板中。假使有鍵盤提供了VK_SELECT、VK_PRINT、VK_EXECUTE和VK_HELP,大概也沒幾個人看過那樣的鍵盤。
Windows也包括在主鍵盤上的字母和數字鍵的虛擬鍵碼(數字鍵盤將單獨處理)。
表6-5
十進制
十六進制
Windows.inc標識符
必需?
IBM兼容鍵盤
48-57 30-39 無 ˇ 主鍵盤上的0到9
65-90 41-5A 無 ˇ A到Z
注意,數字和字母的虛擬鍵碼是ASCII碼。Windows程序幾乎從不使用這些虛擬鍵碼;實際上,程序使用的是ASCII碼字符的字符消息。
表6-6所示的代碼是由Microsoft Natural Keyboard及其兼容鍵盤產生的:
表6-6
十進制
十六進制
Windows.inc標識符
必需?
IBM兼容鍵盤
91 5B VK_LWIN 左Windows鍵
92 5C VK_RWIN 右Windows鍵
93 5D VK_APPS Applications鍵
Windows用VK_LWIN和VK_RWIN鍵打開「開始」菜單。這兩個都可以用于登錄或注銷Windows(只在Microsoft Windows NT中有效),或者登錄或注銷網絡(在Windows for Applications中)。應用程序能夠通過顯示輔助信息或者當成快捷方式鍵看待來處理application鍵。
表6-7所示的代碼用于數字鍵盤上的鍵(如果有的話):
表6-7
十進制
十六進制
Windows.inc標識符
必需?
IBM兼容鍵盤
96-105 60-69 VK_NUMPAD0到VK_ NUMPAD9 NumLock打開時數字鍵盤上的0到9
106 6A VK_MULTIPLY 數字鍵盤上的*
107 6B VK_ADD 數字鍵盤上的+
108 6C VK_SEPARATOR
109 6D VK_SUBTRACT 數字鍵盤上的-
110 6E VK_DECIMAL 數字鍵盤上的.
111 6F VK_DIVIDE 數字鍵盤上的/
最后,雖然多數的鍵盤都有12個功能鍵,但Windows只需要10個,而位旗標卻有24個。另外,程序通常用功能鍵作為鍵盤快捷鍵,這樣,它們通常不處理表6-8所示的按鍵:
表6-8
十進制
十六進制
Windows.inc標識符
必需?
IBM兼容鍵盤
112-121 70-79 VK_F1到VK_F10 ˇ 功能鍵F1到F10
122-135 7A-87 VK_F11到VK_F24 功能鍵F11到F24
144 90 VK_NUMLOCK Num Lock
145 91 VK_SCROLL Scroll Lock
另外,還定義了一些其它虛擬鍵碼,但它們只用于非標準鍵盤上的鍵,或者通常在大型主機終端機上使用的鍵。
lParam信息
在四個按鍵消息(WM_KEYDOWN、WM_KEYUP、WM_SYSKEYDOWN和WM_SYSKEYUP)中,wParam消息參數含有上面所討論的虛擬鍵碼,而lParam消息參數則含有對了解按鍵非常有用的其它信息。lParam的32位分為6個字段,如圖6-1所示。
重復計數
重復計數是該消息所表示的按鍵次數,大多數情況下,重復計數設定為1。不過,如果按下一個鍵之后,您的窗口消息處理程序不夠快,以致不能處理自動重復速率(您可以在「控制面板」的「鍵盤」中進行設定)下的按鍵消息,Windows就把幾個WM_KEYDOWN或者WM_SYSKEYDOWN消息組合到單個消息中,并相應地增加重復計數。WM_KEYUP或WM_SYSKEYUP消息的重復計數總是為1。
因為重復計數大于1指示按鍵速率大于您程序的處理能力,所以您也可能想在處理鍵盤消息時忽略重復計數。幾乎每個人都有文字處理或執行電子表格時畫面卷過頭的經驗,因為多余的按鍵堆滿了鍵盤緩沖區,所以當程序用一些時間來處理每一次按鍵時,如果忽略您程序中的重復計數,就能夠解決此問題。不過,有時可能也會用到重復計數,您應該嘗試使用兩種方法執行程序,并從中找出一種較好的方法。
OEM掃描碼
OEM掃描碼是由硬件(鍵盤)產生的代碼。這對中古時代的匯編程序寫作者來說應該很熟悉,它是從PC相容機種的ROM BIOS服務中所獲得的值(OEM指的是PC的原始設備制造商(Original Equipment Manufacturer)及其與「IBM標準」同步的內容)。在此我們不需要更多的信息。除非需要依賴實際鍵盤布局的樣貌,不然Windows程序可以忽略掉幾乎所有的OEM掃描碼信。
擴充鍵旗標
如果按鍵結果來自IBM增強鍵盤的附加鍵之一,那么擴充鍵旗標為1(IBM增強型鍵盤有101或102個鍵。功能鍵在鍵盤頂端,光標移動鍵從數字鍵盤中分離出來,但在數字鍵盤上還保留有光標移動鍵的功能)。對鍵盤右端的Alt和Ctrl鍵,以及不是數字鍵盤那部分的光標移動鍵(包括Insert和Delete鍵)、數字鍵盤上的斜線(/)和Enter鍵以及Num Lock鍵等,此旗標均被設定為1。Windows程序通常忽略擴充鍵旗標。
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -