其中消息处理函数的参数point是CPoint对象,它存储了产生鼠标消息时鼠标光标所处位置的坐标,如鼠标左键按下的处理函数中传入的point参数中存放了鼠标左键按下位置的坐标。参数nFlags是一个无符号数,它表明了在鼠标消息产生的时候鼠标按钮及部分键盘按键的状态,其值可为下表中值的任意组合: nFlags参数值 说明 MK_CONTROL Ctrl键按下 MK_LBUTTON 鼠标左键按下 MK_MBUTTON 鼠标中键按下 MK_RBUTTON 鼠标右键按下 MK_SHIFT Shift键按下 比如在鼠标左键按下的处理函数中,如果参数nFlags传入的值为MK_CONTROL,则表示在鼠标左键按下的同时键盘上的Ctrl键也被按下。
我们可以使用类向导来添加鼠标消息处理函数,应用程序框架将会自动填写代码完成鼠标消息和其处理函数之间的映像。打开类向导,在类列表中选择CDrawMapView类,在消息列表框中选择WM_LBUTTONDOWN消息并用鼠标左键双击,此时类向导自动在成员函数列表框中添加该消息的处理函数。因为该处理函数的名称不能修改,所以不会出现增加成员函数对话框。用同样方法添加WM_MOUSEMOVE消息和WM_LBUTTONUP消息的处理函数,因为前面我们制定的鼠标绘图方法中将要用到这三种鼠标消息的处理函数。我们打开CDrawMapView类的头文件,可以在其中看到如下代码:
// Generated message map functions protected:
//{{AFX_MSG(CDrawMapView) afx_msg void OnDrawline(); afx_msg void OnDrawellipse();
afx_msg void OnDrawellipseregion(); afx_msg void OnDrawrectangle();
afx_msg void OnLButtonDown(UINT nFlags, CPoint point); afx_msg void OnMouseMove(UINT nFlags, CPoint point); afx_msg void OnLButtonUp(UINT nFlags, CPoint point); //}}AFX_MSG
DECLARE_MESSAGE_MAP()
前面四句代码声明了工具条按钮的处理函数,而后三句代码则声明了鼠标消息的处理函数。
打开CDrawMapView类的类文件(CPP文件),在文件的开始部分可以看到如下代码:
BEGIN_MESSAGE_MAP(CDrawMapView, CView) //{{AFX_MSG_MAP(CDrawMapView)
ON_COMMAND(ID_DRAWLINE, OnDrawline)
ON_COMMAND(ID_DRAWELLIPSE, OnDrawellipse)
ON_COMMAND(ID_DRAWELLIPSEREGION, OnDrawellipseregion) ON_COMMAND(ID_DRAWRECTANGLE, OnDrawrectangle) ON_WM_LBUTTONDOWN() ON_WM_MOUSEMOVE()
ON_WM_LBUTTONUP() //}}AFX_MSG_MAP END_MESSAGE_MAP()
该段代码完成了消息及对应的处理函数之间的映像。其中前四句代码是工具条按钮与处理函数的映像,而后三句代码是鼠标消息和处理函数间的映像。之所以鼠标消息的映像中没有指定处理函数名,是因为鼠标消息处理函数的名称是固定的,不能修改。以上代码是类向导自动添加的,如果我们不使用类向导来创建鼠标消息的处理函数,也可以手动添加以上代码,效果是一样的。
现在看一下应用程序框架创建的原始的鼠标消息处理函数,代码如下: //鼠标左键按下处理函数
void CDrawMapView::OnLButtonDown(UINT nFlags, CPoint point) {
// TODO: Add your message handler code here and/or call default
CView::OnLButtonDown(nFlags, point); }
//鼠标移动处理函数
void CDrawMapView::OnMouseMove(UINT nFlags, CPoint point) {
// TODO: Add your message handler code here and/or call default
CView::OnMouseMove(nFlags, point); }
//鼠标左键抬起处理函数
void CDrawMapView::OnLButtonUp(UINT nFlags, CPoint point) {
// TODO: Add your message handler code here and/or call default
CView::OnLButtonUp(nFlags, point); }
我们发现在函数中已经添加了一句代码,这行代码是调用对应的父类的鼠标消息处理函数进行一些默认处理。我们所添加的代码都必须添加到该句代码之前,并且此句代码不能删除,否则将会出错。
同时需要注意的是,Windows的鼠标消息发生的间隔是一秒钟,并不是所有的鼠标动作都会产生鼠标消息。假设你按鼠标左键的速度足够快,在一秒钟内可以按鼠标左键多次,则并不是每次按键都会产生鼠标消息,只有第一次按键以及后面按键与前一次按键的时间间隔在一秒钟以上的那些按键才会产生鼠标消息。 2.2.2 用鼠标绘制直线段
现在看一下如何编码实现用鼠标绘制直线段。既然鼠标消息分为视图区鼠标消息和非视图区鼠标消息,那么如果在鼠标绘图过程中,鼠标在视图区内按下左键,然后移动到视图区外才把鼠标左键抬起,应用程序窗口就得不到视图区的鼠标左键抬起消息。因此在用鼠标绘制图形之前应该首先捕捉鼠标,使当前视图区
接受所有的鼠标操作引起的鼠标消息。 2.2.2.1 捕捉鼠标
我们可以用SetCapture函数和ReleaseCapture函数捕捉和释放鼠标。 ? SetCapture函数,用于捕捉鼠标,其函数声明如下: CWnd* SetCapture();
该函数是窗口基类CWnd的成员函数,因为CView类是从CWnd类派生而来的,所以它也拥有该函数。该函数返回捕捉了鼠标的窗口指针。执行该函数后,视图类就捕捉了鼠标,此后的鼠标动作都将产生视图区鼠标消息。
? ReleaseCapture函数,用于释放鼠标,其函数声明如下: BOOL ReleaseCapture();
该函数用于释放被捕捉的鼠标。在用鼠标绘图完毕后,需要调用该函数释放鼠标,否则窗口将不能正确接受鼠标消息。
除了在鼠标绘图开始时捕捉鼠标之外,也可以通过调用ClipCursor函数将鼠标限制在指定区域中以避免在鼠标绘图过程中出现不同类型的鼠标消息。
? ClipCursor函数,用于将鼠标限制在指定的矩形区域中,其函数声明如下: BOOL ClipCursor(CONST RECT lpRect);
参数lpRect指向一个RECT结构体,该结构体定义了一个矩形区域,该函数将鼠标限制在此矩形区域中。如果鼠标光标要移动到矩形区域外,系统将自动调正鼠标光标位置,使其始终在指定的矩形区域内。通常用如下代码把鼠标限制在应用程序窗口的视图区内:
CRect rect;//矩形区域对象
GetClientRect(&rect);//获得并保存窗口视图区区域坐标
ClientToScreen(&rect);//用视图区区域坐标重新计算屏幕坐标 ClipCursor(&rect);//限制鼠标在窗口视图区中 上面的代码将鼠标限制在窗口的视图区中,这样鼠标只能在视图区中产生动作,也就只会产生视图区鼠标消息。其中用到的GetClientRect函数和ClientToScreen函数都是CWnd类的成员函数。
? GetClientRect函数,用于获得窗口视图区的矩形区域坐标,其函数声明如下:
void GetClientRect(LPRECT lpRect) const;
该函数将窗口的视图区的左上角和右下角坐标存放在lpRect指针指向的矩形区域结构中。
? ClientToScreen函数,用于将传入的矩形区域坐标或点坐标转化成实际的屏幕坐标,其函数声明如下:
void ClientToScreen(LPPOINT lpPoint) const; void ClientToScreen(LPRECT lpRect) const;
参数lpPoint和lpRect分别指向点结构和矩形区域结构,该函数将传入的点的坐标或矩形区域的坐标(左上角点和右下角点坐标)转换成实际的屏幕坐标,这样调用ClipCursor函数时才能将鼠标限制在正确的区域中。
被限制的鼠标在绘图完毕后应该取消限制,采用如下语句来完成取消对鼠标的限制:
ClipCursor(NULL);
限制鼠标移动范围会加大系统负担,所以通常不采用此种方法。
2.2.2.2 设置鼠标光标形状
在用鼠标绘制图形时,我们希望修改鼠标光标形状,而不是使用默认的斜箭头光标。鼠标的光标形状由专门的光标(Cursor)资源所决定。我们可以在资源面板上向项目中添加光标资源(在添加资源对话框中有光标资源类型,如图2.3所示),每个光标资源对应一个唯一的资源ID,应用程序框架通过该ID来识别光标资源。要使用光标资源作为鼠标的光标形状,需要首先将光标资源加载到系统中,然后再设置鼠标光标形状为加载的光标资源的形状。
? LoadCursor函数,用于加载光标资源,其函数声明如下: HCURSOR LoadCursor(LPCTSTR lpszResourceName) const; HCURSOR LoadCursor(UINT nIDResource) const;
参数lpszResourceName和nIDResouce分别为光标资源的名称和ID号,函数将指定的光标资源加载到系统内存中。函数返回光标资源句柄(HCURSOR)。
? LoadStandardCursor函数,用于加载Windows预定义的光标资源,其函数声明如下:
HCURSOR LoadStandardCursor(LPCTSTR lpszCursorName) const;
参数lpszCursorName是由一些以IDC_开头的光标资源名称,用来指定Windows预定义的光标资源,下表中列出了预定义的光标资源名称和对应的光标形状: 预定义的光标资源名称 光标形状 IDC_ARROW 标准的斜箭头光标 IDC_IBEAM 标准的插入文本光标 IDC_WAIT 沙漏形状的光标 IDC_CROSS 标准的十字光标 IDC_UPARROW 向上方向的箭头 IDC_SIZEALL 带有四个方向箭头的光标 IDC_SIZENSWE 带有左上和右下方向箭头的光标 IDC_SIZENESW 带有右上和左下方向箭头的光标 IDC_SIZEWE 带有左右方向箭头的光标 IDC_SIZENS 带有上下方向箭头的光标 该函数同样返回光标资源句柄。 上面两个加载光标资源的函数都是应用程序基类CWinApp的成员函数,在视图类中要使用该函数,需要调用AfxGetApp()函数获得应用程序基类的指针,然后再调用加载光标资源函数。
加载完光标资源后,调用SetCursor函数设置使用光标资源。
? SetCursor函数,用于设置当前使用的光标资源,其函数声明如下: HCURSOR SetCursor(HCURSOR hCursor);
参数hCursor为要设置的光标资源句柄。函数返回原来使用的光标资源的句柄。
在我们的绘图应用程序中,使用鼠标绘图时,设置鼠标光标形状为标准的十字光标。在CDrawMapView类中加入下面的成员变量:
HCURSOR m_Cursor;//光标资源句柄
该变量存放应用程序当前使用的光标资源句柄。在绘图工具条按钮的处理函数中设置对应的光标资源句柄,添加如下代码到四个绘图工具条按钮的处理函数
中:
//设置鼠标光标形状为标准十字光标
m_Cursor = AfxGetApp()->LoadStandardCursor(IDC_CROSS); 因为应用程序初始状态就是绘图状态(绘制直线段),所以此代码也需加入到CDrawMapView类的构造函数中。然后只需在鼠标消息处理函数中加入如下代码设置使用光标资源:
SetCursor(m_Cursor);
三个鼠标消息的处理函数中都要加入该行代码。假设在鼠标左键按下的处理函数中不设置使用鼠标资源,则当鼠标左键按下时,鼠标光标将变回到默认的斜箭头状态。
2.2.2.3 使用橡皮线绘图
在使用鼠标绘图的时候,当鼠标左键按下时表示绘图开始,此时随着鼠标光标的移动,希望实时的把图形绘制出来,这样用户可以随时看到自己要绘制的图形是什么样的,而不是只有到最后鼠标左键抬起的时候才把图形绘制出来。为了实现这种效果可以在鼠标移动消息处理函数中就把当前图形绘制出来,这样每当鼠标移动消息处理函数被调用的时候都会将当前鼠标光标所处位置和鼠标左键按下位置所确定的图形绘制出来。但是如果一直绘图的话,每次绘制的图形都留在视图区中,会产生许多根本不需要的图形。所以正确的做法是每次绘制图形时都先擦除上次所绘制的图形,然后再绘制新的图形。这种绘图方法就称为使用橡皮线绘图(意指绘图线像橡皮一样可以擦除以前绘制的图形)。
因为在本章的绘图应用程序中除了要用鼠标绘制图形之外,还要用鼠标选择图形并进行编辑,所以我们单独编写三个函数,分别应用在鼠标绘图、鼠标移动、鼠标左键抬起三个鼠标消息的处理上。在CDrawMapView类中添加下面三个成员函数:
//鼠标绘图时鼠标左键按下消息处理函数
void DrawLButtonDown(UINT nFlags, CPoint point); //鼠标绘图时鼠标移动消息处理函数
void DrawMouseMove(UINT nFlags, CPoint point); //鼠标绘图时鼠标左键抬起消息处理函数 void DrawLButtonUp(UINT nFlags, CPoint point);
这三个函数的参数与系统的鼠标消息处理函数参数相同,我们分别编写这三个函数,编写完的代码如下:
//鼠标绘图时鼠标左键按下处理函数
void CDrawMapView::DrawLButtonDown(UINT nFlags, CPoint point) {
SetCursor(m_Cursor);//设置使用光标资源
this->SetCapture();//捕捉鼠标
//设置开始点和终止点,此时为同一点 m_StartPoint = point; m_EndPoint = point;
m_LButtonDown = true;//设置鼠标左键按下 }