튜토리얼 7: Mouse Input


 이번 시간에는 윈도우 프로시져에서 어떻게 마우스 입력을 받고 응답하는지 배워 봅시다. 예제 프로그램에서는 왼쪽 마우스 클릭을 기다리고 있고, 클라이언트 영역에 클릭을 하면 바로 그 위치에 문자열을 출력할 것입니다.



Theory:


 키보드 입려처럼, Windows 는 각각의 윈도우에 상대적인 마우스 움직임을 감지하고 이를 알려줄 것입니다. 왼쪽 오른쪽 마우스 클릭과 같은 동작은 물론, 윈도우 위에서의 마우스 커서의 움직임, 더블클릭 모두를 의미합니다. 키보드의 경우에는 입력 포커스를 가지고 있는 윈도우에만 메세지가 보내졌지만, 마우스 메세지의 경우에는 마우스 커서 아래의 윈도우, 활성화 되어 있는 윈도우, 비활성화 된 윈도우 등에 상관없이 모두에게 전달됩니다. 게다가 클라이언트 영역이 아닌 곳까지 모두 마우스 메세지가 전달됩니다. 하지만 대부분의 경우, 우린 이런 것들을 무시할 수 있습니다. 즉, 클라이언트 영역과 관련된 것들에만 초점을 둘 수 있습니다.

 각 마우스 버튼에는 다음과 같이 두가지 메세지가 있습니다. : WM_LBUTTONDOWN, WM_RBUTTONDOWN 그리고 WM_LBUTTONUP, WM_RBUTTONUP. 버튼이 세개인 마우스를 위한 WM_MBUTTONDOWN 과 WM_MBUTTTONUP 도 있습니다. 마우스 커서가 클라이언트 영역 위를 움직이고 있을 때, Windows 는 WM_MOUSEMOVE 메세지를 마우스 커서 아래에 있는 윈도우에게 전달합니다.

 만약 윈도우 클래스가 CS_DBLCLKS 스타일 플래그를 가지고 있다면, 윈도우는 WM_LBUTTONDBCLK, WM_RBUTTONDBCLK 메세지를 받을 수 있습니다. 플래그가 없다며 윈도우는 마우스 버튼 업, 다운 메세지를 받게 됩니다.

 이런 모든 메세지들은 lParam 안에 마우스의 위치 정보를 가지고 있습니다. low word(역주: 하위 16비트)에는 윈도우 클라이언트 영역의 좌측 상단을 기준으로 한 x 좌표가, high word(역주: 상위 16비트)에는 y 좌표가 들어있습니다. 그리고 wParam 에는 마우스 버튼, Shift, Ctrl 키의 상태에 대한 정보가 들어 있습니다.
 

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 STRUCT
    x   dd ?
    y   dd ?
POINT ENDS


 그리고 적어도 한번 클라이언트 영역에 왼쪽 마우스 클릭이 있었다는 뜻으로, MouseClick 플래그를 TRUE 로 세팅합니다.
 

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


 x 좌표값이 lParam 의 low word(역주: 하위 16비트) 안에 있는데, POINT 구조체 안에서 다루는 좌표 정보의 크기가 32비트이므로 hitpoint.x 값을 저장하기 전에 미리 eax 의 high word(역주 : 상위 16비트)를 0 으로 만들어야 합니다.
 

        shr eax,16
        mov hitpoint.y,eax


 y 좌표값이 lParam 의 high word(역주: 상위 16비트)안에 들어있기 때문에, hitpoint.y 값을 저장하기 전에 eax 의 low word(역주: 하위 16비트)를 0으로 만들어야 하는데, 이는 eax 를 오른쪽으로 16비트 시프트 하면 해결됩니다.

 마우스 좌표 정보를 저장한 뒤에는, MouseClick 플래그를 TRUE 로 세팅하여 WM_PAINT 섹션에 있는 코드에서 적어도 한번 클라이언트 영역이 클릭됬음을 인지하고, 클릭 된 위치에 문자열을 그릴 수 있도록 합니다. 그 다음 InvalidRect 함수를 호출하여 클라이언트 영역 전체가 강제적으로 다시 그려지도록 합니다.
 

        .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 로 변경합니다.

 적어도 한번 마우스 클릭을 하게 되면, 클라이언트 영역의 클릭한 위치에 문자열을 그립니다. 여기에서 화면에 그려야 하는 문자열의 길이를 구하기 위해 lstrlen 함수를 사용하고, 그 값을 TextOut 함수의 마지막 파라미터로 사용하는 것에 주목하시기 바랍니다.