Mouse Basic:
早期程序员认为鼠标并不是必要的,所以有的计算机是没鼠标的,我们可以通过
fMouse = GetSystemMetrics (SM_MOUSEPRESENT) ;//返回true表示连有鼠标,否则没连鼠标
cButtons = GetSystemMetrics (SM_CMOUSEBUTTONS) ;//没连鼠标返回0,否则返回鼠标的键数
Cursor有一个单像素的点叫做"hot spot",这个点的坐标就是鼠标的位置
Cursor在设计窗口类的时候就已经默认好了
Client-Area Mouse Message:
与Keystroke message不同,对于keystroke message会先存在system message queue再送到拥有input focus的active window的消息队列中,而mouse message分为客户区的mouse message 与非客户区的mouse message,通常忽视非客户区的mouse message,mouse message无论是移动还是点击,窗口是active window还是非active window,都会收得到mouse message的。
客户区的mouse message(10种):
WM_MOUSEMOVE
WM_LBUTTOMDOWN WM_LBUTTONUP WM_LBUTTONDBLCLK(第二次按下,只有在窗口类设了接收双击才会收到这个消息)
WM_RBUTTONDOWN WM_RBUTTONUP WM_RBUTTONDBLCLK
WM_MBUTTONDOWN WM_MBUTTONUP WM_MBUTTONDBLCLK
所有这些消息的lparam:
x=LOWORD(lparam); y=HIWORD(lparam);
所有这些消息的wparam指示了mouse button,shift,ctrl这些键的状态:
MK_LBUTTON //Left button is down
MK_MBUTTON //Middle button is down
MK_RBUTTON //Right button is down
MK_SHIFT //Shift key is down
MK_CONTROL //Ctrl key is down
MK_MBUTTON //Middle button is down
MK_RBUTTON //Right button is down
MK_SHIFT //Shift key is down
MK_CONTROL //Ctrl key is down
应用例子:例如在一个WM_LBUTTONDOWN中
if(wparam & MK_SHIFT)
{ //左键按下并且按了shift键 }
Windows不会为每个像素都产生一个WM_MOUSEMOVE的,主要取决于鼠标硬件与窗口过程处理速率关系
在mouse message中可以通过wparam & MK_SHIFT这样子来确定shift键是否按下,另外前一章中GetKeyState(VK_SHIFT)<0也表示shift键按下,2中方式都可以。
要想接收到双击消息,窗口类必须有CS_DBLCLKS:
没设置CS_DBLCLKS下双击得到的消息:
WM_LBUTTONDOWN
WM_LBUTTONUP
WM_LBUTTONDOWN
WM_LBUTTONUP
WM_LBUTTONUP
WM_LBUTTONDOWN
WM_LBUTTONUP
设置了CS_DBLCLKS下双击得到的消息:
WM_LBUTTONDOWN
WM_LBUTTONUP
WM_LBUTTONDBLCLK
WM_LBUTTONUP
WM_LBUTTONUP
WM_LBUTTONDBLCLK
WM_LBUTTONUP
Nonclient-Area mouse message:
非客户区mouse message有WM_NCLBUTTONDOWN WM_NCLBUTTONUP WM_NCLBUTTONDBLCLK(还有R,MBUTTON)
这些消息一般与WM_SYSKEYDOWN ,WM_SYSKEYUP,WM_SYSCHAR这些类似有WinProc处理
他们的wparam:
指示了鼠标位于非客户区的哪里(它的值一般有HT_开头,含义是hittest)
lparam:
是基于screen coordinates的坐标
最后一个消息:WM_NCHITTEST(优先级最高)
它的lparam是屏幕坐标,wparam没用,返回值为其他所有的wparam参数
根据lparam的坐标返回:
HTCLIENT(位于客户区内),之后把lparam转变成客户区坐标,并产生一个客户区鼠标消息
HTNOWHERE(不位于任何窗口)
HTTRANSPARENT(位于被覆盖的窗口)
HTERROR(使WinProc产生警鸣声)
the value returned from DefWindowProc when processing WM_NCHITTEST becomes the wParam parameter in the mouse message.
举个例子:双击标题菜单栏能关闭窗口,这个过程是这样的:
双击过程产生一系列WM_NCHITTEST,由于鼠标在标题菜单栏处,所以DefWindowprco返回的值是HTSYSMENU,作为后面非客户区mouse message的wparam,产生的消息由WM_NCLBUTTONDOWN-->WM_NCBUTTONUP-->WM_NCLBUTTONDBLCLK-->WM_NCLBUTTONUP,当处理到WM_NCLBUTTONDBLCLK的时候,一般交给DefWindowProc处理产生WM_SYSCOMMAND(按关闭按钮也是产生这个消息),接着处理WM_SYSCOMMAND消息又是交由WinProc处理,产生的是WM_CLOSE消息,这个消息一般交由DefWindowProc处理,在WinProc里面会DestroyWindow(),并且产生WM_DESTROY,我们可以截获WM_CLOSE消息,直接return 0;那么你无论怎样都关不了窗口了。
Hit-Testing in Your Programs:
Cursor相关的display count:如果连有鼠标初始化为0,没连鼠标初始化为-1,只有当display count>=0的时候才会显示Cursor
增加display count:ShowCursor(true);减少就ShowCursor(false);
想显示Cursor就ShowCursor(true)增加display count,再ShowCursor(false)减少display count,如果没连鼠标就变回负数就会不见了,连有鼠标仍然>=0,所以不会消失。
If a mouse is not installed and you display the mouse cursor, it might appear in any part of the display and will remain in that position until you explicitly move it.
如果你没装鼠标而显示cursor,它会随便出现在一个地方的,并且维持不动,除非你move it.
GetCursorPos(&POINT);//获取Cursor位置
SetCursorPos(x,y);//设置Cursor位置
其中x,y都是screen coordinate
Mouse message中lparam参数得到的位置是这个消息发送时Cursor的位置,GetCursorPos(&pt)得到的是实时Cursor的位置,所以可能不同的
子窗口:
child window把Client划分为多个矩形区域,每个child window都有自己的hwnd,WndProc,Client area的,并且子窗口只接收子窗口的mouse message,并且lparam参数得到的cursor 坐标是以子窗口左上角为(0,0)的。换句话说,如果子窗口的窗口类与父窗口的窗口类不同,就意味着用不同的过程函数。
注册窗口类时cbWndExtra是为每个窗口创建都预留一定的空间来保存消息用的。
创建子窗口CreateWindow(子窗口类名,NULL(没标题栏嘛),WS_CHILD | WS_VISIBLE(设为子窗口style,并且可视,这样就不用ShowWindow),x,y,cxWidth,cyHeight,父窗口的句柄,菜单句柄在创建子窗口的时候含义会改变为子窗口的ID:(HMENU)ID,实例句柄一般与父窗口一样吧同一个进程里(HINSTANCE)GetWindowLong(hwnd,GWL_HINSTANCE),NULL);
如果一开始不知道子窗口的大小与位置,一般CreateWindow时设为0,接着知道的时候就调用MoveWindow(hwnd,X,Y,cx,cy,brepaint);来调整就可以了。
前面我们知道mouse message,子窗口会接收到子窗口的message,但是keyboard message呢,按下一个键,Windows不可能知道你按键是为父窗口服务还是为子窗口服务的吧,所以对于keycoard message是子窗口与父窗口共享的,所谓的共享经过调试得出的结果是keyboard message是被送到具有input focus的子窗口那里的,如果想要给父窗口处理,就必须在子窗口过程函数那里调用SendMessage了。
讲到这里keyboard message就不得不涉及到input focus的问题了,因为keyboard message是会从system message queue送到具有input focus的窗口中去,同时又有另一个麻烦就是子窗口是没办法自动获取input focus的,获取input focus的父窗口必须要调用SetFocus来为子窗口设置焦点才行。
每个子窗口都有一个独一无二的ID,是在CreateWindow的时候HMENU那里设置的。
GetWindowLong(ChildHwnd,GWL_ID);//获取子窗口ID
GetDlgCtrlID(ChildHwnd);//获取子窗口ID
GetDlgItem(hwndParent,ID);//知道父窗口句柄和子窗口ID可以获取子窗口的句柄
捕获鼠标:
一般来说鼠标在窗口的客户区或者非客户区上,过程函数才会收得到mouse message的,但是当你在客户区按下鼠标,在窗口外面松手的时候,过程函数就收不到WM_LBUTTONUP消息了,有时我们可能要令鼠标暂时性离开窗口,但是还能发送消息到窗口,这个时候我们就必需要用到捕获鼠标了。
SetCaptrue(hwnd);//这个函数表示捕获鼠标,之后所有鼠标信息都会发送到hwnd的过程函数了,而且lparam参数仍然是client coordinate(这点很好用)
ReleaseCaptrue();//停止捕获鼠标
一般我们需要追踪WM_MOUSEMOVE消息的时候,我们都在WM_LBUTTONDOWN中设置捕获鼠标,在WM_LBUTTONUP中停止捕获。
Mouse Wheel:
当滑动鼠标的滑轮的时候Windows就会发送WM_MOUSEWHEEL消息到具有input focus的窗口的过程函数中去。
WM_MOUSEWHEEL的lparam是鼠标的位置,屏幕坐标,wparam的LOWORD是判断mouse buttons,shift,ctrl等键的状态,HIWORD是鼠标的滑动一下的增量"delta",一个WHEEL_DELTA的值是120(向前滑)或者是-120(向后滑)。未来的鼠标滑轮可能会变得更加精确,那时HIWORD返回的可能会是40或者-40。
在WM_CREATE消息中SystemParametersInfo(SPI_GETWHEELSCROLLLINES,0,&ui,0);就可以获得当前系统级的设定:一个WHEEL_DELTA滚动多少行,默认值是3,这样就可以求出一行占了多少增量:iDeltaPreLine=WHEEL_DELTA/3;
知道了一行占了多少增量再回到WM_MOUSEWHEEL里面,HIWORD(wparam)无论是120(-120)还是未来的40(-40)根据一行的增量来调整滚动就行了。