VC++ 实现画图功能 HDraw29文档格式.docx
《VC++ 实现画图功能 HDraw29文档格式.docx》由会员分享,可在线阅读,更多相关《VC++ 实现画图功能 HDraw29文档格式.docx(38页珍藏版)》请在冰点文库上搜索。
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)在绘图时,选择绘制某种图像后(如直线),在画布中按住鼠标左键后移动鼠标,在画布中实时的根据鼠标的移动显示相应的图形。
在松开鼠标左键后,一次绘图操作完成。
(3)能够在绘制一图形(如一条直线)前设置线的粗细、颜色。
(以菜单方式)
(4)可以以矢量图方式保存绘制的图形。
(5)可以读取保存的矢量图形文件,并显示绘图的结果。
界面友好的要求:
(6)有画直线、矩形、椭圆的工具箱。
(7)有颜色选择工具箱。
(8)对于当前选中的绘图工具,以“下沉”的形式显示。
(9)在状态栏中显示鼠标的位置。
(10)在鼠标移向一工具不动时,有工具的功能提示。
(11)在菜单上有当前选中的菜单项标识(即前面有小钩)
(12)可以用鼠标操作方式,通过“拖拽”方式,改变画布的大小。
(13)在画布大而外框小时,应有水平或垂直方向的滚动条。
(1)具有Undo功能。
(2)可以用鼠标选中绘制的某一图形。
被选中的图形符号有标识(参见Word,如一直线段,其两端点上加了两个小框;
矩形上有8个小框点)。
(3)当鼠标靠近某一目标时,鼠标的形状发生改变
(4)修改被选中的图形。
通过鼠标的“拖拽”,可以改变图形的位置、或大小。
(5)修改被选中图形的颜色、笔划的粗细。
(6)删除被选中的图形。
(7)可以使用鼠标“拖拽”一个虚矩形框,一次选择多个图形。
(8)可以使用Ctrl或Shift加鼠标左键选择多个图形对象。
(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);
//设置新点的坐标(自定义函数)
//画新线(自定义函数)
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
ShowWindow(SW_NORMAL);
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<
m_points.GetAt
(1).x?
m_points.GetAt(0).x:
m_points.GetAt
(1).x;
//矩形左上角y坐标
inty1=m_points.GetAt(0).y<
m_points.GetAt
(1).y?
m_points.GetAt(0).y:
m_points.GetAt
(1).y;
//矩形右下角x坐标
intx2=m_points.GetAt(0).x>
//矩形右下角y坐标
inty2=m_points.GetAt(0).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<
rule&
measure>
-rule){
//判断该点是否属于这条线段
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;
-rule)
3.5.文档序列化
MFC提供了良好的序列化机制,只要在类定义时加入DECLARE_SERIAL宏,在类构造函数的实现前加入IMPLEMENT_SERIAL宏,然后实现Serialize方法即可。
本程序即使用该方法序列化:
首先在CHDrawDoc类实现Serialize方法,保存画布大小和所有图形信息:
voidCHDrawDoc:
Serialize(CArchive&
ar)
{
if(ar.IsStoring())
{
//保存时,首先保存画布高和宽,然后序列化所有图形
ar<
<
m_cavasH<
m_cavasW;
m_strokeList.Serialize(ar);
//打开时,首先打开画布高和宽,然后打开所有图形
ar>
>
m_cavasH>
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:
if(ar.IsStoring()){
intenumIndex=m_picType;
enumIndex<
m_penWidth<
m_penColor;
m_points.Serialize(ar);
else{
intenumIndex;
enumIndex>
m_penWidth>
m_picType=(enumHPicType)enumIndex;
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.GetSize();
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;
=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