บทที่ 7 : การรับข้อมูลจากเมาส์

ข้อมูลจาก Iczelion

เราจะศึกษาว่าวินโดว์โพรซิเยอร์สามารถรับและตอบสนองต่อเมาส์ได้อย่างไร โดยตัวอย่างโปรแกรมในบทนี้จะรอการคลิ๊กเมาส์แล้วจึงแสดงข้อความ ณ ตำแหน่งที่คลิ๊กเมาส์นั้นใน client area ของวินโดว์

หลักการ

วินโดวส์จะตรวจสอบและส่งเมสเสจเตือนถ้ามีการทำงานของเมาส์ไปยังวินโดว์ที่เมาส์ทำงานอยู่คล้ายๆ กับการทำงานกับคีย์บอร์ด การทำงานของเมาส์นั้นมีทั้งการคลิ๊กซ้าย คลิ๊กขวา การเคลื่อนที่บนวินโดว์ รวมทั้งการดับเบิ้ลคลิ๊กด้วย แต่ที่ต่างจากคีย์บอร์ดก็คือถ้าเป็นคีย์บอร์ด เมสเสจที่ส่งมาจะเป็นเมสเสจของวินโดว์ที่กำลังรับโฟกัสอยู่ ส่วนเมาส์นั้นจะส่งไปยังวินโดว์ใดก็ตามที่เมาส์เคลื่อนที่อยู่ด้านบน ไม่ว่าวินโดว์นั้นจะแอกทีฟอยู่หรือไม่ รวมทั้งเมสเสจของเมาส์ที่อยู่นอก client area ของวินโดว์ด้วย แต่เรากล่าวถึงเฉพาะส่วนที่อยู่ใน client area

เมสเสจที่เกี่ยวกับการทำงานของปุ่มของเมาส์ มี WM_LBUTTONDOWN,WM_RBUTTONDOWN และ WM_LBUTTONUP, WM_RBUTTONUP สำหรับเมาส์ที่มีสามปุ่มจะมีเมสเสจเพิ่มขึ้มมาคือ WM_MBUTTONDOWN และ WM_MBUTTONUP. วินโดวส์จะส่งเมสเสจ WM_MOUSEMOVE ไปยังวินโดว์ที่เมาส์เคลื่อนที่อยู่บน client area ของวินโดว์นั้น

วินโดว์จะสามารถรับเมสเสจการดับเบิ้ลคลิ๊ก WM_LBUTTONDBCLK หรือ WM_RBUTTONDBCLK ก็ต่อเมื่อคลาสของวินโดว์นั้นมีสไตล์แฟล็ก CS_DBLCLKS อยู่ด้วย มิฉะนั้นวินโดว์ก็จะรับได้แค่เมสเสจที่เกี่ยวกับการกดหรือปล่อยปุ่มเมาส์เท่านั้น

เมสเสจดังกล่าวที่ส่งมานั้นจะส่งมาพร้อมกับ wParam และ lParam ตำแหน่งของเมาส์ที่ส่งเมสเสจเข้ามาจะอยู่ที่ lParam โดยตำแหน่ง x จะอยู่ที่ low word ส่วน y จะอยู่ที่ high word ของ lParam เมื่อเทียบกับมุมบนซ้ายของ client area ส่วนค่าของ wParam จะเป็นสถานะของปุ่มเมาส์และคีย์ Shift และ Ctrl

ตัวอย่าง

.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 จะเป็นตำแหน่งของเมาส์ใน client area และจะเก็บตำแหน่งนี้ในตัวแปรที่มีโครงสร้างแบบ POINT ซึ่งมีรูปแบบดังนี้


POINT STRUCT 
	x   dd ? 
	y   dd ? 
POINT ENDS 

แล้วจึงเซ็ทตัวแปร MouseClick ให้มีค่าเป็น TRUE เพื่อให้ทราบว่าอย่างน้อยมีการคลิ๊กปุ่มซ้ายของเมาส์ภายใน client area ของวินโดว์


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

เพราะว่าจุดโคออดิเนตของ x จะอยู่ที่ low word ของ lParam และเมมเบอร์ของ POINT เป็นตัวแปรแบบ 32 บิต จึงต้องกำหนดค่าด้วยรีจิสเตอร์ 32 บิตด้วย ดังนั้นจึงต้องทำให้ high word ของ eax เป็นศูนย์เสียก่อนด้วยคำสั่ง and eax,0FFFFh ก่อนที่จะเก็บค่าใส่ในตัวแปร hitpoint.x


shr eax,16 
mov hitpoint.y,eax 

และจุดโคออดิเนต y เก็บอยู่ที่ high word ของ lParam จึงด้อง shift รีจิสเตอร์ eax ไปทางขวา 16 บิต เพื่อให้ low word เป็นค่าของโคออดิเนต y และ high word เป็นศูนย์ก่อนที่จะเก็บค่าลงตัวแปร hitpoint.y

หลังจากเก็บค่าตำแหน่งของเมาส์แล้ว เราเซ็ตค่า MouseClick ให้เป็น TRUE เพื่อให้โค้ดของการวาดหน้าจอในส่วน WM_PAINT ทราบว่าอย่างน้อยมีการคลิ๊กเมาส์ซ้ายภายใน client area จึงสามารถวาดตัวอักษร ณ ตำแหน่งที่มีการคลิ๊กเมาส์ หลังจากนั้นจึงเรียกฟังก์ชั่น InvalidateRec เพื่อบังคับให้วินโดว์วาดพื้นที่ client area ทั้งหมดใหม่


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

โค้ดในส่วนของ WM_PAINT จะตรวจสอบตัวแปร MouseClick ว่ามีค่าเป็น TRUE หรือไม่ ถ้ามีค่าเป็น TRUE ก็จะทำการวาดตัวอักษรลงไปในตำแหน่งที่คลิ๊กเมาส์ แต่ในตอนเริ่มโปรแกรมไม่ควรจะมีตัวอักษรปรากฏขึ้นบน client area ดังนั้นจึงต้องกำหนดค่าของ MouseClick ให้มีค่าเป็น FALSE และจะเปลี่ยนเป็น TRUE เมื่อมีการคลิ๊กเมาส์ซ้ายเกิดขึ้น

เมื่อมีการคลิ๊กเมาส์มันจะเรียกฟังก์ชั่น lstrlen เพื่อหาค่าความยาวของตัวอักษร (AppName) ที่จะแสดงบนหน้าจอ ค่าความยาวของตัวอักษรที่ให้กลับมาจะเก็บไว้ที่ตัวแปร eax แล้วจึงนำไปใส่ในพารามิเตอร์สุดท้ายของฟังก์ชั่น TextOut เพื่อให้พิมพ์ตัวอักษรออกมา


กลับหน้าแรก