響應鍵盤事件
------------------------
在開始學習有關鍵盤事件的知識前首先來看一段代碼(回調函數這部分的代碼):
1 LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ) 2 { 3 HDC hdc ; 4 PAINTSTRUCT ps ; 5 6 switch( message ) 7 { 8 case WM_PAINT: 9 hdc = BeginPaint( hwnd, &ps ) ; 10 EndPaint( hwnd, &ps ) ; 11 return 0 ; 12 13 case WM_KEYDOWN: //鍵盤按鍵被按下 14 switch(wParam) 15 { 16 case VK_UP: //方向鍵上 17 MessageBox( hwnd, TEXT("你按下了方向鍵 上"), TEXT("鍵盤消息"), MB_OK ) ; 18 break ; 19 case VK_DOWN: //方向鍵下 20 MessageBox( hwnd, TEXT("你按下了方向鍵 下"), TEXT("鍵盤消息"), MB_OK ) ; 21 break ; 22 case VK_LEFT: //方向鍵左 23 MessageBox( hwnd, TEXT("你按下了方向鍵 左"), TEXT("鍵盤消息"), MB_OK ) ; 24 break ; 25 case VK_RIGHT: //方向鍵右 26 MessageBox( hwnd, TEXT("你按下了方向鍵 右"), TEXT("鍵盤消息"), MB_OK ) ; 27 break ; 28 } 29 return 0 ; 30 31 case WM_DESTROY: 32 PostQuitMessage( 0 ) ; 33 return 0 ; 34 } 35 36 return DefWindowProc( hwnd, message, wParam, lParam ) ; 37 }
完整的示例代碼:

1 #include<windows.h> 2 3 LRESULT CALLBACK WndProc( HWND, UINT, WPARAM, LPARAM ) ; 4 5 int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow ) 6 { 7 static TCHAR szAppName[] = TEXT( "UseKeyboard" ) ; 8 HWND hwnd ; 9 MSG msg ; 10 WNDCLASS wndclass ; 11 12 wndclass.style = CS_HREDRAW | CS_VREDRAW ; 13 wndclass.lpfnWndProc = WndProc ; 14 wndclass.cbClsExtra = 0 ; 15 wndclass.cbWndExtra = 0 ; 16 wndclass.hbrBackground = (HBRUSH) GetStockObject( WHITE_BRUSH ) ; 17 wndclass.hCursor = LoadCursor( NULL, IDC_ARROW ) ; 18 wndclass.hIcon = LoadIcon( NULL, IDI_APPLICATION ) ; 19 wndclass.lpszMenuName = NULL ; 20 wndclass.hInstance = hInstance ; 21 wndclass.lpszClassName = szAppName ; 22 23 if( !RegisterClass( &wndclass ) ) 24 { 25 MessageBox( NULL, TEXT("窗口類注冊失敗!"), TEXT("錯誤"), MB_OK ) ; 26 return 0 ; 27 } 28 29 hwnd = CreateWindow( szAppName, TEXT("響應鍵盤事件"), 30 WS_OVERLAPPEDWINDOW, 31 CW_USEDEFAULT, CW_USEDEFAULT, 32 CW_USEDEFAULT, CW_USEDEFAULT, 33 NULL, NULL, hInstance, NULL 34 ); 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 45 return msg.wParam ; 46 } 47 48 LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ) 49 { 50 HDC hdc ; 51 PAINTSTRUCT ps ; 52 53 switch( message ) 54 { 55 case WM_PAINT: 56 hdc = BeginPaint( hwnd, &ps ) ; 57 EndPaint( hwnd, &ps ) ; 58 return 0 ; 59 60 case WM_KEYDOWN: 61 switch(wParam) 62 { 63 case VK_UP: 64 MessageBox( hwnd, TEXT("你按下了方向鍵 上"), TEXT("鍵盤消息"), MB_OK ) ; 65 break ; 66 case VK_DOWN: 67 MessageBox( hwnd, TEXT("你按下了方向鍵 下"), TEXT("鍵盤消息"), MB_OK ) ; 68 break ; 69 case VK_LEFT: 70 MessageBox( hwnd, TEXT("你按下了方向鍵 左"), TEXT("鍵盤消息"), MB_OK ) ; 71 break ; 72 case VK_RIGHT: 73 MessageBox( hwnd, TEXT("你按下了方向鍵 右"), TEXT("鍵盤消息"), MB_OK ) ; 74 break ; 75 } 76 return 0 ; 77 78 case WM_DESTROY: 79 PostQuitMessage( 0 ) ; 80 return 0 ; 81 } 82 83 return DefWindowProc( hwnd, message, wParam, lParam ) ; 84 }
在這段代碼中, 編譯運行如果沒有錯誤的話當你在按下方向鍵上下左右時應該都會彈出一個對話框, 重點看一下case WM_KEYDOWN:部分的代碼:
case WM_KEYDOWN: switch(wParam) { case VK_UP: MessageBox( hwnd, TEXT("你按下了方向鍵 上"), TEXT("鍵盤消息"), MB_OK ) ; break ; case VK_DOWN: MessageBox( hwnd, TEXT("你按下了方向鍵 下"), TEXT("鍵盤消息"), MB_OK ) ; break ; case VK_LEFT: MessageBox( hwnd, TEXT("你按下了方向鍵 左"), TEXT("鍵盤消息"), MB_OK ) ; break ; case VK_RIGHT: MessageBox( hwnd, TEXT("你按下了方向鍵 右"), TEXT("鍵盤消息"), MB_OK ) ; break ; } return 0 ;
如果對前一部分中提到的"Windows向應用程序發送了一條消息"有較為深刻的理解的話, 那么此時你應該能夠明白了, 所謂的響應鍵盤的按鍵事件不過也是處理系統發來的按鍵消息罷了。
當然, 的確是這樣, 當有按鍵被按下時系統就會向應用程序發送一個按鍵被按下的消息, 在發來的消息中的wParam字段中包涵有有關按鍵被按下的必要信息, 正如上面代碼中寫到的那樣, 首先程序先case到一個按鍵被按下的消息WM_KEYDOWN, 然后再進一步處理到底是哪個按鍵被按下了, 於是就嵌套一個switch語句, 通過wParam中的信息判斷到底是哪個按鍵被按下了, 當信息為VK_UP時就說明是方向鍵上被按下了, 這樣我們就可以根據這樣消息作出相應的動作, 在這個示例中, 程序是通過顯示一個對話框"你按下了方向鍵 上"來響應方向鍵上的, 同樣, 你還可以同理推導出其他按鍵的響應方式, 這只是我們所看到的表面現象, 既然學習還是要更進一步了解下響應按鍵的具體情況。
一、這條消息屬於誰?
通常來說, 一台電腦上一般只配有一個鍵盤, 而運行在Windows系統上的應用程序卻有很多, 也就是說, 除了你的應用程序外還有其他一些應用程序在和你共用這個鍵盤, 那么系統是如何知道當前用戶的按下的按鍵是針對你的應用程序的呢?
Windows通過一個被稱為"輸入焦點"的名詞來確定該消息屬於哪個窗口, 接收到這個鍵盤事件的窗口稱為有輸入焦點的窗口。一個具有輸入焦點的窗口必須是活動窗口或者活動窗口的子孫窗口。
何為活動窗口? 活動窗口很容易鑒別, 系統會對這個窗口的標題框或者窗口邊框進行加亮提示, 同樣會在任務欄突出顯示, 圖示如下:
當活動窗口有子窗口時, 具有輸入焦點的窗口可以是活動窗口, 也可能是他的子孫窗口中的一個, 例如當一個窗口中有許多控件(按鈕、輸入框、文本框、編輯框、滾動條等), 這些控件通常通過顯示一個虛線框或插入符號以及閃爍的光標等表示自己獲得了輸入焦點。
在程序中, 可以通過捕獲WM_SETFOCUS和WM_KILLFOCUS來確定自己的窗口是否獲得了焦點。
當程序最小化到系統托盤時, 系統同樣能夠將消息發送給程序, 只不過此時消息的形式與發給活動窗口時不同。
二、隊列和同步
當一個按鍵被按下時到你的應用程序得到這個消息期間需要經過一個"中轉站", 這個中轉站就是系統的消息隊列, 也就是說, 當用戶按下按鍵后, Windwows和鍵盤設備的驅動程序將硬件的掃描碼轉化成格式化后的消息, 但這個消息並不會直接發送給應用程序的消息隊列, 而是存儲在系統的一個獨立的消息隊列當中, 這個系統的消息隊列注要用來存儲用戶從鍵盤和鼠標輸入的消息。
為什么要這樣做? 可以想象一下, 當系統這會正在運行許多的應用程序導致系統超負荷運作時, 恰好你的程序也在被運行並且獲取了輸入焦點, 用戶正好要使用你的程序完成一些操作, 但是用戶的輸入速度快於此時系統處理的速度, 也就是說如果不先存放在系統的消息隊列中那么此時用戶的輸入已經大於你的程序的處理速度了, 當鍵盤上某一個特殊的按鍵被按下時系統可能會將輸入焦點轉換到另一個窗口上, 后面的操作系統會認為用戶的擊鍵操作已經屬於另一個窗口。當先存放在系統的隊列, 再把這些消息發給這些消息發給當前活動的窗口就可以避免消息接收混亂的情況。
三、擊鍵消息
當一個按鍵被按下時, Windows將WM_KEYDOWN或WM_SYSKEYDOWN消息放進具有輸入焦點的窗口的消息隊列中, 當按鍵被釋放時, Windows又會把WM_KWYUP或WM_SYSKEYUP放進當前具有輸入焦點的消息隊列中。
具體是WM_KEYDOWN還是WM_SYSKEYDOWN要看擊鍵的類型, 擊鍵分為"系統擊鍵"和"非系統擊鍵", 帶上"SYS"標識符的表示為系統擊鍵, 與非系統擊鍵的區別是系統擊鍵表示該擊鍵對於Windows系統更重要, 例如, 當使用Alt鍵與其他輸入按鍵進行組合時產生的消息即為系統擊鍵消息, 這些消息一般具有特殊的用途, 例如轉換活動窗口: Alt + Tab或Alt + Esc, 關閉當前活動的應用程序, Alt + F4等。
對於這些系統擊鍵消息一般是交給DefWindowProc即默認的消息處理函數處理, 當然, 我們也可以自己處理這些系統擊鍵消息, 處理的方法同普通消息相同, 例如阻止所有系統擊鍵操作:
case WM_SYSKEYDOWN: case WM_SYSKEYUP: case WM_SYSCHAR: return 0 ;
四、虛擬鍵代碼
虛擬鍵代碼存儲在WM_KEYDOWN、WM_KEYUP、WM_SYSKEYDOWN、WM_SYSKEYUP消息的wParam參數中, 常用的虛擬鍵代碼如下:
十進制 | 十六進制 | WINUSER.H中的標識符 | 是否必須 | IBM兼容鍵盤 | ||
1 | 1 | VK_LBUTTON | 鼠標左鍵 | |||
2 | 2 | VK_RBUTTON | 鼠標右鍵 | |||
3 | 3 | VK_CANCEL | √ | Ctrl + Break | ||
4 | 4 | VK_MBUTTON | 鼠標中鍵 | |||
8 | 8 | VK_BACK | √ | 退格鍵 | ||
9 | 9 | VK_TAB | √ | Tab鍵 | ||
12 | C | VK_CLEAR | Clear鍵 | |||
13 | D | VK_RETURN | √ | 回車鍵(任意一個) | ||
16 | 10 | VK_SHIFT | √ | Shift鍵(任意一個) | ||
17 | 11 | VK_CONTROL | √ | Ctr鍵(任意一個) | ||
18 | 12 | VK_MENU | √ | Alt鍵(任意一個) | ||
19 | 13 | VK_PAUSE | Pause鍵 | |||
20 | 14 | VK_CAPITAL | √ | 大寫鎖定鍵 | ||
27 | 1B | VK_ESCAPE | √ | Esc鍵 | ||
32 | 20 | VK_SPACE | √ | 空格鍵 | ||
33 | 21 | VK_PRIOR | √ | PageUp鍵 | ||
34 | 22 | VK_NEXT | √ | PageDown鍵 | ||
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_PAINT | ||||
43 | 2B | VK_EXECUTE | ||||
44 | 2C | VK_SNAPSHOT | Paint Screen鍵 | |||
45 | 2D | VK_INSERT | √ | Insert鍵 | ||
46 | 2E | VK_DELETE | √ | Del鍵 | ||
47 | 2F | VK_HELP | ||||
48 - 57 | 30 - 39 | 無 | √ | 主鍵盤上的0到9 | ||
65 - 90 | 41 - 5A | 無 | √ | A - Z | ||
91 | 5B | VK_LWIN | 左Windows鍵 | |||
92 | 5C | VK_RWIN | 右Windows鍵 | |||
93 | 5D | VK_APPS | Applicatin鍵 | |||
96 - 105 | 60 - 69 | VK_NUMPAD0 - VK_NUMPAD9 | 小鍵盤區的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 | 小鍵盤區的/ | |||
112 - 121 | 70 - 79 | VK_F1 - VK_F10 | √ | 功能鍵F1 - F10 | ||
122 - 135 | 7A- 79 | VK_F11 - VK_F24 | 功能鍵F11 - F24 | |||
144 | 90 | VK_NUMLOCK | 數字鎖定鍵 | |||
145 | 91 | VK_SCROLL | Scroll Lock鍵 |
五、轉義狀態
當需要知道用戶是否按下了轉義鍵時(Shift鍵、Ctrl鍵和Alt鍵)或者切換鍵(Caps Lock鍵和Scroll Lock鍵), 可以通過調用GetKeyState函數來獲得該信息:
iState = GetKeyState(VK_SHIFT) ;
如果Shift鍵被按下, 函數的返回值為負, 即最高位為1
如果需要判斷Caps Lock鍵是否打開, 則
iState = GetKeyState(VK_CAPITAL) ;
當返回值得最低位為1時說明Caps Lock鍵被打開了。
需要注意的是, GetKeyState函數獲取的按鍵狀態並不是實時狀態下的, 也就是說當你函數獲取時是這種狀態, 而你剛剛獲取后用戶就可能又已經釋放了這個按鍵, 不過在大多數情況下通過GetKeyState獲得的消息還是符合我們的目的的。
一個響應鍵盤的實例:
實例的內容為在窗口的客戶區畫一個直徑為100的圓, 然后通過上下左右方向鍵操作這個圓進行相應的移動, 回調函數相應的代碼如下:
1 LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ) 2 { 3 HDC hdc ; 4 PAINTSTRUCT ps ; 5 static POINT pt = { 500, 300 }; //初始顯示坐標為(500, 300) 6 7 switch( message ) 8 { 9 case WM_PAINT: 10 hdc = BeginPaint( hwnd, &ps ) ; 11 Ellipse( hdc, pt.x, pt.y, pt.x + 100, pt.y + 100 ) ; //畫一個直徑為100的圓 12 EndPaint( hwnd, &ps ) ; 13 return 0 ; 14 15 case WM_KEYDOWN: //處理按鍵按下消息 16 switch(wParam) 17 { 18 case VK_UP: //上 19 pt.y -= 5 ; 20 break ; 21 case VK_DOWN: //下 22 pt.y += 5 ; 23 break ; 24 case VK_LEFT: //左 25 pt.x -= 5 ; 26 break ; 27 case VK_RIGHT: //右 28 pt.x += 5 ; 29 break ; 30 } 31 InvalidateRect( hwnd, NULL, TRUE ) ; //使窗口無效化 32 return 0 ; 33 34 case WM_DESTROY: 35 PostQuitMessage( 0 ) ; 36 return 0 ; 37 } 38 39 return DefWindowProc( hwnd, message, wParam, lParam ) ; 40 }
完整的代碼:

1 #include<windows.h> 2 3 LRESULT CALLBACK WndProc( HWND, UINT, WPARAM, LPARAM ) ; 4 5 int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow ) 6 { 7 static TCHAR szAppName[] = TEXT( "UseKeyboard" ) ; 8 HWND hwnd ; 9 MSG msg ; 10 WNDCLASS wndclass ; 11 12 wndclass.style = CS_HREDRAW | CS_VREDRAW ; 13 wndclass.lpfnWndProc = WndProc ; 14 wndclass.cbClsExtra = 0 ; 15 wndclass.cbWndExtra = 0 ; 16 wndclass.hbrBackground = (HBRUSH) GetStockObject( WHITE_BRUSH ) ; 17 wndclass.hCursor = LoadCursor( NULL, IDC_ARROW ) ; 18 wndclass.hIcon = LoadIcon( NULL, IDI_APPLICATION ) ; 19 wndclass.lpszMenuName = NULL ; 20 wndclass.hInstance = hInstance ; 21 wndclass.lpszClassName = szAppName ; 22 23 if( !RegisterClass( &wndclass ) ) 24 { 25 MessageBox( NULL, TEXT("窗口類注冊失敗!"), TEXT("錯誤"), MB_OK ) ; 26 return 0 ; 27 } 28 29 hwnd = CreateWindow( szAppName, TEXT("響應鍵盤事件"), 30 WS_OVERLAPPEDWINDOW, 31 CW_USEDEFAULT, CW_USEDEFAULT, 32 CW_USEDEFAULT, CW_USEDEFAULT, 33 NULL, NULL, hInstance, NULL 34 ); 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 45 return msg.wParam ; 46 } 47 48 LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ) 49 { 50 HDC hdc ; 51 PAINTSTRUCT ps ; 52 static POINT pt = { 500, 300 }; //初始顯示坐標為(500, 300) 53 54 switch( message ) 55 { 56 case WM_PAINT: 57 hdc = BeginPaint( hwnd, &ps ) ; 58 Ellipse( hdc, pt.x, pt.y, pt.x + 100, pt.y + 100 ) ; //畫一個直徑為100的圓 59 EndPaint( hwnd, &ps ) ; 60 return 0 ; 61 62 case WM_KEYDOWN: //處理按鍵按下消息 63 switch(wParam) 64 { 65 case VK_UP: //上 66 pt.y -= 5 ; 67 break ; 68 case VK_DOWN: //下 69 pt.y += 5 ; 70 break ; 71 case VK_LEFT: //左 72 pt.x -= 5 ; 73 break ; 74 case VK_RIGHT: //右 75 pt.x += 5 ; 76 break ; 77 } 78 InvalidateRect( hwnd, NULL, TRUE ) ; //使窗口無效化 79 return 0 ; 80 81 case WM_DESTROY: 82 PostQuitMessage( 0 ) ; 83 return 0 ; 84 } 85 86 return DefWindowProc( hwnd, message, wParam, lParam ) ; 87 }
在這段代碼中首先定義了一個static類型的POINT結構記錄圓的坐標, 然后通過方向鍵去操作它, 使POINT的對象pt中的x或y的值改變, 再通過InvalidateRect函數使窗口的客戶區無效化觸發WM_PAINT消息使其對窗口進行重繪, 這樣就達到了看起來移動圓的目的。
--------------------
wid, 2012.11.20
上一篇: C語言Windows程序設計 -> 第九天 -> GDI繪圖基礎