Tutorial 7: Mouse Input

今回は、ウィンドウプロシージャにおいてマウスメッセージをどうやって送受信するかを説明する。 サンプルプログラムでは、マウスの左クリックボタンが押されるのを待ち構え、 もしクリックされたら、クライアントエリアに文字列を表示するというものだ。
   ソース      実行結果  

Theory:

キーボード入力と同じように、それぞれのウィンドウに関係あるマウスイベントを送信して、 マウスメッセージが入力されたことをウィンドウに伝えることになっている。 それらの動作には、左ボタンクリックや右ボタンクリック、マウスカーソルの移動、 ダブルクリックなどがある。 ただ、フォーカスのあるウィンドウに送信していたキーボード入力と違って、 マウスカーソルが重なったウィンドウになら、そのウィンドウがアクティブであろうと無かろうとマウスメッセージが送信される。 加えて、クライアントエリアでないところにもマウスメッセージがある。 しかし、非クライアントエリアに送られてくるメッセージを扱うとことはほとんどないので、無視して構わない。 なので、クライアントエリアに送られてくるマウスメッセージに焦点をあてればよい。

マウスボタン1つにつき、マウスメッセージは2つある。つまり、少なくともマウスボタンは2つあるので、 WM_LBUTTONDOWN、WM_RBUTTONDOWN と WM_LBUTTONUP, WM_RBUTTONUP というメッセージが存在する。 3つボタンマウスはさらにWM_MBUTTONDOWN と WM_MBUTTONUPがある。 クライアントエリアにマウスカーソルが重なった時、 Windows はそのウィンドウにWM_MOUSEMOVEメッセージを送信する。

また、ダブルクリックメッセージも WM_LBUTTONDBCLK か WM_RBUTTONDBCLK によって認識できるようになっているが、 ウィンドウクラスのスタイルに CS_DBLCLKS フラグが設定されているウィンドウに限る。 そうでなければ単に、マウスが押されたメッセージと離されたメッセージを連続して受け取ることになる。

これら全てのメッセージで、lParamの値がマウスの座標値となっている。 クライアントエリアの左上隅を原点として、 下位ワード(ワード=2Byte)が X座標値で、上位ワードがY座標値となっている。 wParam はマウスボタンと、シフトキー、コントロールキーの状態を表すものとなっている。

Example:

.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

Analysis:

.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型の変数となっており、POINT型は以下のように定義されている。

POINT STRUCT
   x  dd ?
   y  dd ?
POINT ENDS

その後、MouseClickフラグをTRUEにしている。 これは、少なくとも1度はクライアントエリアで左マウスボタンがクリックされた、ということを表している。

mov eax,lParam
and eax,0FFFFh
mov hitpoint.x,eax

X座標は lParam の下位ワードとなっており、そして、POINT型のメンバはのサイズは32ビットなので、 lParamの上位ワードをクリアすることにより、X座標を取得し、hitpoint.x に代入している。

shr eax,16
mov hitpoint.y,eax

Y座標値はlParamの上位ワードなので、16ビット右へシフトして上位ワードを下位ワードの位置にシフトし、 hitpoint.y に代入する。

マウスのX,Y座標それぞれを取得したあと、MouseClickフラグをTRUEにし、 WM_PAINTセクションで、少なくともクライアントエリアでマウスを一回クリックされたことがわかるようにし、 もし、WM_PAINTセクションに入れば、マウスの座標に文字列を描画するようになる。

次に、InvalidateRect関数をCALLし、 ウィンドウに強制的に全クライアントエリアの再描画を行うようにする。

.IF MouseClick
    invoke lstrlen,ADDR AppName
    invoke TextOut,hdc,hitpoint.x,hitpoint.y,ADDR AppName,eax
.ENDIF

WM_PAINTセクションの描画処理は、 MouseClickがTRUEかどうかをチェックしなければならない。 というのは、ウィンドウが作られたその瞬間に、WM_PAINTメッセージがとんできてしまい、 何もマウスをクリックしてないのに、文字列を表示してしまうからである。 なので、初期化時にはMouseClickをFALSEにセットし、 本当にマウスクリックが発生した時に、TRUEにセットしなおす。

もし、少なくとも1回のマウスクリックが発生すれば、 その時のマウス座標値に文字が描画される。 その処理中で、lstrlen関数をCALLし、描画する文字の長さを求め、 TextOut関数の最後の引数に渡さなければならないことに注意しなければならない。


[戻る]