一、建立工程
在Visual Studio 6.0中新建一个单文档工程,除在文档模式中选择单文档方式外,其它步骤均选择默认配置,工程名为CreateLine。
二、鼠标划线的实现思路
本例为使用鼠标绘制线段、矩形、圆及任意曲线等图形。
1、画线段:在菜单中选择划线命令,在窗口客户区可以单击鼠标左键连续划线,双击鼠标左键结束划线。设置一个保存鼠标 单击次数的变量,当变量值为0时,使用MoveTo函数设置起始点,变量值大于0时,使用LineTo函数实现连续划线;利用鼠标左键双击消息处理函数, 设置鼠标单击次数变量值为-1,结束划线;利用鼠标移动消息处理函数实现划线过程中的橡皮线显示。
2、画圆:选择画圆菜单,点击鼠标左键确定圆心,释放左键并拖动鼠标以确定半径,再次点击鼠标时画圆。
3、画矩形:与画圆类似,点击鼠标左键确定矩形左上角,释放左键并拖动鼠标已确定矩形尺寸,再次点击鼠标时画矩形。
4、画任意曲线:选择菜单,点击鼠标左键确定曲线起始点,释放并拖动鼠标绘制任意曲线,再次点击鼠标左键结束绘制。
三、具体实现
1、设置成员变量:
在工程的ClassView中右键选择CCreateLineView类,选择添加成员变量,包括记录图形类型的CString变量 m_drawtype;记录线段起始和终止点(对圆来说则是圆心坐标和圆周上的点坐标)的m_Startp和m_Endp,类型为CPoint;添加记录 鼠标左键单击次数的变量m_step(类型为int)。
2、添加绘图菜单命令:
为统一处理鼠标绘图菜单消息,这里用到了ON_COMMAND_RANGE宏和ON_UPDATE_COMMAND_UI_RANGE宏,用于映射一组绘图菜单命令。下面是MFC手册中关于ON_COMMAND_RANGE宏的用法:
ON_COMMAND_RANGE( id1, id2, memberFxn )
参数: id1 一个连续范围的命令ID的起始值。
id2 一个连续范围的命令ID的结束值。
memberFxn 命令组被映射到的消息处理函数的名字。
说明:
不同于ON_COMMAND把单个命令ID映射到成员函数。使用这个宏可以把一个连续范围的多个命令ID映射到单个命令处理成员函数。ID的范围从id1 开始,到id2结束,也就是说这一组命令ID要连续。一般来说,只要连续定义菜单等命令,其ID都是连续的,可以通过 View->Resourec symbloes菜单,或String Table资源来查看。如果命令ID不连续,可以直接在Resource.h中修改。
由于ClassWizard不支持消息映射范围,所以你必须自己写入这个宏。确保你把它写在了消息映射的“//{{AFX_MSG_MAP”分界符外面。
基于此,可以在ResourceView的菜单资源中添加“鼠标绘图”菜单,在该菜单下添加“绘直线”、“画圆”、“画矩形”、“画任意曲线”等二级菜单,并编辑各菜单的命令ID名称,如画曲线的命令ID为ID_CURVE。
在CreateLineView.h和CreateLineView.cpp文件中手工添加如下代码:
// CreateLineView.h : interface of the CCreateLineView class
......
class CCreateLineView : public CView
{
......
// Generated message map functions
protected:
//{{AFX_MSG(CCreateLineView)
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
afx_msg void OnLButtonDblClk(UINT nFlags, CPoint point);
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
//}}AFX_MSG
//以上为使用类向导添加的消息处理函数(后面还有详细说明)
//下两行代码为手工添加,用于处理鼠标绘图菜单的统一处理
afx_msg void OnDrawByMouse(int m_nID);
afx_msg void OnUpdateDrawByMouse(CCmdUI *pCmdUI);
......
};
......
//CreateLineView.cpp :implementation of the CCreateLineView class
......
//消息映射
BEGIN_MESSAGE_MAP(CCreateLineView, CView)
//{{AFX_MSG_MAP(CCreateLineView)
ON_WM_LBUTTONDOWN()
ON_WM_LBUTTONDBLCLK()
ON_WM_MOUSEMOVE()
//}}AFX_MSG_MAP
// Standard printing commands
ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)
......
//以下两行代码为手工添加,用于鼠标绘图菜单的统一处理
//设置菜单范围的第一个和最后一个菜单项
ON_COMMAND_RANGE(ID_DRAWLINE,ID_CURVE,OnDrawByMouse)
ON_UPDATE_COMMAND_UI_RANGE(ID_DRAWLINE,ID_CURVE,OnUpdateDrawByMouse)
END_MESSAGE_MAP()
......
//手工添加相应函数
void CCreateLineView::OnDrawByMouse(int m_nID)
{
switch(m_nID)
{
case ID_DRAWLINE:
m_drawtype="line";
m_step=0;
break;
case ID_DRAWCIRCLE:
m_drawtype="circle";
m_step=0;
break;
case ID_DRAWRECT:
m_drawtype="rect";
m_step=0;
break;
case ID_CURVE:
m_drawtype="curve";
m_step=0;
break;
default:;
}
}
void CCreateLineView::OnUpdateDrawByMouse(CCmdUI *pCmdUI)
{
int nFlag = 0 ;
switch(pCmdUI->m_nID)
{
case ID_DRAWLINE:
if(m_drawtype=="line")
nFlag=1;
break;
case ID_DRAWCIRCLE:
if(m_drawtype=="circle")
nFlag=1;
break;
case ID_DRAWRECT:
if(m_drawtype=="rect")
nFlag=1;
break;
case ID_CURVE:
if(m_drawtype=="curve")
nFlag=1;
break;
default:;
}
pCmdUI->SetCheck(nFlag);
}
......
3、添加鼠标左键单击消息处理函数:
在ClassWizard中为CCreateLineView类添加鼠标左键单击消息处理函数(消息名称为WM_LBUTTONDOWN):
void CCreateLineView::OnLButtonDown(UINT nFlags, CPoint point)
{
CDC* pDC = GetDC() ;//获取设备环境
pDC->SelectStockObject(NULL_BRUSH) ;//将库存GDI对象选进设备环境
if(m_drawtype== "line")
{
if(m_step==0)//第一次单击鼠标左键确定直线起始点
{
m_Startp=m_Endp=point;
m_step++;
}
if(m_step>0)//再次单击鼠标左键确定直线段终点,可以连续绘制折线
{
m_Endp=point;
m_step++;
DrawLine(pDC,m_Startp,m_Endp);//定义的成员函数
m_Startp=point;
}
}
if(m_drawtype== "circle")
{
if(m_step==0)
{
m_Startp=m_Endp=point;
m_step=1;
}
else if(m_step==1)//如果没有else,两个条件语句都会执行(上一if改变了条件变量)
{
m_Endp=point;
m_step=0;//等于0可以继续画圆,等于-1则一次只能画一个圆。双击鼠标左键可以停止画图。
DrawCircle(pDC,m_Startp,m_Endp);//定义的成员函数
}
}
if(m_drawtype== "rect")
{
if(m_step==0)
{
m_Startp=m_Endp=point;
m_step=1;
}
else if(m_step==1)
{
m_Endp=point;
m_step=0;
DrawRect(pDC,m_Startp,m_Endp);//定义的成员函数
}
}
//绘制曲线在鼠标移动函数中具体实现
if(m_drawtype == "curve")
{
if(m_step==0)//第一次单击鼠标左键确定曲线起始点
{
m_Startp=m_Endp=point;
m_step=1;
}
else if(m_step==1)//再次单击鼠标左键结束绘制曲线
m_step=0;
}
ReleaseDC(pDC) ;//释放掉不再使用的DC ;
CView::OnLButtonDown(nFlags, point);
}
4、添加鼠标左键双击消息处理函数:
在ClassWizard中为CCreateLineView类添加鼠标左键双击消息处理函数(消息名称为WM_LBUTTONDBLCLK):
void CCreateLineView::OnLButtonDblClk(UINT nFlags, CPoint point)
{
m_step=-1;//表示结束划线。
CView::OnLButtonDblClk(nFlags, point);
}
5、添加成员函数:
为CCreateLineView类添加DrawLine等成员函数:
// CreateLineView.h
protected:
void DrawLine(CDC *pDC,CPoint startPoint,CPoint endPoint);
void DrawRect(CDC *pDC,CPoint m_Startp,CPoint m_Endp);
int ComputeRadius(CPoint m_Centerp,CPoint m_Arroundp);
void DrawCircle(CDC *pDC,CPoint m_Centerp,CPoint m_Arroundp);
// CreateLineView.cpp
void CCreateLineView::DrawLine(CDC *pDC,CPoint startPoint, CPoint endPoint)
{
pDC->MoveTo(startPoint);
pDC->LineTo(endPoint);
}
void CCreateLineView::DrawCircle(CDC *pDC, CPoint m_Centerp, CPoint m_Arroundp)
{
int radius=ComputeRadius(m_Centerp,m_Arroundp);
CRect rc(m_Centerp.x-radius,m_Centerp.y-radius,m_Centerp.x+radius,m_Centerp.y+radius);
pDC->Ellipse(rc);
}
//计算半径
int CCreateLineView::ComputeRadius(CPoint m_Centerp, CPoint m_Arroundp)
{
int dx=m_Centerp.x-m_Arroundp.x;
int dy=m_Centerp.y-m_Arroundp.y;
return (int)sqrt(dx*dx+dy*dy);
}
void CCreateLineView::DrawRect(CDC *pDC, CPoint m_Startp, CPoint m_Endp)
{
pDC->Rectangle(m_Startp.x,m_Startp.y,m_Endp.x,m_Endp.y);
}
6、OnMouseMove函数(包括橡皮线的实现):
在ClassWizard中为CCreateLineView类添加鼠标移动消息处理函数(消息名称为WM_MOUSEMOVE):
//参考《Visual C++ 实践与提高》之图形图像编程篇
void CCreateLineView::OnMouseMove(UINT nFlags, CPoint point)
{
//插入设置状态栏的代码(后面有详细说明)
CDC* pDC = GetDC() ;
//设置绘图模式,并将先前的绘图模式加以保存
int nDrawmode = pDC->SetROP2(R2_NOT) ;// 设置绘图像素的颜色为屏幕颜色的反色
//将库存GDI对象选进设备环境
pDC->SelectStockObject(NULL_BRUSH) ;
if(m_drawtype=="line")
{
if(m_step >0)
{
CPoint prePnt, curPnt ;
// 获得鼠标所在的前一个位置
prePnt = m_Endp ;
curPnt = point ;
//绘制橡皮线
DrawLine(pDC, m_Startp,prePnt) ;//该语句起到抹掉上一次划线的作用。当第一次点击鼠标
//左键时,m_Startp=m_Endp,m_step由0变为1,移动鼠标时,先划了从m_Startp到m_Endp的
//线(由于两个点的位置相同,系统并未真正划线),又划了从m_Startp到鼠标移动的当前点
//的线,同时,m_Endp被赋值为鼠标移动的当前点;再次移动鼠标,又会先划一条从m_Startp
//到m_Endp的线,等于重划了上次移动鼠标时所划的第二条线,由于设置线像素的颜色为屏幕
//颜色的反色,重划线等于抹掉原来划的线;然后再划从m_Startp到鼠标移动的当前点的线,
//依次类推,划出新橡皮线,抹掉旧线,就起到了橡皮线的作用。
DrawLine(pDC, m_Startp,curPnt) ;
m_Endp = point ;
}
}
if(m_drawtype=="circle")
{
if(m_step>0)
{
CPoint prePnt, curPnt ;
// 获得鼠标所在的前一个位置
prePnt = m_Endp ;
curPnt = point ;
DrawCircle(pDC, m_Startp,prePnt) ;//绘制橡皮线
DrawCircle(pDC, m_Startp,curPnt) ;
m_Endp = point ;
}
}
if(m_drawtype=="rect")
{
if(m_step>0)
{
CPoint prePnt, curPnt ;
// 获得鼠标所在的前一个位置
prePnt = m_Endp ;
curPnt = point ;
//绘制橡皮线
DrawRect(pDC, m_Startp,prePnt) ;
DrawRect(pDC, m_Startp,curPnt) ;
m_Endp = point ;
}
}
if(m_drawtype=="curve")
{
if(m_step ==1)
{
CPoint prePnt, curPnt ;
// 获得鼠标所在的前一个位置
prePnt = m_Endp ;
curPnt = point ;
//绘制任意曲线
//划任意曲线的方式:点击划线菜单,在窗口中点击选择起始点位置,移动鼠标就可以任意划
//线,再次点击鼠标左键,结束当前曲线输入;选择新的曲线起始点可以继续绘制新曲线。双
//击鼠标左键结束曲线绘制。
DrawLine(pDC, prePnt,curPnt) ;
m_Endp = point ;
}
}
//恢复到先前的绘图模式
pDC->SetROP2(nDrawmode) ;
//释放掉不再使用的DC ;
ReleaseDC(pDC) ;
CView::OnMouseMove(nFlags, point);
}
7、状态栏显示鼠标坐标:
在框架类的状态栏设置中增加一个分割区:
// MainFrm.cpp : implementation of the CMainFrame class
static UINT indicators[] =
{
ID_SEPARATOR, //增加一个状态分隔区
ID_SEPARATOR, // status line indicator
ID_INDICATOR_CAPS,
ID_INDICATOR_NUM,
ID_INDICATOR_SCRL,
};
在视图类的鼠标移动消息处理函数中增加状态显示代码:
// CreateLineView.cpp
void CCreateLineView::OnMouseMove(UINT nFlags, CPoint point)
{
//设置状态条,显示鼠标坐标
CStatusBar* pStatus=
(CStatusBar*)AfxGetApp()->m_pMainWnd->GetDescendantWindow(ID_VIEW_STATUS_BAR);
//ASSERT(pStatus) ;
CString str ;
str.Format("(%8d,%8d)",point.x,point.y) ;
if(pStatus)
{
//pStatus->SetPaneText(0,str);未增加状态栏分割区前,只能在第1个分割区显示
pStatus->SetPaneText(1,str);//在增加了状态栏分割区后,可在第2个分割区显示
}
//插入前面提到的其它绘图代码
CView::OnMouseMove(nFlags, point);
}