代码改变世界

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 }

完整的示例代码:

View Code
 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_SETFOCUSWM_KILLFOCUS来确定自己的窗口是否获得了焦点。
    
  当程序最小化到系统托盘时, 系统同样能够将消息发送给程序, 只不过此时消息的形式与发给活动窗口时不同。

 

二、队列和同步
  当一个按键被按下时到你的应用程序得到这个消息期间需要经过一个"中转站", 这个中转站就是系统的消息队列, 也就是说, 当用户按下按键后, Windwows和键盘设备的驱动程序将硬件的扫描码转化成格式化后的消息, 但这个消息并不会直接发送给应用程序的消息队列, 而是存储在系统的一个独立的消息队列当中, 这个系统的消息队列注要用来存储用户从键盘和鼠标输入的消息。


  为什么要这样做? 可以想象一下, 当系统这会正在运行许多的应用程序导致系统超负荷运作时, 恰好你的程序也在被运行并且获取了输入焦点, 用户正好要使用你的程序完成一些操作, 但是用户的输入速度快于此时系统处理的速度, 也就是说如果不先存放在系统的消息队列中那么此时用户的输入已经大于你的程序的处理速度了, 当键盘上某一个特殊的按键被按下时系统可能会将输入焦点转换到另一个窗口上, 后面的操作系统会认为用户的击键操作已经属于另一个窗口。当先存放在系统的队列, 再把这些消息发给这些消息发给当前活动的窗口就可以避免消息接收混乱的情况。

 

三、击键消息
  当一个按键被按下时, Windows将WM_KEYDOWNWM_SYSKEYDOWN消息放进具有输入焦点的窗口的消息队列中, 当按键被释放时, Windows又会把WM_KWYUPWM_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 }

 

完整的代码:

 

View Code
 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绘图基础