Document/View结构
首先总结下MFC的Document/View结构相关的知识点:
(1)在MFC中,文档类负责管理数据,提供保存和加载数据的功能;视类负责数据的显示,以及提供给用户对数据编辑和修改功能。
(2)有关文件的读写操作在CDocument的Serialize函数中进行,有关数据和图形显示的操作在CView的OnDraw函数中进行,相关的函数都是虚函数,我们只需要在派生类中重写这些函数即可。
(3)在单击“文件/打开”菜单命令后,应用程序框架会激活“文件打开”对话框,让用户指定将要打开的文件名。然后程序自动调用文档类的Serialize函数,完成数据的加载,同时还会调用视类的OnDraw函数,提供窗口重绘的机会。
(4)在应用程序启动会创建文档类对象,同时还创建了框架类对象和视类对象。这是MFC Doc/View的特定,即每当有一个文档产生,总是会产生一个文档类对象、框架类对象、视类对象,它们三位一个为这份文档服务。
(5)对于一个文档类对象来说,可以有多个视类对象与之相关;对一个视类对象来说,只有一个文档类对象相关。
(6)若需要保存类对象数据,我们需要设计可串行化的类,数据的保存和加载实际上都是在可串行化的类对象中进行。
(7)可串行化的类均是派生于CObject类,在该类中有Serialize函数,我们可利用该函数做为串行化的入口。
(8)在可串行化的类中,我们“IMPLEMENT_SERIAL宏”会帮助我们在堆上申请对象,因此我们在提取类对象数据时,我们不用分配类对象空间,我们传入该类的指针即可。
(9)在堆上申请的内存,需要我们手动去释放,文档类提供了DeleteContents虚函数,用于文档对象数据的销毁。在新建文件和打开文件都会调用这个DeleteContents函数。
(10)MFC为我们提供的文档/视类/框架结构中,已经为我们设计好了CDoucment类,CView类,CMainFrame类这三个类的相互调用接口。
实例
根据以上特点,我们将绘图三要素作为数据,保存在文档类中,在视类中完成图形的显示,这个功能基于“图形重绘”这篇文件进行修改的,部分关键代码如下:
Graph.h 中是图形类的数据和操作方法
//(1)派生于CObject类 class CGraph:public CObject { public: //(3)声明中使用DECLARE_SERIAL宏 DECLARE_SERIAL(CGraph) //(4)定义无参的构造函数 CGraph(void); CGraph(CPoint ptBegin, CPoint ptEnd, int DrawType); ~CGraph(void); //(2)重写Serialize成员函数 virtual void Serialize(CArchive& ar); public: enum { EN_RECT = 0, EN_ELLIPSE, EN_LINE, }; void SetDrawType(int nType); void SetBeginPoint(CPoint ptBegin); void SetEndPont(CPoint ptEnd); CPoint GetBeginPoint(); CPoint GetEndPont(); int GetDrawType(); //new CGraph对象 CGraph* CreateGraphObj(); //完成画图功能 void DrawItem(CDC *pDC); private: CPoint m_ptBegin; CPoint m_ptEnd; int m_DrawType; };//Graph.cpp是实现部分
#include "StdAfx.h" #include "Graph.h" //(5)实现文件中使用“IMPLEMENT_SERIAL”宏 IMPLEMENT_SERIAL(CGraph, CObject, 1) CGraph::CGraph(void) { m_DrawType = 0; } CGraph::CGraph(CPoint ptBegin, CPoint ptEnd, int DrawType) { this->m_ptBegin = ptBegin; this->m_ptEnd = ptEnd; this->m_DrawType = DrawType; } CGraph::~CGraph(void) { } //Call your base class version of Serialize to make sure that the inherited portion of the object is serialized. //Insert or extract the member variables specific to your class. void CGraph::Serialize(CArchive& ar) { // call base class function first // base class is CObject in this case CObject::Serialize(ar); // now do the stuff for our specific class if (ar.IsStoring()) { ar << m_DrawType << m_ptBegin << m_ptEnd; } else { ar >> m_DrawType >> m_ptBegin >> m_ptEnd; } } void CGraph::SetDrawType(int nType) { m_DrawType = nType; } void CGraph::SetBeginPoint(CPoint ptBegin) { m_ptBegin = ptBegin; } void CGraph::SetEndPont(CPoint ptEnd) { m_ptEnd = ptEnd; } CPoint CGraph::GetBeginPoint() { return m_ptBegin; } CPoint CGraph::GetEndPont() { return m_ptEnd; } int CGraph::GetDrawType() { return m_DrawType; } CGraph* CGraph::CreateGraphObj() { CGraph *pObj = NULL; try { pObj = new CGraph(m_ptBegin, m_ptEnd, m_DrawType); } catch (...) { return NULL; } return pObj; } //图形绘制显示功能 void CGraph::DrawItem(CDC *pDC) { //开始绘图操作流程 CBrush* pBrush = CBrush::FromHandle((HBRUSH)GetStockObject(NULL_BRUSH)); CBrush* pOldBrush = pDC->SelectObject(pBrush); //画图选择 switch (m_DrawType) { case CGraph::EN_RECT : { pDC->Rectangle(CRect(m_ptBegin, m_ptEnd)); break; } case CGraph::EN_LINE : { pDC->MoveTo(m_ptBegin); pDC->LineTo(m_ptEnd); break; } case CGraph::EN_ELLIPSE : { pDC->Ellipse(CRect(m_ptBegin, m_ptEnd)); break; } default: break; } pDC->SelectObject(pOldBrush); }
//CGraphicview.cpp 主要是图形数据保存和显示 m_obArray是CObject类型,是文档类的成员数据
/**************************************************************** *函数名称: *功 能:画图显示到屏幕 *作 者:Jin *日 期:2017年2月12日 ****************************************************************/ void CGraphicView::OnDraw(CDC* pDC) { CGraphicDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return; //窗口变化或者窗口生成时开始绘制历史图形 int nCount = pDoc->m_obArray.GetCount(); for (int i = 0; i < nCount; i++) { ((CGraph*)pDoc->m_obArray.GetAt(i))->DrawItem(pDC); } } /**************************************************************** *函数名称: 主要完成数据保存和显示 *功 能: *作 者:Jin *日 期:2017年2月12日 ****************************************************************/ void CGraphicView::OnLButtonUp(UINT nFlags, CPoint point) { // TODO: 在此添加消息处理程序代码和/或调用默认值 m_GrahpInfo.SetEndPont(point); //显示本次图形,但是存在延迟效果不佳 //CClientDC dc(this); //m_GrahpInfo.DrawItem(&dc); //记录本次画图数据 CGraph *pNewGraph = m_GrahpInfo.CreateGraphObj(); if (pNewGraph != NULL) { CGraphicDoc* pDoc = GetDocument(); if (pDoc!= NULL) { pDoc->m_obArray.Add(pNewGraph); } } //引发客户窗口无效,发生重绘, //图形能立即显示,但大量数据情况下频繁重绘效率下 Invalidate(TRUE); CView::OnLButtonUp(nFlags, point); }
GraphicDoc.cpp主要是完成文档数据的销毁、数据加载和保存功能
//重新新建或者打开文档对象之前需要数据销毁,防止堆溢出 void CGraphicDoc::DeleteContents() { int nCount = m_obArray.GetCount(); for (int i = 0; i < nCount; i++) { if (NULL != m_obArray.GetAt(i)) { //删除指针对象的所指的内存 delete m_obArray.GetAt(i); m_obArray.SetAt(i, NULL); } } //清空数据 m_obArray.RemoveAll(); } // CGraphicDoc 序列化 void CGraphicDoc::Serialize(CArchive& ar) { CGraphicView *pView = NULL; CGraph *pGraph = NULL; POSITION pos = GetFirstViewPosition(); if (pos != NULL) { pView = (CGraphicView*)GetNextView(pos); } if (ar.IsStoring()) { } else { } //使用这种方式更加简便 m_obArray.Serialize(ar); }
我们绘制一些图案后,然后点击保存,会有“文件保存”提示框,我们命名为GraphData.txt,关闭应用程序后;使用打击工具栏的“打开”后,会有“文件打开对话”,我们选中GraphData.txt,界面上就会显示我们之前绘制的图案了,如下所示:
以上我们是使用MFC的文档/视类结构以及串行化完成图形重绘和保存功能。