ITEEDU

第六课 处理键盘输入消息

在本课中,我们将要学习WINDOWS程序是如何处理键盘消息的。

理论:

因为大多数的PC只有一个键盘,所以所有运行中的WINDOWS程序必须共用它。WINDOWS 将负责把击键消息送到具有输入焦点的那个应用程序中去。尽管屏幕上可能同时有几个应用程序窗口,但一个时刻仅有一个窗口有输入焦点。有输入焦点的那个应用程序的标题条总是高亮度显示的。 实际上您可以从两个角度来看键盘消息:一是您可以把它看成是一大堆的按键消息的集合,在这种情况下,当您按下一个键时,WINDOWS就会发送一个WM_KEYDOWN给有输入焦点的那个应用程序,提醒它有一个键被按下。当您释放键时,WINDOWS又会发送一个WM_KYEUP消息,告诉有一个键被释放。您把每一个键当成是一个按钮;另一种情况是:您可以把键盘看成是字符输入设备。当您按下“a”键时,WINDOWS发送一个WM_CHAR消息给有输入焦点的应用程序,告诉它“a”键被按下。实际上WINDOWS 内部发送WM_KEYDOWN和WWM_KEYUP消息给有输入焦点的应用程序,而这些消息将通过调用TranslateMessage翻译成WM_CHAR消息。WINDOWS窗口过程函数将决定是否处理所收到的消息,一般说来您不大会去处理WM_KEYDOWN、WM_KEYUP消息,在消息循环中TranslateMessage函数会把上述消息转换成WM_CHAR消息。在我们的课程中将只处理WM_CHAR。

例子:

.386
.MODEL        FLAT,STDCALL
option casemap:none WinMain proto :DWORD,:DWORD,:DWORD,:DWORD 

     INCLUDE  \MASM32\INCLUDE\WINDOWS.INC
     INCLUDE  \MASM32\INCLUDE\USER32.INC
     INCLUDE  \MASM32\INCLUDE\KERNEL32.INC
     INCLUDE  \MASM32\INCLUDE\GDI32.INC
  INCLUDELIB  \MASM32\LIB\USER32.LIB
  INCLUDELIB  \MASM32\LIB\KERNEL32.LIB
  INCLUDELIB  \MASM32\LIB\GDI32.LIB

.DATA
   CLASSNAME  DB        "SIMPLEWINCLASS",0
     APPNAME  DB        "OUR FIRST WINDOW",0
char WPARAM 20h ; the character the program receives from keyboard 

.DATA?
hInstance HINSTANCE ? 
CommandLine LPSTR ? 

.CODE
      START:
              INVOKE    GETMODULEHANDLE, NULL
              MOV       HINSTANCE,EAX
              INVOKE    GETCOMMANDLINE
              MOV       COMMANDLINE,EAX
              INVOKE    WINMAIN, HINSTANCE,NULL,COMMANDLINE, SW_SHOWDEFAULT
              INVOKE    EXITPROCESS,EAX

     WINMAIN  PROC      HINST:HINSTANCE,HPREVINST:HINSTANCE,CMDLINE:LPSTR,CMDSHOW:DWORD
              LOCAL     WC:WNDCLASSEX
              LOCAL     MSG:MSG
              LOCAL     HWND:HWND
              MOV       WC.CBSIZE,SIZEOF WNDCLASSEX
              MOV       WC.STYLE, CS_HREDRAW OR CS_VREDRAW
              MOV       WC.LPFNWNDPROC, OFFSET WNDPROC
              MOV       WC.CBCLSEXTRA,NULL
              MOV       WC.CBWNDEXTRA,NULL
              PUSH      HINST
              POP       WC.HINSTANCE
              MOV       WC.HBRBACKGROUND,COLOR_WINDOW+1
              MOV       WC.LPSZMENUNAME,NULL
              MOV       WC.LPSZCLASSNAME,OFFSET CLASSNAME
              INVOKE    LOADICON,NULL,IDI_APPLICATION
              MOV       WC.HICON,EAX
              MOV       WC.HICONSM,EAX
              INVOKE    LOADCURSOR,NULL,IDC_ARROW
              MOV       WC.HCURSOR,EAX
              INVOKE    REGISTERCLASSEX, ADDR WC
              INVOKE    CREATEWINDOWEX,NULL,ADDR CLASSNAME,ADDR APPNAME,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\ 
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\ 
hInst,NULL 
              MOV       HWND,EAX
              INVOKE    SHOWWINDOW, HWND,SW_SHOWNORMAL
              INVOKE    UPDATEWINDOW, HWND
