C语言Windows程序设计 -> 第十天 -> 响应键盘事件
2012-11-20 21:36 wid 阅读(13787) 评论(1) 编辑 收藏 举报响应键盘事件
------------------------
在开始学习有关键盘事件的知识前首先来看一段代码(回调函数这部分的代码):
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绘图基础