VC++ 实现画图功能 HDraw29.docx
- 文档编号:11156181
- 上传时间:2023-02-25
- 格式:DOCX
- 页数:38
- 大小:278.93KB
VC++ 实现画图功能 HDraw29.docx
《VC++ 实现画图功能 HDraw29.docx》由会员分享,可在线阅读,更多相关《VC++ 实现画图功能 HDraw29.docx(38页珍藏版)》请在冰豆网上搜索。
VC++实现画图功能HDraw29
HDraw2.9程序文档
学生:
黄进东
时间:
2018-09-10
资料来源:
源码爱好者
目录
1.概述1
1.1.简介1
1.2.功能需求1
1.2.1.基本绘图功能:
(必须全部实现)1
1.2.2.高级编辑功能:
1
1.2.3.附加功能:
2
1.3.未实现的功能2
2.主要功能描述3
3.技术细节5
3.1.代码结构5
3.1.1.代码文件5
3.1.2.代码类5
3.2.SetROP2实现重绘6
3.3.嵌套View实现画布6
3.4.鼠标靠近目标时突出显示7
3.4.1.判断一点是否属于矩形HStrokeRect7
3.4.2.判断一点是否属于线段8
3.4.3.判断一点是否属于椭圆8
3.5.文档序列化9
3.6.打开保存导出10
3.7.友好用户界面13
3.8.右键菜单修改选中图形的属性14
3.9.撤销和恢复操作15
3.10.使用鼠标拖拽选中多个图形16
3.11.直线HStrokeLine的Tracker只显示两个Point17
3.12.键盘控制18
3.13.对话框控制20
3.14.动画程序图标20
3.15.LButtonDown流程21
3.16.LButtonUp流程:
21
3.17.MouseMove流程:
22
4.总结23
4.1.Tricks23
4.1.1.子View和父View公用一个Doc23
4.1.2.在类中获取其它类的句柄23
4.1.3.CRectTracker用法24
4.1.4.内存泄露25
4.2.收获25
4.2.1.MSDN文档25
4.2.2.Debug26
4.2.3.XX知道26
5.参考文献27
1.概述
1.1.简介
使用VC开发平台,MFC框架实现一个画图程序,尽可能多的实现Windows自带的画图功能,并扩展其功能。
1.2.功能需求
1.2.1.基本绘图功能:
(必须全部实现)
(1)能够用鼠标操控方式,绘制直线、矩形、椭圆。
(2)在绘图时,选择绘制某种图像后(如直线),在画布中按住鼠标左键后移动鼠标,在画布中实时的根据鼠标的移动显示相应的图形。
在松开鼠标左键后,一次绘图操作完成。
(3)能够在绘制一图形(如一条直线)前设置线的粗细、颜色。
(以菜单方式)
(4)可以以矢量图方式保存绘制的图形。
(5)可以读取保存的矢量图形文件,并显示绘图的结果。
界面友好的要求:
(6)有画直线、矩形、椭圆的工具箱。
(7)有颜色选择工具箱。
(8)对于当前选中的绘图工具,以“下沉”的形式显示。
(9)在状态栏中显示鼠标的位置。
(10)在鼠标移向一工具不动时,有工具的功能提示。
(11)在菜单上有当前选中的菜单项标识(即前面有小钩)
(12)可以用鼠标操作方式,通过“拖拽”方式,改变画布的大小。
(13)在画布大而外框小时,应有水平或垂直方向的滚动条。
1.2.2.高级编辑功能:
(1)具有Undo功能。
(2)可以用鼠标选中绘制的某一图形。
被选中的图形符号有标识(参见Word,如一直线段,其两端点上加了两个小框;矩形上有8个小框点)。
(3)当鼠标靠近某一目标时,鼠标的形状发生改变
(4)修改被选中的图形。
通过鼠标的“拖拽”,可以改变图形的位置、或大小。
(5)修改被选中图形的颜色、笔划的粗细。
(6)删除被选中的图形。
(7)可以使用鼠标“拖拽”一个虚矩形框,一次选择多个图形。
(8)可以使用Ctrl或Shift加鼠标左键选择多个图形对象。
1.2.3.附加功能:
(1)可选择打开或关闭工具栏。
(2)应用程序的标题栏上有程序的图标。
(3)将图形转换成位图文件的形式保存。
(4)在选择一个图形元素后(如直线),会有进一步选择线型或线宽的界面。
(5)仿Word,选择“线型”、“粗细”图标后,会出现进一步选择的选项卡。
1.3.未实现的功能
2.主要功能描述
右键修改选中图形的颜色,粗细,线型,删除选中图形
右键和鼠标调整图形大小
对话框矢量修改所有图形
3.技术细节
3.1.代码结构
3.1.1.代码文件
MFC自动生成的文件
1个CHDrawPView
1个HStroke
2个Dialog(HStrokeEditDlg+HStrokeTextDlg)
1个ToolBar(HColorBar)
3.1.2.代码类
HDrawPView文件只有一个类:
CHDrawPView,该类集成自MFC的CScrollView,主要实现维护画布类CHDrawView和滚动功能
HStroke文件里包含目前所有的图形类信息,包括集成与MFC的CObject类的基类HStroke,以及集成自HStroke的具体图形类HStrokeLine(直线),HStrokeRect(矩形),HStrokeEllipse(椭圆),HStrokeText(文本),HStrokePoly(曲线)。
HStrokeEditDlg文件只有一个类:
HStrokeEditDlg,该类集成自MFC的CDialog类,主要用来编辑已有图形类,如下图所示:
HStrokeTextDlg文件只有一个类:
HStrokeTextDlg,该类集成自MFC的CDialog类,主要用来画文本时输入文本信息,如图所示:
HColorBar类只有一个类:
HColorBar类,该类集成自MFC的CToolBar类,呈现一个颜色框,方便用户在绘图时选择不同的颜色。
3.2.SetROP2实现重绘
在画图状态下,鼠标移动时既要擦除旧图形,又要绘制新图形。
这里主要有两种实现方法:
一是全部重绘,二是先擦除旧图形。
如果使用矢量图全部重绘,频繁的绘图动作消耗很大,很容易造成屏幕闪动。
但是如果将已有图形保存为位图,然后重绘的时候只要绘制位图即可,这样能避免闪动。
第二种方法要考虑的就是擦除旧图形的问题,本程序使用SetROP2函数设置MASK的方式,每次绘图时采用非异或运算的方式擦除旧图形:
pDC->SetROP2(R2_NOTXORPEN);//设置ROP2
DrawStroke(pDC);//画图擦除旧线(自定义函数)
SetCurrentPoint(point);//设置新点的坐标(自定义函数)
DrawStroke(pDC);//画新线(自定义函数)
3.3.嵌套View实现画布
m_drawView=newCHDrawView();//创建画布View
if(!
m_drawView->CreateEx(WS_EX_LEFT|WS_EX_LTRREADING|WS_EX_RIGHTSCROLLBAR,
AfxRegisterWndClass(CS_VREDRAW|CS_HREDRAW,LoadCursor(NULL,IDC_CROSS),
(HBRUSH)GetStockObject(WHITE_BRUSH),NULL),///白色画布
"",WS_CHILDWINDOW|WS_VISIBLE|WS_CLIPCHILDREN|WS_CLIPSIBLINGS,
m_tracker.m_rect.left,m_tracker.m_rect.top,
m_tracker.m_rect.right-1,m_tracker.m_rect.bottom-1,
this->m_hWnd,NULL)){
TRACE0("Failedtocreatetoolbar\n");
return-1;//failtocreate
}
m_drawView->SetDocument((CHDrawDoc*)m_pDocument);//传递CDocument给新View
m_drawView->ShowWindow(SW_NORMAL);
m_drawView->UpdateWindow();
//设置背景View颜色为灰色
SetClassLong(m_hWnd,GCL_HBRBACKGROUND,(long)GetStockObject(GRAY_BRUSH));
3.4.鼠标靠近目标时突出显示
在鼠标移动的时候,OnMouseMove函数会遍历已有图形,判断鼠标所在点是否属于已有图形范围,如果是,则高亮显示该图形。
高亮显示的方法比较简单,只要增加CRectTracker即可,而判断当前点是否属于某图形比较有意思:
3.4.1.判断一点是否属于矩形HStrokeRect
使用用MFC的CRect类的IsPointIn方法,当鼠标在矩形边框附近时,认为该点属于HStrokeRect。
如图,实线矩形表示HStrokeRect。
外矩形为外面的虚线矩形,内矩形为里面的虚线矩形:
BOOLHStrokeRect:
:
IsPointIn(constCPoint&point){
//矩形左上角x坐标
intx1=m_points.GetAt(0).x (1).x? m_points.GetAt(0).x: m_points.GetAt (1).x; //矩形左上角y坐标 inty1=m_points.GetAt(0).y (1).y? m_points.GetAt(0).y: m_points.GetAt (1).y; //矩形右下角x坐标 intx2=m_points.GetAt(0).x>m_points.GetAt (1).x? m_points.GetAt(0).x: m_points.GetAt (1).x; //矩形右下角y坐标 inty2=m_points.GetAt(0).y>m_points.GetAt (1).y? m_points.GetAt(0).y: m_points.GetAt (1).y; //构建外矩行和内矩形 CRectrect(x1,y1,x2,y2),rect2(x1+5,y1+5,x2-5,y2-5); //如果在外矩形内并在内矩形外 if(rect.PtInRect(point)&&! rect2.PtInRect(point)) returnTRUE; else returnFALSE; } 3.4.2.判断一点是否属于线段 首先判断一点是否属于这条线段所属的直线,根据直线的判定公式y1/x1=y2/x2得到x1*y2-x2*y1=0,但是在画图中应该在直线附近就能选中,所以在本程序中: |x1*y2-x2*y1|<偏差,然后判断该点是否属于这条线段。 //计算该点到线段HStrokeLine的两个顶点的线段(x1,y1),(x2,y2) intx1=point.x-m_points.GetAt(0).x; intx2=point.x-m_points.GetAt (1).x; inty1=point.y-m_points.GetAt(0).y; inty2=point.y-m_points.GetAt (1).y; //计算判断量x1*y2-x2*y1 intmeasure=x1*y2-x2*y1; //误差允许范围,也就是直线的“附近” intrule=abs(m_points.GetAt (1).x-m_points.GetAt(0).x) +abs(m_points.GetAt(0).y-m_points.GetAt (1).y); rule*=m_penWidth;//将线宽考虑进去 //属于直线 if(measure //判断该点是否属于这条线段 if(x1*x2<0) returnTRUE;; } returnFALSE; 3.4.3.判断一点是否属于椭圆 根据椭圆的定义椭圆上的点到椭圆的两个焦点的距离之和为2a,首先计算出椭圆的a,b,c,然后计算出椭圆的两个焦点。 针对某个点,首先根据点坐标和两个焦点的坐标计算出该点到椭圆焦点的距离,然后减去2a,如果在“附近”,则认为其属于HStrokeEllipse,否则不属于。 //计算椭圆的a,b,c int_2a=abs(m_points.GetAt(0).x-m_points.GetAt (1).x); int_2b=abs(m_points.GetAt(0).y-m_points.GetAt (1).y); doublec=sqrt(abs(_2a*_2a-_2b*_2b))/2; //计算椭圆的焦点 doublex1,y1,x2,y2; if(_2a>_2b){//横椭圆 x1=(double)(m_points.GetAt(0).x+m_points.GetAt (1).x)/2-c; x2=x1+2*c; y1=y2=(m_points.GetAt(0).y+m_points.GetAt (1).y)/2; } else{//纵椭圆 _2a=_2b; x1=x2=(m_points.GetAt(0).x+m_points.GetAt (1).x)/2; y1=(m_points.GetAt(0).y+m_points.GetAt (1).y)/2-c; y2=y1+2*c; } //点到两个焦点的距离之和,再减去2a //distance(point-p1)+distance(point-p2)=2*a; doublemeasure=sqrt((x1-point.x)*(x1-point.x)+(y1-point.y)*(y1-point.y)) +sqrt((point.x-x2)*(point.x-x2)+(point.y-y2)*(point.y-y2)) -_2a; //计算椭圆的“附近” doublerule=4*m_penWidth; if(measure returnTRUE; else returnFALSE; 3.5.文档序列化 MFC提供了良好的序列化机制,只要在类定义时加入DECLARE_SERIAL宏,在类构造函数的实现前加入IMPLEMENT_SERIAL宏,然后实现Serialize方法即可。 本程序即使用该方法序列化: 首先在CHDrawDoc类实现Serialize方法,保存画布大小和所有图形信息: voidCHDrawDoc: : Serialize(CArchive&ar) { if(ar.IsStoring()) { //保存时,首先保存画布高和宽,然后序列化所有图形 ar< m_strokeList.Serialize(ar); } else { //打开时,首先打开画布高和宽,然后打开所有图形 ar>>m_cavasH>>m_cavasW; m_strokeList.Serialize(ar); } } m_strokeList.Serialize(ar);这一句很神奇,Debug追踪的时候会发现,容器类会自动序列化容器内的元素数量,并调用每个元素的序列化方法序列化,所以还需要对每个图形元素实现序列化,以HStrokeLine为例: 在HStrokeLine的类声明中: classHStrokeLine: publicHStroke { public: HStrokeLine(); DECLARE_SERIAL(HStrokeLine) 然后在HStrokeLine的构造函数实现前: IMPLEMENT_SERIAL(HStrokeLine,CObject,1) HStrokeLine: : HStrokeLine() { m_picType=PIC_line; } 最后实现HStrokeLine的序列化函数,因为这里HStrokeLine集成自HStroke类而且没有特殊的属性,而HStroke类实现了Serialize函数,所以HStrokeLine类不需要实现Serilize方法,看一下HStroke的Serialize方法即可: voidHStroke: : Serialize(CArchive&ar) { if(ar.IsStoring()){ intenumIndex=m_picType; ar< m_points.Serialize(ar); } else{ intenumIndex; ar>>enumIndex>>m_penWidth>>m_penColor; m_picType=(enumHPicType)enumIndex; m_points.Serialize(ar); } } 3.6.打开保存导出 文档序列化实现以后,程序的打开和保存功能就已经完成了。 但是从序列化方法可以看出,打开和保存的都是矢量图形,所以这里实现了一个导出为BMP图像的方法,导出: //保存文件对话框,选择导出路径 CFileDialogdlg(FALSE,"bmp","hjz.bmp"); if(dlg.DoModal()! =IDOK){ return; } CStringfilePath=dlg.GetPathName(); // CClientDCclient(this);//用于本控件的,楼主可以不用此句 CDCcdc; CBitmapbitmap; RECTrect;CRectr; GetClientRect(&rect); intcx=rect.right-rect.left; intcy=rect.bottom-rect.top; bitmap.CreateCompatibleBitmap(&client,cx,cy); cdc.CreateCompatibleDC(NULL); //获取BMP对象 CBitmap*oldbitmap=(CBitmap*)cdc.SelectObject(&bitmap); //白色画布 cdc.FillRect(&rect,CBrush: : FromHandle((HBRUSH)GetStockObject(WHITE_BRUSH))); //画图 for(inti=0;i GetDocument()->m_strokeList.GetAt(i)->DrawStroke(&cdc); } cdc.SelectObject(oldbitmap); : : OpenClipboard(this->m_hWnd); : : EmptyClipboard(); : : SetClipboardData(CF_BITMAP,bitmap); : : CloseClipboard(); HBITMAPhBitmap=(HBITMAP)bitmap; HDChDC; intiBits; WORDwBitCount; DWORDdwPaletteSize=0,dwBmBitsSize=0,dwDIBSize=0,dwWritten=0; BITMAPBitmap; BITMAPFILEHEADERbmfHdr; BITMAPINFOHEADERbi; LPBITMAPINFOHEADERlpbi; HANDLEfh,hDib,hPal,hOldPal=NULL; hDC=CreateDC("DISPLAY",NULL,NULL,NULL); iBits=GetDeviceCaps(hDC,BITSPIXEL)*GetDeviceCaps(hDC,PLANES); DeleteDC(hDC); if(iBits<=1)wBitCount=1; elseif(iBits<=4)wBitCount=4; elseif(iBits<=8)wBitCount=8; elsewBitCount=24; GetObject(hBitmap,sizeof(Bitmap),(LPSTR)&Bitmap); bi.biSize=sizeof(BITMAPINFOHEADER); bi.biWidth=Bitmap.bmWidth; bi.biHeight=Bitmap.bmHeight; bi.biPlanes=1; bi.biBitCount=wBitCount; bi.biCompression=BI_RGB; bi.biSizeImage=0; bi.biXPelsPerMeter=0; bi.biYPelsPerMeter=0; bi.biClrImportant=0; bi.biClrUsed=0; dwBmBitsSize=((Bitmap.bmWidth*wBitCount+31)/32)*4*Bitmap.bmHeight; hDib=GlobalAlloc(GHND,dwBmBitsSize+dwPaletteSize+sizeof(BITMAPINFOHEADER)); lpbi=(LPBITMAPINFOHEADER)GlobalLock(hDib); *lpbi=bi; hPal=GetStockObject(DEFAULT_PALETTE); if(hPal) { hDC=: : GetDC(NULL); hOldPal=: : SelectPalette(hDC,(HPALETTE)hPal,FALSE); RealizePalette(hDC); } GetDIBits(hDC,hBitmap,0,(UINT)Bitmap.bmHeight,(LPSTR)lpbi+sizeof(BITMAPINFOHEADER) +dwPaletteSize,(BITMAPIN
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- VC+ 实现画图功能 HDraw29 VC 实现 画图 功能