.WHILE        TRUE
              INVOKE    GETMESSAGE, ADDR MSG,NULL,0,0
.BREAK        .IF (!EAX)
              INVOKE    TRANSLATEMESSAGE, ADDR MSG
              INVOKE    DISPATCHMESSAGE, ADDR MSG
.ENDW
              MOV       EAX,MSG.WPARAM
              RET
     WINMAIN  ENDP

     WNDPROC  PROC      HWND:HWND, UMSG:UINT, WPARAM:WPARAM, LPARAM:LPARAM
              LOCAL     HDC:HDC
              LOCAL     PS:PAINTSTRUCT

.IF           UMSG==WM_DESTROY
              INVOKE    POSTQUITMESSAGE,NULL
.ELSEIF       UMSG==WM_CHAR
              PUSH      WPARAM
              POP       CHAR
              INVOKE    INVALIDATERECT, HWND,NULL,TRUE
.ELSEIF       UMSG==WM_PAINT
              INVOKE    BEGINPAINT,HWND, ADDR PS
              MOV       HDC,EAX
              INVOKE    TEXTOUT,HDC,0,0,ADDR CHAR,1
              INVOKE    ENDPAINT,HWND, ADDR PS
.ELSE
              INVOKE    DEFWINDOWPROC,HWND,UMSG,WPARAM,LPARAM
              RET
.ENDIF
              XOR       EAX,EAX
              RET
     WNDPROC  ENDP
              END       START

分析:

  char WPARAM 20h  ; the character the program receives from keyboard 

这个变量将保存从键盘接收到的字符。因为它是在窗口过程中通过WPARAM型变量传送的,所以我们简单地把它定义为WPARAM型。由于我们的窗口在初次刷新时(也即刚被创建的那一次)是没有键盘输入的所以我们把他设成空格符(20h),这样显示时您就什么都看不见。

.ELSEIF       UMSG==WM_CHAR
              PUSH      WPARAM
              POP       CHAR
              INVOKE    INVALIDATERECT, HWND,NULL,TRUE

这一段是用来处理WM_CHAR消息的。它把接收到的字符放入变量char中,接着调用InvalidateRect,而InvalidateRect使得窗口的客户区无效,这样它会发出WM_PAINT消息,而WM_PAINT消息迫使WINDOWS重新绘制它的客户区。该函数的语法如下:

InvalidateRect proto hWnd:HWND,\               
  lpRect:DWORD,\             
  bErase:DWORD 

lpRect是指向客户区我们想要其无效的一个正方形结构体的指针。如果该值等于NULL,则整个客户区都无效;布尔值bErase告诉WINDOWS是否擦除背景,如果是TRUE,则WINDOWS在调用BeginPaint函数时把背景擦掉。 所以我们此处的做法是:我们将保存所有有关重绘客户区的数据,然后发送WM_PAINT消息,处理该消息的程序段然后根据相关数据重新绘制客户区。尽管这么做事有点像走了弓背,但WINDOWS要处理那么庞大的消息群,没有一定的规矩可不行。 实际上我们完全可以通过调用GetDC 获得设备上下文句柄,然后绘制字符,然后再调用ReleaseDC释放设备上下文句柄,毫无疑问这样也能在客户区绘制出正确的字符。但是如果这之后接收到WM_PAINT消息要处理时,客户区会重新刷新,而我们这稍前所绘制的字符就会消失掉。所以为了让字符一直正确地显示,就必须把它们放到WM_PAINT的处理过程中处理。而在本消息处理中发送WM_PAINT消息即可。

              INVOKE    TEXTOUT,HDC,0,0,ADDR CHAR,1

在调用InvalidateRect时,WM_PAINT消息被发送到了WINDOWS窗口处理过程,程序流程转移到处理WM_PAINT消息的程序段,然后调用BeginPaint得到设备上下文的句柄,再调用TextOut在客户区的(0,0)处输出保存的按键字符。这样无论您按什么键都能在客户区的左上角显示,不仅如此,无论您怎么缩放窗口(迫使WINDOWS重新绘制它的客户区),字符都会在正确的地方显示,所以必须把所有重要的绘制动作都放到处理WM_PAINT消息的程序段中去。