1、VC+ 实现画图功能 HDraw29HDraw2.9程序文档学生:黄进东时间:2018-09-10资料来源:源码爱好者目录1. 概述 11.1. 简介 11.2. 功能需求 11.2.1. 基本绘图功能:(必须全部实现) 11.2.2. 高级编辑功能: 11.2.3. 附加功能: 21.3. 未实现的功能 22. 主要功能描述 33. 技术细节 53.1. 代码结构 53.1.1. 代码文件 53.1.2. 代码类 53.2. SetROP2实现重绘 63.3. 嵌套View实现画布 63.4. 鼠标靠近目标时突出显示 73.4.1. 判断一点是否属于矩形HStrokeRect 73.4.2.
2、 判断一点是否属于线段 83.4.3. 判断一点是否属于椭圆 83.5. 文档序列化 93.6. 打开保存导出 103.7. 友好用户界面 133.8. 右键菜单修改选中图形的属性 143.9. 撤销和恢复操作 153.10. 使用鼠标拖拽选中多个图形 163.11. 直线HStrokeLine的Tracker只显示两个Point 173.12. 键盘控制 183.13. 对话框控制 203.14. 动画程序图标 203.15. LButtonDown流程 213.16. LButtonUp流程: 213.17. MouseMove流程: 224. 总结 234.1. Tricks 234.1
3、.1. 子View和父View公用一个Doc 234.1.2. 在类中获取其它类的句柄 234.1.3. CRectTracker用法 244.1.4. 内存泄露 254.2. 收获 254.2.1. MSDN文档 254.2.2. Debug 264.2.3. XX知道 265. 参考文献 271. 概述1.1. 简介使用VC开发平台,MFC框架实现一个画图程序,尽可能多的实现Windows自带的画图功能,并扩展其功能。1.2. 功能需求1.2.1. 基本绘图功能:(必须全部实现)(1) 能够用鼠标操控方式,绘制直线、矩形、椭圆。(2) 在绘图时,选择绘制某种图像后(如直线),在画布中按住鼠
4、标左键后移动鼠标,在画布中实时的根据鼠标的移动显示相应的图形。在松开鼠标左键后,一次绘图操作完成。(3) 能够在绘制一图形(如一条直线)前设置线的粗细、颜色。(以菜单方式)(4) 可以以矢量图方式保存绘制的图形。(5) 可以读取保存的矢量图形文件,并显示绘图的结果。界面友好的要求:(6) 有画直线、矩形、椭圆的工具箱。(7) 有颜色选择工具箱。(8) 对于当前选中的绘图工具,以“下沉”的形式显示。(9) 在状态栏中显示鼠标的位置。(10) 在鼠标移向一工具不动时,有工具的功能提示。(11) 在菜单上有当前选中的菜单项标识(即前面有小钩)(12) 可以用鼠标操作方式,通过“拖拽”方式,改变画布的
5、大小。(13) 在画布大而外框小时,应有水平或垂直方向的滚动条。1.2.2. 高级编辑功能:(1) 具有Undo功能。(2) 可以用鼠标选中绘制的某一图形。被选中的图形符号有标识(参见Word,如一直线段,其两端点上加了两个小框;矩形上有8个小框点)。(3) 当鼠标靠近某一目标时,鼠标的形状发生改变(4) 修改被选中的图形。通过鼠标的“拖拽”,可以改变图形的位置、或大小。(5) 修改被选中图形的颜色、笔划的粗细。(6) 删除被选中的图形。(7) 可以使用鼠标“拖拽”一个虚矩形框,一次选择多个图形。(8) 可以使用 Ctrl 或Shift加鼠标左键选择多个图形对象。1.2.3. 附加功能:(1)
6、 可选择打开或关闭工具栏。(2) 应用程序的标题栏上有程序的图标。(3) 将图形转换成位图文件的形式保存。(4) 在选择一个图形元素后(如直线),会有进一步选择线型或线宽的界面。(5) 仿Word,选择“线型”、“粗细”图标后,会出现进一步选择的选项卡。1.3. 未实现的功能2. 主要功能描述右键修改选中图形的颜色,粗细,线型,删除选中图形右键和鼠标调整图形大小对话框矢量修改所有图形3. 技术细节3.1. 代码结构3.1.1. 代码文件MFC自动生成的文件1个CHDrawPView1个HStroke2个Dialog(HStrokeEditDlg+HStrokeTextDlg)1个ToolBar
7、(HColorBar)3.1.2. 代码类HDrawPView文件只有一个类:CHDrawPView,该类集成自MFC的CScrollView,主要实现维护画布类CHDrawView和滚动功能HStroke文件里包含目前所有的图形类信息,包括集成与MFC的CObject类的基类HStroke,以及集成自HStroke的具体图形类HStrokeLine(直线),HStrokeRect(矩形),HStrokeEllipse(椭圆),HStrokeText(文本),HStrokePoly(曲线)。HStrokeEditDlg文件只有一个类:HStrokeEditDlg,该类集成自MFC的CDialo
8、g类,主要用来编辑已有图形类,如下图所示:HStrokeTextDlg文件只有一个类:HStrokeTextDlg,该类集成自MFC的CDialog类,主要用来画文本时输入文本信息,如图所示:HColorBar类只有一个类:HColorBar类,该类集成自MFC的CToolBar类,呈现一个颜色框,方便用户在绘图时选择不同的颜色。3.2. SetROP2实现重绘在画图状态下,鼠标移动时既要擦除旧图形,又要绘制新图形。这里主要有两种实现方法:一是全部重绘,二是先擦除旧图形。如果使用矢量图全部重绘,频繁的绘图动作消耗很大,很容易造成屏幕闪动。但是如果将已有图形保存为位图,然后重绘的时候只要绘制位图
9、即可,这样能避免闪动。第二种方法要考虑的就是擦除旧图形的问题,本程序使用SetROP2函数设置MASK的方式,每次绘图时采用非异或运算的方式擦除旧图形: pDC-SetROP2(R2_NOTXORPEN); /设置ROP2 DrawStroke(pDC); /画图擦除旧线(自定义函数) SetCurrentPoint(point); /设置新点的坐标(自定义函数) DrawStroke(pDC); /画新线(自定义函数)3.3. 嵌套View实现画布 m_drawView = new CHDrawView();/创建画布View if (!m_drawView-CreateEx(WS_EX_L
10、EFT | 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-
11、1,m_tracker.m_rect.bottom-1, this-m_hWnd,NULL) TRACE0(Failed to create toolbarn); return -1; / fail to create m_drawView-SetDocument(CHDrawDoc*)m_pDocument);/传递CDocument给新View m_drawView-ShowWindow(SW_NORMAL); m_drawView-UpdateWindow(); /设置背景View颜色为灰色 SetClassLong(m_hWnd,GCL_HBRBACKGROUND,(long)GetS
12、tockObject(GRAY_BRUSH);3.4. 鼠标靠近目标时突出显示在鼠标移动的时候,OnMouseMove函数会遍历已有图形,判断鼠标所在点是否属于已有图形范围,如果是,则高亮显示该图形。高亮显示的方法比较简单,只要增加CRectTracker即可,而判断当前点是否属于某图形比较有意思:3.4.1. 判断一点是否属于矩形HStrokeRect使用用MFC的CRect类的IsPointIn方法,当鼠标在矩形边框附近时,认为该点属于HStrokeRect。如图,实线矩形表示HStrokeRect。外矩形为外面的虚线矩形,内矩形为里面的虚线矩形:BOOL HStrokeRect:IsPo
13、intIn(const CPoint &point) /矩形左上角x坐标 int x1 = m_points.GetAt(0).x m_points.GetAt(1).x ? m_points.GetAt(0).x : m_points.GetAt(1).x; /矩形左上角y坐标 int y1 = m_points.GetAt(0).y m_points.GetAt(1).x ? m_points.GetAt(0).x : m_points.GetAt(1).x; /矩形右下角y坐标 int y2 = m_points.GetAt(0).y m_points.GetAt(1).y ? m_po
14、ints.GetAt(0).y : m_points.GetAt(1).y; /构建外矩行和内矩形 CRect rect(x1,y1,x2,y2), rect2(x1+5,y1+5,x2-5,y2-5); /如果在外矩形内并在内矩形外 if(rect.PtInRect(point) & !rect2.PtInRect(point) return TRUE; else return FALSE;3.4.2. 判断一点是否属于线段首先判断一点是否属于这条线段所属的直线,根据直线的判定公式y1/x1 = y2/x2得到x1*y2-x2*y1=0,但是在画图中应该在直线附近就能选中,所以在本程序中:|
15、x1*y2-x2*y1| 偏差,然后判断该点是否属于这条线段。 /计算该点到线段HStrokeLine的两个顶点的线段(x1,y1), (x2,y2) int x1 = point.x - m_points.GetAt(0).x; int x2 = point.x - m_points.GetAt(1).x; int y1 = point.y - m_points.GetAt(0).y; int y2 = point.y - m_points.GetAt(1).y; /计算判断量x1*y2 - x2*y1 int measure = x1*y2 - x2*y1; /误差允许范围,也就是直线的“
16、附近” int rule = 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) /判断该点是否属于这条线段 if(x1 * x2 _2b)/横椭圆 x1 = (double)(m_points.GetAt(0).x + m_points.GetAt(1).x)/2 - c; x2 = x1 + 2*c; y1 = y2 = (m_points.GetA
17、t(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; double measure = sqrt(x1 - point.x)*(x1-point.x)
18、 + (y1 - point.y)*(y1-point.y) ) + sqrt( (point.x - x2)*(point.x - x2) + (point.y - y2)*(point.y - y2) - _2a; /计算椭圆的“附近” double rule = 4*m_penWidth; if(measure -rule) return TRUE; else return FALSE;3.5. 文档序列化MFC提供了良好的序列化机制,只要在类定义时加入DECLARE_SERIAL宏,在类构造函数的实现前加入IMPLEMENT_SERIAL宏,然后实现Serialize方法即可。本程序即
19、使用该方法序列化:首先在CHDrawDoc类实现Serialize方法,保存画布大小和所有图形信息:void CHDrawDoc:Serialize(CArchive& ar) if (ar.IsStoring() /保存时,首先保存画布高和宽,然后序列化所有图形 arm_cavasHm_cavasHm_cavasW; m_strokeList.Serialize(ar); m_strokeList.Serialize(ar);这一句很神奇,Debug追踪的时候会发现,容器类会自动序列化容器内的元素数量,并调用每个元素的序列化方法序列化,所以还需要对每个图形元素实现序列化,以HStrokeLi
20、ne为例:在HStrokeLine的类声明中:class HStrokeLine : public HStroke public: HStrokeLine(); DECLARE_SERIAL(HStrokeLine)然后在HStrokeLine的构造函数实现前:IMPLEMENT_SERIAL(HStrokeLine, CObject, 1)HStrokeLine:HStrokeLine() m_picType = PIC_line;最后实现HStrokeLine的序列化函数,因为这里HStrokeLine集成自HStroke类而且没有特殊的属性,而HStroke类实现了Serialize函数
21、,所以HStrokeLine类不需要实现Serilize方法,看一下HStroke的Serialize方法即可:void HStroke:Serialize(CArchive& ar) if(ar.IsStoring() int enumIndex = m_picType; arenumIndexm_penWidthenumIndexm_penWidthm_penColor; m_picType = (enum HPicType)enumIndex; m_points.Serialize(ar); 3.6. 打开保存导出文档序列化实现以后,程序的打开和保存功能就已经完成了。但是从序列化方法可以
22、看出,打开和保存的都是矢量图形,所以这里实现了一个导出为BMP图像的方法,导出: /保存文件对话框,选择导出路径 CFileDialog dlg(FALSE, bmp,hjz.bmp); if(dlg.DoModal() != IDOK) return ; CString filePath = dlg.GetPathName(); / CClientDC client(this);/用于本控件的,楼主可以不用此句 CDC cdc; CBitmap bitmap; RECT rect;CRect r; GetClientRect(&rect); int cx = rect.right - rec
23、t.left; int cy = 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(int i = 0; i m_strokeLi
24、st.GetSize(); i +) GetDocument()-m_strokeList.GetAt(i)-DrawStroke(&cdc); cdc.SelectObject(oldbitmap); :OpenClipboard(this-m_hWnd); :EmptyClipboard(); :SetClipboardData(CF_BITMAP, bitmap); :CloseClipboard(); HBITMAP hBitmap = (HBITMAP)bitmap; HDC hDC; int iBits; WORD wBitCount; DWORD dwPaletteSize=0,
25、 dwBmBitsSize=0, dwDIBSize=0, dwWritten=0; BITMAP Bitmap; BITMAPFILEHEADER bmfHdr; BITMAPINFOHEADER bi; LPBITMAPINFOHEADER lpbi; HANDLE fh, hDib, hPal,hOldPal=NULL; hDC = CreateDC(DISPLAY, NULL, NULL, NULL); iBits = GetDeviceCaps(hDC, BITSPIXEL) * GetDeviceCaps(hDC, PLANES); DeleteDC(hDC); if (iBits
26、 = 1) wBitCount = 1; else if (iBits = 4) wBitCount = 4; else if (iBits = 8) wBitCount = 8; else wBitCount = 24; GetObject(hBitmap, sizeof(Bitmap), (LPSTR)&Bitmap); bi.biSize = sizeof(BITMAPINFOHEADER); bi.biWidth = Bitmap.bmWidth; bi.biHeight = Bitmap.bmHeight; bi.biPlanes = 1; bi.biBitCount = wBitC
27、ount; 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);
28、 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
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1