导图社区 MFC实现office界面画图
这是一篇关于MFC实现office界面画图的思维导图,包括:一、创建项目;二、进入多个文档的控件页面;三、编写画图小程序(矩形);四、编写画图小程序;五、轮廓填充和内部填充;六、序列化保存和读取文件; 七、导出文件按钮;八总结。
编辑于2022-04-30 10:11:29MFC实现office界面画图
三、编写画图小程序(矩形)
属性窗口->修改Caption
点击面板
Ribbon工具箱->拖动按钮至图形面板
修改这个按钮的属性中杂项的ID,改为ID_RECTANGLE 这里指的注意的就是这个ID,这个在MFC编程中十分重要,因为我们往往需要获取控件的ID号来对该控件进行函数编写、消息处理等操作!!!
右键该按钮,添加事件处理程序
弹出以下框
类列表我们选择CDrawView,点击确定
在DrawView.cpp中生成命令函数
在DrawView.cpp前面的代码多了一条语句
这里的红波浪线提示没有这个ID,其实我们是添加了这个按钮的,可以在Resource.h中看到(注意打开Resource.h再去看资源试图是打不开的,会报错,因此需要先关掉Resource.h再去访问资源试图!!!!!)  #define ID_RECTANGLE 32771 这个IDE编辑器的小问题,我们可以选择重启vs2019打开就不会显示未定义ID了,如下,没有红色波浪线了 
继承
现在的问题就是如何画图的问题:画矩形,则定义一个矩形类,写方法,在消息函数里新建对象即可;但是我们如果不满足只画矩形呢?画箭头、三角可不可以?这个时候我们就应该想到继承,即建一个抽象类graph,然后派生几个子类去完成这个功能。因此我们应该先新建一个graph抽象类:
建立graph类
graph.h
我们右键头文件,选择添加-->新建项,弹出如下界面; 选择头文件(.h),名称为graph.h,点击确定
建立后如下
写graph.h代码
class graph : public CObject { protected: //边框 DECLARE_SERIAL(graph) int left, up, right, down; //选中状态 unsigned int state; int sx, sy; int f_width = 5; int fcolor = 0xffffff, bcolor = 0; public: graph() :graph(50, 50, 100, 100) { } graph(int l, int u, int r, int d); void Offset(int cx, int cy); void onPress(int x, int y); // 鼠标按下 int onMove(int cx, int cy); // 鼠标移动 void onRelease(int x, int y); // 鼠标释放 virtual void onDraw(CDC* pDC); virtual int getGraphID() { return 0; } virtual void Serialize(CArchive& ar); void SetFillColor(int color); void SetBorderColor(int color); ~graph(); };
报错
结果报了一堆错误,事实是因为我们新建的graph并没有和MFC本身的类关联起来
include抽象类
我们可以这样做:打开framework.h(vs2017里是stdafx.h),我们在这里include一下我们的graph抽象类 如下(注意自己写的头文件要使用引号“”) #include "graph.h"
错误解决
graph.cpp
我们右键头文件,选择添加-->新建项,弹出如下界面; 选择C++文件(.cpp),名称为graph.cpp,点击确定
建立后如下
写graph.cpp代码
#include "framework.h" IMPLEMENT_SERIAL(graph, CObject, 1) graph::graph(int l, int u, int r, int d) { left = l; up = u; right = r; down = d; state = 0; fcolor = 0xffffff; } void graph::Offset(int cx, int cy) { left += cx; right += cx; up += cy; down += cy; } void graph::onPress(int x, int y) { sx = x; sy = y; state = 0; //选中图形 if (left < x && x < right && up < y && y < down) { state = 1; return; } if (left - f_width / 2 < x && x < left + f_width / 2) state |= 2; // 选中左边 if (up - f_width / 2 < y && y < up + f_width / 2) state |= 4;//选中上边 if (right - f_width / 2 < x && x < right + f_width / 2) state |= 8;//选中右边 if (down - f_width / 2 < y && y < down + f_width / 2) state |= 16; // 选中下边 } void graph::onRelease(int x, int y) { state = 0; } void graph::SetBorderColor(int color) { fcolor = color; } void graph::SetFillColor(int color) { bcolor = color; } int graph::onMove(int x, int y) { int cx, cy; cx = x - sx; cy = y - sy; sx = x; sy = y; if (state == 1) { Offset(cx, cy); // 位移量cx,cy } if (2 == (state & 2)) { left = x; } if (4 == (state & 4)) { up = y; } if (8 == (state & 8)) { right = x; } if (16 == (state & 16)) { down = y; } return state == 0 ? 0 : 1; } void graph::Serialize(CArchive & ar) { CObject::Serialize(ar); if (ar.IsLoading()) { ar >> left >> right >> up >> down >> f_width >> fcolor >> bcolor; } else { ar << left << right << up << down << f_width << fcolor << bcolor; } } graph::~graph() { } void graph::onDraw(CDC * pDC) { CBrush b(fcolor); pDC->SelectObject(&b); CRect r(left, up, right, down); pDC->FillRect(&r, &b); CPen p(PS_SOLID, 1, bcolor); pDC->SelectObject(&p); pDC->Rectangle(left, up, right, down); pDC->MoveTo(left, up); pDC->DrawText(_T("空图形"), -1, new CRect(left, up, right, down), DT_CENTER | DT_VCENTER | DT_SINGLELINE); }
建立矩形rectangle类
rectangle.h
#pragma once #include "graph.h" class rectangle : public graph { public: //DECLARE_SERIAL(graph) //void Serialize(CArchive& ar); rectangle() :graph(50, 50, 100, 100) {} rectangle(int l, int u, int r, int d); void onDraw(CDC* pDC); int getGraphID() { return 2; } ~rectangle(); };
注意
注意framework.h不要忘了加上: #include "rectangle.h"
rectangle.cpp
#include "framework.h" rectangle::rectangle(int l, int u, int r, int d) :graph(l, u, r, d) { state = 0; fcolor = 0xffffff; } void rectangle::onDraw(CDC* pDC) { CBrush b(fcolor); pDC->SelectObject(&b); CRect r(left, up, right, down); pDC->FillRect(&r, &b); CPen p(PS_SOLID, 1, bcolor); pDC->SelectObject(&p); pDC->Rectangle(left, up, right, down); pDC->MoveTo(left, up); } rectangle::~rectangle() { }
list
由于我们需要可以添加多个图形,因此一个以graph对象构成的list是必不可少的。我们在DrawDoc.h文档头文件添加这个list,可以添加//操作下面 std::list<graph*> graphList;
报错
include一下
把list的头文件加到framework.h中 #include <list>
错误解决
再回去看DrawDoc.h,发现没有错误
回到DrawView.cpp
编写消息处理程序rectangle
void CDrawView::OnRectangle() { // TODO: 在此添加命令处理程序代码 CDrawDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return; pDoc->graphList.push_front(new rectangle(50, 50, 100, 100)); Invalidate(); }
修改绘图代码部分
添加如下代码
(CDC* 后面的pDC取消注释) // CDrawView 绘图 void CDrawView::OnDraw(CDC* pDC) { CDrawDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return; // TODO: 在此处为本机数据添加绘制代码 std::list<graph*>::iterator v; for (v = pDoc->graphList.begin(); v != pDoc->graphList.end(); ++v) { (*v)->onDraw(pDC); } }
报错
错误解决
运行程序,发现报错,这是预编译头的问题 21.点击菜单栏的项目-->属性,选择C/C++-->预编译头,如图  改为不使用预编译头,点击确定 
运行
消息响应函数
右键源文件,点击类向导
鼠标按下
鼠标左键按下
我们添加一个鼠标左键按下的消息响应:选择消息栏,选择WM_LBUTTONUP,类名选择CDrawView,点击添加处理程序
添加处理程序
添加代码
void CDrawView::OnLButtonDown(UINT nFlags, CPoint point) { // TODO: 在此添加消息处理程序代码和/或调用默认值 CDrawDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return; // TODO: 在此处为本机数据添加绘制代码 std::list<graph*>::iterator v; for (v = pDoc->graphList.begin(); v != pDoc->graphList.end(); ++v) { (*v)->onPress(point.x, point.y); } Invalidate(); //CView::OnLButtonDown(nFlags, point); }
鼠标抬起
鼠标左键抬起
填写代码
void CDrawView::OnLButtonUp(UINT nFlags, CPoint point) { // TODO: 在此添加消息处理程序代码和/或调用默认值 CDrawDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return; // TODO: 在此处为本机数据添加绘制代码 std::list<graph*>::iterator v; for (v = pDoc->graphList.begin(); v != pDoc->graphList.end(); ++v) { (*v)->onRelease(point.x, point.y); } Invalidate(); //CView::OnLButtonUp(nFlags, point); }
鼠标移动
添加代码
void CDrawView::OnMouseMove(UINT nFlags, CPoint point) { // TODO: 在此添加消息处理程序代码和/或调用默认值 CDrawDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return; // TODO: 在此处为本机数据添加绘制代码 std::list<graph*>::iterator v; for (v = pDoc->graphList.begin(); v != pDoc->graphList.end(); ++v) { (*v)->onMove(point.x, point.y); } Invalidate(); //CView::OnMouseMove(nFlags, point); }
四、编写画图小程序
三角形
属性->杂项ID->ID_TRIANGLE

箭头
属性->杂项ID->ID_ARROW

直线
属性->杂项ID->ID_LINE

添加的类和方法
*framework.h #include <afxwin.h> // MFC 核心组件和标准组件 #include <afxext.h> // MFC 扩展 #include "graph.h" #include "rectangle.h" #include "triangle.h" #include "arrow.h" #include "line.h" #include <list> 
三角形
triangle.h
#pragma once #include "graph.h" class triangle : public graph { protected: public: triangle(int l, int u, int r, int d); int getGraphID() { return 3; } void onDraw(CDC* pDC); ~triangle(); };
triangle.cpp
#include "framework.h" triangle::triangle(int l, int u, int r, int d) :graph(l, u, r, d) { state = 0; fcolor = 0xffffff; } void triangle::onDraw(CDC* pDC) { CPoint pts[3]; pts[0].x = (left + right) / 2; pts[0].y = up; pts[1].x = left; pts[1].y = down; pts[2].x = right; pts[2].y = down; CBrush b(fcolor); pDC->SelectObject(&b); CPen p(PS_SOLID, 1, bcolor); pDC->SelectObject(&p); pDC->Polygon(pts, 3); } triangle::~triangle() { }
箭头
arrow.h
#pragma once #include "graph.h" class arrow : public graph { public: arrow(int l, int u, int r, int d); void onDraw(CDC* pDC); int getGraphID() { return 4; } ~arrow(); };
arrow.cpp
#include "framework.h" arrow::arrow(int l, int u, int r, int d) :graph(l, u, r, d) { } void arrow::onDraw(CDC* pDC) { CPoint pts[2], pt[3]; pts[0].x = left; pts[0].y = (up + down) / 2; pts[1].x = (left + right) / 2; pts[1].y = (up + down) / 2; pt[0].x = (left + right) / 2; pt[0].y = up; pt[1].x = (left + right) / 2; pt[1].y = down; pt[2].x = right; pt[2].y = (up + down) / 2; CBrush b(fcolor); pDC->SelectObject(&b); CPen p(PS_SOLID, 1, bcolor); pDC->SelectObject(&p); pDC->Polygon(pts, 2); pDC->Polygon(pt, 3); } arrow::~arrow() { }
直线
line.h
#pragma once #include "graph.h" class line : public graph { public: line() :line(50, 50, 100, 100) {} line(int l, int u, int r, int d); void onDraw(CDC* pDC); int getGraphID() { return 1; } ~line(); };
line.cpp
#include "framework.h" line::line(int l, int u, int r, int d) :graph(l, u, r, d) { state = 0; fcolor = 0xffffff; } void line::onDraw(CDC* pDC) { CPoint pts[2]; pts[0].x = left; pts[0].y = (up + down) / 2; pts[1].x = right; pts[1].y = (up + down) / 2; CBrush b(fcolor); pDC->SelectObject(&b); CPen p(PS_SOLID, 1, bcolor); pDC->SelectObject(&p); pDC->Polygon(pts, 2); } line::~line() { }
事件处理程序
三角形
void CDrawView::OnTriangle() { // TODO: 在此添加命令处理程序代码 CDrawDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return; pDoc->graphList.push_front(new triangle(50, 50, 100, 100)); Invalidate(); }
箭头
void CDrawView::OnArrow() { // TODO: 在此添加命令处理程序代码 CDrawDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return; pDoc->graphList.push_front(new arrow(50, 50, 100, 100)); Invalidate(); }
直线
void CDrawView::OnLine() { // TODO: 在此添加命令处理程序代码 CDrawDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return; pDoc->graphList.push_front(new line(50, 50, 100, 100)); Invalidate(); }
五、轮廓填充和内部填充
轮廓
属性->杂项ID->ID_FILLCOLOR_LINE

内部
属性->杂项ID->ID_FILLCOLOR_IN

事件处理程序
include一下
需要在DrawView.cpp中添加MainFrm.h头文件 #include "MainFrm.h" 
protected->public
还需要在MainFrm.h中将m_wndRibbonBar从protected变为public,这样才能访问到这个变量,否则报错! protected: // 控件条嵌入成员 //CMFCRibbonBar m_wndRibbonBar; CMFCRibbonApplicationButton m_MainButton; CMFCToolBarImages m_PanelImages; CMFCRibbonStatusBar m_wndStatusBar; COutlookBar m_wndNavigationBar; CMFCShellTreeCtrl m_wndTree; CCalendarBar m_wndCalendar; CMFCCaptionBar m_wndCaptionBar; public: CMFCRibbonBar m_wndRibbonBar; 
轮廓
void CDrawView::OnFillcolorLine() { // TODO: 在此添加命令处理程序代码 CDrawDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return; CMainFrame* pFrame = (CMainFrame*)AfxGetMainWnd(); CMFCRibbonColorButton* a = (CMFCRibbonColorButton*)((pFrame->m_wndRibbonBar).FindByID(ID_FILLCOLOR_LINE)); COLORREF c = a->GetColor(); std::list<graph*>::iterator v; for (v = pDoc->graphList.begin(); v != pDoc->graphList.end(); ++v) { (*v)->SetFillColor(c); } Invalidate(); }
内部
void CDrawView::OnFillcolorIn() { // TODO: 在此添加命令处理程序代码 CDrawDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return; CMainFrame* pFrame = (CMainFrame*)AfxGetMainWnd(); CMFCRibbonColorButton* a = (CMFCRibbonColorButton*)((pFrame->m_wndRibbonBar).FindByID(ID_FILLCOLOR_IN)); COLORREF c = a->GetColor(); std::list<graph*>::iterator v; for (v = pDoc->graphList.begin(); v != pDoc->graphList.end(); ++v) { (*v)->SetBorderColor(c); } Invalidate(); }
六、序列化保存和读取文件
DrawDoc.cpp
修改DrawDoc.cpp的序列化部分,这是用于菜单栏里的保存和打开图片所使用的 
修改代码
// CDrawDoc 序列化 void CDrawDoc::Serialize(CArchive& ar) { if (ar.IsStoring()) { // TODO: 在此添加存储代码 ar << graphList.size(); for (auto it = graphList.begin(); it != graphList.end(); it++) { ar << (*it)->getGraphID(); (*it)->Serialize(ar); } } else { // TODO: 在此添加加载代码 for (auto it = graphList.begin(); it != graphList.end(); it++) { delete* it; } graphList.clear(); int i, gid; ar >> i; graph* g; while (i--) { ar >> gid; switch (gid) { case 1: g = new line(); break; case 2: g = new rectangle(); break; case 3: g = new triangle(0, 0, 0, 0); break; case 4: g = new arrow(0, 0, 0, 0); break; default: g = new graph(); break; } g->Serialize(ar); graphList.push_back(g); } } }
七、导出文件按钮
新建面板->按钮
再新建一个面板,拖入一个按钮,名字到导出图片,ID为ID_SAVE 
事件处理程序
void CDrawView::OnSave() { // TODO: 在此添加命令处理程序代码 CClientDC dc(this); CRect rect; CString saveFilePath; BOOL showMsgTag; BOOL saveTag = FALSE; GetClientRect(&rect); HBITMAP hbitmap = CreateCompatibleBitmap(dc, rect.right - rect.left, rect.bottom - rect.top); HDC hdc = CreateCompatibleDC(dc); HBITMAP hOldMap = (HBITMAP)SelectObject(hdc, hbitmap); BitBlt(hdc, 0, 0, rect.right - rect.left, rect.bottom - rect.top, dc, 0, 0, SRCCOPY); CImage image; image.Attach(hbitmap); if (!saveTag) { saveTag = TRUE; showMsgTag = TRUE; CString strFilter = _T("位图文件(*.bmp)|*.bmp|JPEG 图像文件|*.jpg| GIF图像文件 | *.gif | PNG图像文件 | *.png |其他格式(*.*) | *.* || "); CFileDialog dlg(FALSE, _T("bmp"), _T("iPaint1.bmp"), NULL, strFilter); if (dlg.DoModal() != IDOK) return; CString strFileName; CString strExtension; strFileName = dlg.m_ofn.lpstrFile; if (dlg.m_ofn.nFileExtension == 0) { switch (dlg.m_ofn.nFilterIndex) { case 1: strExtension = "bmp"; break; case 2: strExtension = "jpg"; break; case 3: strExtension = "gif"; break; case 4: strExtension = "png"; break; } strFileName = strFileName + "." + strExtension; } saveFilePath = strFileName; } else { showMsgTag = FALSE; } HRESULT hResult = image.Save(saveFilePath); if (FAILED(hResult)) { MessageBox(_T("保存图像文件失败!")); } else { if (showMsgTag) MessageBox(_T("文件保存成功!")); } image.Detach(); SelectObject(hdc, hOldMap); }
运行程序,导入Draw1

导出图片

文件保存成功

打开该文件
双击打开这个文件iPaint1.bmp  6.可以看到成功打开(注意:这个文件不能用刚才自己写的MFC程序自带的打开来打开这个文件,因为打开的文件不是被序列化过的,因此打开会失败!) 因此不能用这里的打开按钮哟
八总结
1.MFC的按钮消息需要熟练掌握 2.鼠标消息的使用也很重要 3.序列化保存和普通的导出图片不是一码事 4.对于抽象类的使用,尤其是画各种图是很重要的 5.MFC内置的库函数需要熟练掌握(画笔、刷子等等)
二、进入多个文档的控件页面
双击 项目名称.rc2,进入资源视图
当然,我们也可以直接点击系统默认打开的底下的资源视图选项(注意:不能resource.h文件同时打开!!!
Draw->Draw.rc->Menu
这里比较重要的是Menu部分,因为有很多MFC已经内置好的功能,例如主框架IDR_MAINFRAME里就有文件下拉菜单等选项,我们想要添加一个新的下拉菜单,只需要在右边的“请在此键入”输入内容,并且编写对应的代码即可。但这里博主并不在这里添加画图的功能。
Draw->Draw.rc->Ribbon
点击右边的工具箱
点击Ribbon编辑器
一、创建项目
1.点击文件-->新建-->项目,选择MFC应用
2.项目名称为Draw,点击创建
3、进入MFC应用程序
应用程序类型
应用程序类型
单个文档
多个文档(选择)
基于对话框
多个顶层文档
子主题
项目样式
MFC standard
Windows Explorer
Visual Studio
Office(选择)
值得注意的是:Office会比默认选择的项目样式多一个Ribbon框
视觉样式和颜色
(默认)Office 2007(Blue Theme)
... ...
文档模板属性
主框架描述(默认)
文档类型名称(默认)
用户界面功能
可以看到Command bar里有三个选项,这里我们选择默认的使用功能区(ribbon)
高级功能
生成的类
来到最后一步——生成的类,可以看到生成的类和类名,我们选择默认的App即可,这样我们的头文件和.cpp文件就是以项目名称命名
默认即可
(上述完成,进入页面,看到左侧的)解决方案资源管理器
引用
外部依赖项
头文件
源文件
资源文件