2007. 2. 15. 21:25
DevX Win32 Assembly Tutorial 7: Mouse Input

Tutorial 7: Mouse Input

이번 장에서는, 윈도우 프로시저에서 마우스 메세지를 어떻게 처리하는 지를 설명한다. 예제에서는, 마우스의 왼쪽 클릭 버튼이 눌려지면, 작업영역에 문자열을 출력하는 예제이다.
   소스       실행 결과   

Theory:

6장에서 살펴본 키보드 입력때와 같이, 윈도우는 마우스 이벤트가 발생할 경우 해당 윈도우에게 메세지를 전해주는 방식이다. 이런 동작에는, 왼쪽 버튼 클릭이나 오른쪽 버튼 클릭, 마우스 커서의 이동, 더블 클릭등이 있다. 다만, 포커스가 있는 윈도우에 전송하는 키보드 입력과는 다르게, 마우스 커서가 겹쳐진윈도우라면, 그 윈도우가 활성화 되지 않았더라도 마우스 메세지가 전송된다.추가로, 작업영역이 아닌 곳에서도 발생하는 마우스 메세지가 있다. 그러나, 비작업영역의 메세지를 처리하는 경우는 거의 없기 때문에, 무시해도 상관없다(DefWinProc가 처리한다). 그러므로, 작업영역에 보내진 마우스 메세지에 대해서만 초점을 맞춘다.

마우스 버튼 하나에 대해, 마우스 메세지는 2개 있다. 즉, 버튼은 최소한 2개가 있으므로, WM_LBUTTONDOWN, WM_RBUTTONDOWN 과 WM_LBUTTONUP, WM_RBUTTONUP 이라는 메세지가 존재한다. 버튼이 3개인 마우스는 WM_MBUTTONDOWN 과 WM_MBUTTONUP 이라는 마우스 메세지가 있다. 작업영역에 마우스 커서가 이동될 경우 Windows 는 그 윈도우에 WM_MOUSEMOVE 메세지를 전송하게 된다.

또한, 더블 클릭 메세지도 WM_LBUTTONDBCLK 과 WM_RBUTTONDBCLK 메세지에 의해 인식 될 수 있지만, 이경우, 윈도우 클래스의 스타일에 CS_DBLCLKS 플래그가 설정되어 있어야만 가능하다. 그렇지 않으면 단지, 두번 연속적으로 발생되는 Down->UP 메세지가 발생하는 것이다.

이런 모든 마우스 메세지에서는, 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로 하고 있다. 이것은, 최소한 한번은 작업영역에서 왼쪽버튼을 눌렀음을 나타내고 있다.

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

X좌표는 lParam 의 하위 워드(16비트)이므로, 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 함수를 호출 해서, 윈도우에 강제로 작업영역 전체의 화면 복구처리를 하도록 한다.

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

WM_PAINT 섹션의 출력처리는, MouseClick 플래그가 TRUE인지를 체크해야 한다. 이렇게 하지 않으면 클릭하지 않는 상태에서도 문자열을 출력해 버리기 때문이다. 그래서, 초기화할때는 MouseClick 플래그를 FALSE로 설정해서, 실제로 마우스 클릭했을 경우에만, TRUE로 설정하게 한다.

마우스를 클릭하게 되면 클릭한 마우스의 커서 위치에 문자열이 출력된다. 출력처리 전에, lstrlen 함수를 호출 해서, 출력문자열의 길이를 구해서 TextOut 함수의 마지막 인수에 넘겨줘야한다.


Posted by openserver