本文共 6413 字,大约阅读时间需要 21 分钟。
MFC之所以为Application Framework,最重要的一个特征就是它能够将管理数据的程序代码和负责显示数据的程序代码分离开来,而这种能力有Document/View提供。
想要实现数据管理和显示的分离,需要搞清楚一些几个问题: 1. 程序的哪个部分持有数据 2. 程序的哪个部分负责更新数据 3. 如何以多种方式显示数据 4. 如何让数据的更改有一致性 5. 如何实现数据持久化 6. 如何管理使用者接口。不同的数据类型可能需要不同的使用者接口,而一个程序可能管理多种类型的数据。 其实Document/View结构与当下很流行的MVC架构有异曲同工之妙。其间的对应关系可以这样认为:M-Document, V-View,C-Document Template。Document
Document其实就是指数据。Document在MFC的CDocument中被实例化。CDocument本身并无实际用途,它只提供一个框架。当我们开发程序时,应该从CDocument派生出一个属于自己的Document类,并且在类中声明一些成员变量,用以容纳数据。然后再(至少)改写专门负责档案读写操作的Serialize函数。当然,APPWizard会为我们搭好框架,我们只要往里面填就行了。例如: 类声明中:virtual void Serialize(CArchive& ar); 类定义中: void CMyDoc::Serialize(CArchive& ar){ if (ar.IsStoring()) { // TODO: add storing code here } else { // TODO: add loading code here }} View View负责呈现Document中的数据。View在MFC的CView中被实例化。当我们开发自己的程序时,应该从CView派生出一个属于自己的View类,并且在类中(至少)改写专门负责显示数据的OnDraw函数(针对屏幕)或Onprint函数(针对打印机)。同样,APPWizard也会为我们搭好框架,如下: 类声明中:virtual void OnDraw(CDC* pDC); // overridden to draw this view 类实现中: void CMFCView::OnDraw(CDC* pDC){ CMFCDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); // TODO: add draw code for native data here} 由于CView派生子CWnd,所以它可以接收一般的Windows消息(如WM_SIZE、WM_PAINT等),又派生自CCmdTarget,所以它可以接收自菜单或工具栏的WM_COMMAND消息。 在传统的C/SDK程序中,当窗口函数收到WM_PAINT时,我们开始获得DC并绘制画面。而在MFC中,一旦WM_PAINT发生,Framework就会自动调用OnDraw函数。 View其实是一个没有边框的窗口。真正出现时,其外围还有一个有边框的窗口,我们称之为Frame窗口。 Frame Frame负责窗口显示时的UI管理。之所以不然View插手这件事而由Frame全权处理,涉及到框架设计层面的考虑。有时候功能之间需要一定的关联度,但是这种关系又不能太强。把UI管理隔离出来,可以降低彼此之间的依赖程度,也可以是该功能重复适用于各种场合。 Document Template MFC把Document/View/Frame视为三位一体,这个整体就由DocumentTemplate来掌管。 如果程序中能够处理多种数据类型,就必须制造多个Document Template出来,并使用AddDocTemplate函数将它们一一加入系统之中。这和程序是不是MDI(Multiple Document Interface,就是所谓的多文档界面)无关。如果程序支持多种数据类型,但却是个SDI(Single Document Interface,就是所谓的单文档界面),那只不过表示我们每次只能开启一份文件罢了。 CDocTemplate管理CDocument/CView/CFrameWnd 由Document Template来管理Document/View/Frame,那么谁又是Document Template的管理者呢?其实是CWinApp,可以看其InitInstance函数中的操作: { CMultiDocTemplate* pDocTemplate; pDocTemplate = new CMultiDocTemplate( IDR_MFCTYPE, RUNTIME_CLASS(CMFCDoc), RUNTIME_CLASS(CChildFrame), // custom MDI child frame RUNTIME_CLASS(CMFCView)); AddDocTemplate(pDocTemplate);} 联系文件的新建和打开操作,如下图(摘自:《深入浅出MFC》):当使用者单击【文件/新建】命令项时,根据APPWizard为我们所做的Message Map( ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew) ),此命令由CWinApp::OnFileNew函数接手处理。后者调用CDocManager::OnFileNew,后者再调用CWinApp::OpenDocumentFile,后者再调用CDocManager:: OpenDocumentFile,后者再调用CMultiDocTemplate::OpenDocumentFile。最后调用的函数主要操作如下(定义于DOCMULTI.CPP):
CDocument*CMultiDocTemplate::OpenDocumentFile(LPCTSTR lpszPathName, BOOLbMakeVisible) { CDocument*pDocument = CreateNewDocument(); if(pDocument == NULL) { TRACE0("CDocTemplate::CreateNewDocumentreturned NULL.\n"); AfxMessageBox(AFX_IDP_FAILED_TO_CREATE_DOC); returnNULL; } ASSERT_VALID(pDocument); BOOLbAutoDelete = pDocument->m_bAutoDelete; pDocument->m_bAutoDelete= FALSE; // don't destroy if somethinggoes wrong CFrameWnd*pFrame = CreateNewFrame(pDocument, NULL); pDocument->m_bAutoDelete= bAutoDelete; if(pFrame == NULL) { AfxMessageBox(AFX_IDP_FAILED_TO_CREATE_DOC); deletepDocument; // explicit delete onerror returnNULL; } ASSERT_VALID(pFrame); if(lpszPathName == NULL) { //create a new document - with default document name …… } else { //open an existing document …… } InitialUpdateFrame(pFrame,pDocument, bMakeVisible); returnpDocument; } 其中CreateNewDocument函数(定义于DOCTEMPL.CPP)的操作如下: CDocument* CDocTemplate::CreateNewDocument(){ // default implementation constructs one from CRuntimeClass if (m_pDocClass == NULL) { TRACE0("Error: you must override CDocTemplate::CreateNewDocument.\n"); ASSERT(FALSE); return NULL; } CDocument* pDocument = (CDocument*)m_pDocClass-> CreateObject();//动态创建Document对象 if (pDocument == NULL) { TRACE1("Warning: Dynamic create of document type %hs failed.\n", m_pDocClass->m_lpszClassName); return NULL; } ASSERT_KINDOF(CDocument, pDocument); AddDocument(pDocument); return pDocument;} 正如所料,其中动态产生了Document对象。 CreateNewFrame函数的主要操作如下: CFrameWnd*CDocTemplate::CreateNewFrame(CDocument* pDoc, CFrameWnd* pOther) { if(pDoc != NULL) ASSERT_VALID(pDoc); //create a frame wired to the specified document ASSERT(m_nIDResource!= 0); // must have a resource ID to load from CCreateContextcontext; context.m_pCurrentFrame= pOther; context.m_pCurrentDoc= pDoc; context.m_pNewViewClass= m_pViewClass; context.m_pNewDocTemplate= this; …… CFrameWnd*pFrame = (CFrameWnd*)m_pFrameClass->CreateObject();//动态创建Document Frame对象 …… //create new from resource if(!pFrame->LoadFrame(m_nIDResource, WS_OVERLAPPEDWINDOW| FWS_ADDTOTITLE, // default framestyles NULL, &context)) { TRACE0("Warning:CDocTemplate couldn't create a frame.\n"); //frame will be deleted in PostNcDestroy cleanup returnNULL; } //it worked ! returnpFrame; } 显然上面动态创建了所需的Frame,但是View却是踪迹全无。真实如此吗?实则不然。上代码出现了一个context变量,该变量有一个字段指向m_pViewClass。回想CFrameWnd::Create操作的最后一个参数,它正是context。当CFrameWnd收到WM_CREATE时会触发CFrameWnd::OnCreate(由Message Map指定),其调用次序如下: CFrameWnd::OnCreate-> CFrameWnd::OnCreateHelper -> CFrameWnd::OnCreateClient -> CFrameWnd::CreateView 其中最后调用的函数(定义于WINFRM.CPP)主要操作如下: CWnd* CFrameWnd::CreateView(CCreateContext*pContext, UINT nID) { …… CWnd*pView = (CWnd*)pContext->m_pNewViewClass->CreateObject();//动态生成View对象 …… //views are always created with a border! if(!pView->Create(NULL, NULL,AFX_WS_DEFAULT_VIEW, CRect(0,0,0,0),this, nID, pContext)) { TRACE0("Warning:could not create view for frame.\n"); returnNULL; // can't continue without aview } if(afxData.bWin4 && (pView->GetExStyle() & WS_EX_CLIENTEDGE)) { //remove the 3d style from the frame, since the view is // providing it. //make sure to recalc the non-client area ModifyStyleEx(WS_EX_CLIENTEDGE,0, SWP_FRAMECHANGED); } returnpView; } 由上可见,不仅View对象被动态生成,其对应的实际窗口也以Create函数产生出来。 CDocTemplate、CDocument、CView、CFrameWnd关联关系 1. CWinApp拥有一个对象指针:CDocManager * m_pDocManager 2. CDocManager拥有一个指针链表CPtrList m_templateList,用来维护一系列的DocumentTemplate。应用程序在CMyWinApp::InitInstance中以AddDocTemplate将这些Document Templates加入到有CDocManager所维护的链表之中。 3. CDocTemplate拥有三个成员变量,分别持有Document、View、Frame的CRuntimeClass指针,另有一个成员变量m_nIDResource,用来表示此Document显示时应该采用的UI对象。这四份数据在CMyWinApp::InitInstance函数构造CDocTemplate时指定,称为构造函数的参数。 4. CDocument有一个成员变量CDocTemplate * m_pDocTemplate,回指其DocumentTemplate;另外有一个成员变量CPtrList m_viewList,表示它可以同时维护一组Views。 5. CFrameWnd有一个成员变量CView * m_pViewActive,指向当前活动的View。 6. CView有一个成员变量CDocument * m_pDocument,指向相关的Document。 关系图示如下(该图作者水平很高,无法超越,只有借花献佛了): 原文:https://blog.csdn.net/wzxq123/article/details/51981507