第三章、键盘和鼠标
精华浓缩:
键盘和鼠标恐怕是用电脑的人摸的最多的两样东西(当然,DIYer们除外),也正因为有很直观的印象,要理解它们并不难。
在windows中,键盘和鼠标输入是以消息的形式出现的。首先,设备驱动程序接收并处理鼠标和键盘中断,通过中断处理(ISR)把最初的电脉冲信号转换为系统识别的事件通知(就是消息)放入系统消息队列中。操作系统核心有一个专门的线程(应该就是user32.dll)来监控系统消息队列,通过它把消息分发到各应用程序线程消息队列中。以后,就是应用程序对消息的检索处理,在MFC开发中,就是第一章讲到的消息映射机制了。
学习如何响应键盘和鼠标的输入关键是要了解该处理哪个消息以及和处理相关的一些MFC和API函数。
鼠标消息首先分为两大类,即客户区消息和非客户区消息。再按不同输入动作,又细分为左(右、中)键单击、双击和按下、弹起以及鼠标移动消息等。例如WM_LBUTTONDOWN, 表示这是客户区鼠标左键按下消息,而WM_NCMBUTTONUP, 则表示这是非客户区(Not Client)鼠标中键弹起消息。鼠标事件和对应消息的详情可参考MSDN 。
在MFC中,客户区鼠标消息处理函数的原型为:
afx_msg void OnMsgName(UINT nFlags,CPoint point)
其中,point指示当前光标位置,这个位置坐标是以相对于窗口客户区左上角的设备坐标而言的。如有必要,可用CDC::DPtoLP()将其转换为逻辑坐标。nFlags参数指出消息生成时鼠标键以及Shift和Ctrl的状态。通常用于判断鼠标或键盘状态,如 if(nFlags & MK_LBUTTON) ...。
非客户区消息的处理函数原型为:
afx_msg void OnMsgName(UINT nHitTest,CPoint point)
其中,point指示事件在窗口中发生的位置,但这个位置是屏幕坐标。可用CWnd::ScreenToClient()把屏幕坐标转换为客户区坐标。nHitTest参数包含标识窗口非客户区事件发生地方的命中测试码。如HTCAPTION、HTCLOSE、HTSYSMENU等。可在WM_NCHITTEST或CWnd::OnNcHitTest()的帮助文档中找到完整的命中测试码列表。实际应用与nFlags类似。
鼠标光标是鼠标与用户之间的接口。我们知道,每个窗口都有相应的WNDCLASS, 其特性在WNDCLASS结构中定义。其中的hCursor字段保存了窗口类型光标的句柄,该光标就是在窗口客户区上的图象。在鼠标移动时,windows通过重画光标的背景把光标从旧位置上清除。然后,它给光标下的窗口发送包含命中测试码的WM_SETCURSOR消息。对此消息系统的默认响应是调用::SetCursor()API函数。如果命中测试码是HTCLIENT(即在客户区中),则显示设置的类型光标,否则显示默认箭头光标。光标将根据鼠标移动时周期的捕获命中测试码自动更新。这个技巧也可以应用到其他图象处理过程中。
设置光标的方法总结:
1.注册WNDCLASS(如何注册,在后面知识点部分有总结),并指定希望的光标类型为窗口类光标;
2.或在OnSetCursor消息处理函数中调用API函数::SetCursor()响应WM_SETCURSOR消息。
windows应用程序了解键盘事件的方式与了解鼠标事件相同:都是通过消息。键盘消息只有三种,即
WM_KEYDOWN 键盘按下
WM_KEYUP 键盘抬起
WM_CHAR 可打印字符键按下或抬起(这是为了简化字符处理过程)
windows总是把键盘消息送到拥有输入焦点的窗口。它用WM_SETFOCUS和WM_KILLFOCUS消息通知即将通知接收或失去输入焦点的窗口。CWnd::SetFocus()把输入焦点转移到另一个窗口;CWnd::GetFocus()用于找到当前用于输入焦点的窗口,它的返回类型为CWnd。
一个键盘处理函数会接收很多有关击键的消息,其中包括一个代码用来标识被按下或释放的键值。原型如下:
afx_msg void OnMsgName(UINT nChar,UINT nRepCnt,UINT nFlags)
其中,nChar是被按下或释放的键的虚拟键值,nRepCnt是重复次数,通常为1。nFlags参数包含键的扫描码以及一些位标志(详情可参考MSDN)。
和鼠标与光标关联一样,与键盘有关的系统资源是插入符。但光标是全局共享资源,而插入符是单线程共享资源,它被运行在同一线程上的所有窗口共享。
插入符的使用有一些简单规则:
1.插入符应在接收到输入焦点时创建,在失去输入焦点时“销毁”。相关函数有CreateCaret、CreateGrayCaret、::DestroyCaret等;
2.直到调用ShowCaret才能使创建的插入符可见,而调用HideCaret可将使其隐藏;
3.调用SetCaretPos来移动插入符,永远记住,控制插入符的移动是你自己的工作。对插入符位置的检索可用GetCaretPos。
一些知识点:
1.自定义窗口类型:在MFC中,可以用全局函数AfxRigisterWndClass注册窗口类型。这需要初始化WNDCLASS结构中的字段。AfxRigisterWndClass已为你自动填充了大多数字段,你一般只需要关心4个值。AfxRigisterWndClass的函数原型为:
LPCTSTR AfxRigisterWndClass(UINT nClassStyle,HCURSOR hCursor=0,HBRUSH hbrBackground=0,HICON hIcon=0)
其中,nClassStyle指定窗口类型样式(不是显示类型),而是窗口允许的某种操作特性。如CS_OWNDC,表示由WNDCLASS创建的窗口均有自己的设备描述表(DC);hCursor为由WNDCLASS创建的窗口标识“类型光标”。可通过AfxGetApp()->LoadStandardCursor(IDC_XXX)获得;hbrBackground参数定义了窗口的默认背景颜色;最后一个参数hIcon指定windows用来在桌面上、任务栏等地方代表应用程序的大、小图标。
在MFC中,调用此函数通常通过覆盖PreCreateWindow虚函数完成。
2.一些非主流的鼠标消息:
1)WM_NCHITTEST消息:窗口在接受一个客户区或非客户区鼠标消息之前,首先接收到光标的屏幕坐标和WM_NCHITTEST消息。windows一般默认处理它。一个使用OnNcHitTest处理函数的技巧是用HTCAPTION命中测试码代替HTCLIENT,以创建一个可在客户区拖动的窗口:
UINT CMainWindow::OnNcHitTest(CPoint point)
{
UINT nHitTest = CFrameWnd::OnNcHitTest(point);
if(nHitTest == HTCLIENT)
nHitTest=HICAPTION;
return nHitTest;
}
2)WM_MOUSELEAVE和WM_MOUSEHOVER消息:这两个消息可以使你知道鼠标何时进入窗口或在窗口中移动了。与此相关的API函数是::TrackMouseEvent(),通过它,一个程序可以注册,当光标离开窗口时接收WM_MOUSELEAVE消息,而光标在窗口中停滞时接收WM_MOUSEHOVER消息。::TrackMouseEvent()函数只有一个参数,是一个指向TRACKMOUSEEVENT结构的指针。
3)WM_MOUSEWHEEL消息: windows的ON_WM_MOUSEWHEEL宏将WM_MOUSEWHEEL消息映射到消息处理函数OnMouseWheel,其原型为:
BOOL OnMouseWheel(UINT nFlags,short zDelta,CPoint point)
其中,nFlags和point与其他鼠标处理函数意义相同,zDelta的值等于WHEEL_DELTA(向前滚动一个增量)或-WHEEL_DELTA(向后滚动一个增量)。
3.鼠标的捕获:这通常用于“橡皮筋”画图操作中。用CWnd::SetCapture捕获鼠标,相应的用::ReleaseCapture释放它。