ITEEDU

第七课 处理鼠标输入消息

本课中我们将学习如何在我们的窗口过程函数中处理鼠标按键消息。示例程序演示了如何等待左键按下消息,我们将在按下的位置显示一个字符串。

理论:

和处理键盘输入一样,WINDOWS将捕捉鼠标动作并把它们发送到相关窗口。这些活动包括左、右键按下、移动、双击等(译者注:新式鼠标还包括滚轮消息WM_WHEEL)。WINDOWS并不像处理键盘输入那样把所有的鼠标消息都导向有输入焦点的窗口,任何鼠标经过的窗口都将接收到鼠标消息,无论有否输入焦点。另外,窗口还会接收到鼠标在非客户区移动的消息(WM_NCMOVE),但大多数的情况下我们都会将其忽略掉。 对鼠标的每一个按钮都有两个消息:WM_LBUTTONDOWN,WM_RBUTTONDOWN 。对于三键鼠标还会有WM_MBUTTONDOWN和WM_MBUTTONUP消息,当鼠标在某窗口客户区移动时,该窗口将接收到WM_MOUSEMOVE消息。一个窗口若想处理WM_LBUTTONDBCLK或 WM_RBUTTONDBCLK,那么它的窗口类必须有CS_DBLCLKS风格,否则它就会接受到一堆的按键起落(WM_XBUTTONDOWN或WM_XBUTTONUP)的消息。 对于所有的消息,窗口过程函数传入的参数lParam包含了鼠标的位置,其中底位为x坐标,高位为y坐标,这些坐标值都是相对于窗口客户区的左上角的值,wParam中则包含了鼠标按钮的状态。

例子:

.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
  MOUSECLICK  DB        0           ; 0=no click yet

.DATA?
hInstance HINSTANCE ? 
CommandLine LPSTR ? 
hitpoint POINT <> 

.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    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_LBUTTONDOWN
              MOV       EAX,LPARAM
              AND       EAX,0FFFFH
              MOV       HITPOINT.X,EAX
              MOV       EAX,LPARAM
              SHR       EAX,16
              MOV       HITPOINT.Y,EAX
              MOV       MOUSECLICK,TRUE
              INVOKE    INVALIDATERECT,HWND,NULL,TRUE
.ELSEIF       UMSG==WM_PAINT
              INVOKE    BEGINPAINT,HWND, ADDR PS
              MOV       HDC,EAX
.IF           MOUSECLICK
              INVOKE    LSTRLEN,ADDR APPNAME
              INVOKE    TEXTOUT,HDC,HITPOINT.X,HITPOINT.Y,ADDR APPNAME,EAX
.ENDIF
              INVOKE    ENDPAINT,HWND, ADDR PS
.ELSE
              INVOKE    DEFWINDOWPROC,HWND,UMSG,WPARAM,LPARAM
              RET
.ENDIF
              XOR       EAX,EAX
              RET
     WNDPROC  ENDP
              END       START

分析:

.ELSEIF       UMSG==WM_LBUTTONDOWN
              MOV       EAX,LPARAM
              AND       EAX,0FFFFH
              MOV       HITPOINT.X,EAX
              MOV       EAX,LPARAM
              SHR       EAX,16
              MOV       HITPOINT.Y,EAX
              MOV       MOUSECLICK,TRUE
              INVOKE    INVALIDATERECT,HWND,NULL,TRUE

窗口过程处理了WM_LBUTTONDOWN消息,当接收到该消息时,lParam中包含了相对于窗口客户区左上角的坐标,我们把它保存下来,放到一个结构体变量(POINT)中,该结构体变量的定义如下:

然后我们把标志量MouseClick设为TRUE,这表明至少有一次在客户区的左键按下消息。

              MOV       EAX,LPARAM
              AND       EAX,0FFFFH
              MOV       HITPOINT.X,EAX

由于lParam是一个32位长的数,其中高、底16位分别包括了x、y坐标所以我们做一些小处理,以便保存它们。

              SHR       EAX,16
              MOV       HITPOINT.Y,EAX
保存完坐标后我们设标志MouseClick为TRUE,这是在处理WM_PAINT时用来判断是否有鼠标左键按下消息。然后我们调用InvalidateRect()函数迫使WINDOWS重新绘制客户区。

.IF MouseClick

绘制客户区的代码首先检测MouseClick标志位,再决定是否重绘。因为我们在首次显示窗口时还没有左键按下的消息,所以我们在初始时把该标志设为FALSE,告诉WINDOWS不要重绘客户区,当有左键按下的消息时,它会在鼠标按下的位置绘制字符串。注意在调用TextOut()函数时,其关于字符串长度的参数是调用lstrlen()函数来计算的。

              INVOKE    LSTRLEN,ADDR APPNAME
              INVOKE    TEXTOUT,HDC,HITPOINT.X,HITPOINT.Y,ADDR APPNAME,EAX
.ENDIF