`

文档/视图结构中的各个部分是如何联系到一起的

    博客分类:
  • MFC
阅读更多

文档/视图结构是MFC中最有特色而又有难度的部分,在这当中涉及了应用、文档模板、文档、视图、MDI框架窗口、MDI子窗口等不同的对象,如果不了解这些部分之间如何关联的话,就可能犯错误,也就很难编出有水平的文档/视图程序。比如我在初学VC编程的时候,为应用程序添加了两个文档模板,两个模板公用一个文档类,只是视图不一样,期望当一个模板的文档的视图改变了文档后,调用UpdateAllViews后也能更新另一个文档模板的视图,结果当然是不行的,原因就是对MFC的文档/视图结构没有深入的了解,了解的最好方法就是阅读一下MFC的源代码。下面就是我的笔记:

(一)应用程序对象与文档模板之间的联系:

        首先,在应用程序对象中有一个CDocManager指针类型的共有数据成员m_pDocManager,CDocManager中维护一个CPtrList类型的链表:m_tempateList,它是一个保护成员。InitInstance函数中调用CWinApp::AddDocTemplate函数,实际上是调用m_pDocManagerAddDocTemplate函数向链表m_templateList添加模板指针。CWinApp提供了GetFirstDocTemplatePositionGetNextDocTemplate函数实现对m_templateList链表的访问(实际上是调用了CDocManager的相关函数)。

         在文件操作方面CWinApp提供的最常用的功能是文件的新建(OnFileNew)和打开(OnFileOpen),它也是调用CDocManager类的同名函数。对于新建,一般的时候在只有一个文档模板的时候,它新建一个空白的文件;如果有多个文档模板的时候,它会出现一个对话框提示选择文档类型。它的源代码如下:

void CDocManager::OnFileNew()

{

       if (m_templateList.IsEmpty())

       {

                                .......

              return;

       }

                //取第一个文档模板的指针

       CDocTemplate* pTemplate = (CDocTemplate*)m_templateList.GetHead();

       if (m_templateList.GetCount() > 1)

       {

              // 如果多于一个文档模板,出现对话框提示用户去选择

              CNewTypeDlg dlg(&m_templateList);

              int nID = dlg.DoModal();

              if (nID == IDOK)

                     pTemplate = dlg.m_pSelectedTemplate;

              else

                     return;     // none - cancel operation

       }

                ......

                //参数为NULL的时候OpenDocument File会新建一个文件

       pTemplate->OpenDocumentFile(NULL);

}

打开文件:

void CDocManager::OnFileOpen()

{

       // 出现打开文件对话框

       CString newName;

       if (!DoPromptFileName(newName, AFX_IDS_OPENFILE,

         OFN_HIDEREADONLY | OFN_FILEMUSTEXIST, TRUE, NULL))

              return; // open cancelled

 

       AfxGetApp()->OpenDocumentFile(newName);          //实际也是调用文档模板的同名函数

}

(二)文档模板与文档之间的联系:

        从上面看出应用程序对象对文件的新建和打开是依靠文档模板的OpenDocumentFile函数实现的。MFC的模板类是用来联系文档类、视类和框架类的,在它的构造函数就需要这三者的信息:

CDocTemplate ( UINT nIDResource, CRuntimeClass* pDocClass, CRuntimeClass* pFrameClass, CRuntimeClass* pViewClass );

构造函数利用后三个参数为它的三个CruntimeClass*类型的保护成员赋值:

       m_pDocClass = pDocClass;

       m_pFrameClass = pFrameClass;

       m_pViewClass = pViewClass;

    文档模板分为单文档模板和多文档模板两种,这两个模板的实现是不同的,除了上面的三个成员,内部有彼此不相同的但是很重要的成员变量。对于多文档模板:CPtrList m_docList;,单文档模板:CDocument* m_pOnlyDoc;。它们都有一个成员函数AddDocument,分别各自的成员进行赋值操作,而在它们的父类的CDocTemplate中则是为它所添加的文档的m_pDocTemplate变量赋值为模板自己的地址:

void CDocTemplate::AddDocument(CDocument* pDoc)

{

       ASSERT_VALID(pDoc);

       ASSERT(pDoc->m_pDocTemplate == NULL);  

       pDoc->m_pDocTemplate = this;

}

由于单文档模板只能拥有一个文档,所以它只是维护一个指向自己所拥有的模板的指针:m_pOnlyDoc,AddDocument函数就是要为这个成员赋值:

void CSingleDocTemplate::AddDocument(CDocument* pDoc)

{

                ......

       CDocTemplate::AddDocument(pDoc);

       m_pOnlyDoc = pDoc;

}
由于多文档模板可以拥有多个文档,所以它要维护的是包含它所打开的所有文档的指针的链表,所以它的AddDocument的实现为:

void CMultiDocTemplate::AddDocument(CDocument* pDoc)

{

                ......

       CDocTemplate::AddDocument(pDoc);

       m_docList..AddTail(pDoc);

}
   
模板通过m_pOnlyDoc(单文档)或记住了自己所拥有的所有的模板的指针,并通过GetFirstDocPositionGetNextDoc函数可以实现对它所拥有的文档的访问,同时使文档记住了自己所属文档模板的指针,同时文档提供了GetDocTemplate()函数可以取得它所属的模板。

AddDocument函数的调用主要是发生在另一个成员函数CreateNewDocument里,它的作用是创建一个新的文档:

CDocument* CDocTemplate::CreateNewDocument()

{

       if (m_pDocClass == NULL)

       {

         ……

       }

       CDocument* pDocument = (CDocument*)m_pDocClass->CreateObject();

    ……

       AddDocument(pDocument);

       return pDocument;

}

CreateNewDocument函数主要利用文档类的运行时指针的函数CreateObject创建一个新文档对象,并利用AddDocument将其指针赋給相关的成员,留做以后使用。

    在应用程序的OnFileNewOnFileOpen函数都使用了模板的OpenDocumentFile函数,而且在实际编程的时候也大都使用这个函数。在MSDN的文档说这个函数当参数不为NULL的时候打开文件,否则就用上面所说的CreateNewDocument函数创建一个新文档,那么它是如何实现的呢?

CDocument* CSingleDocTemplate::OpenDocumentFile(LPCTSTR lpszPathName,

       BOOL bMakeVisible)

{

       CDocument* pDocument = NULL;

       CFrameWnd* pFrame = NULL;

       BOOL bCreated = FALSE;      // => doc and frame created

       BOOL bWasModified = FALSE;

    //如果已经有打开的文档,就会询问否保存文件

       if (m_pOnlyDoc != NULL)

       {

              pDocument = m_pOnlyDoc;

              if (!pDocument->SaveModified())

                     return NULL;     

 

              pFrame = (CFrameWnd*)AfxGetMainWnd();

                                ......

       }

    //创建新文件

       else

       {

              pDocument = CreateNewDocument();

              ASSERT(pFrame == NULL);    

              bCreated = TRUE;

       }

                ......

    //如果第一次创建文档则也要创建框架窗口。

       if (pFrame == NULL)

       {

              ASSERT(bCreated);

 

              // create frame - set as main document frame

              BOOL bAutoDelete = pDocument->m_bAutoDelete;

              pDocument->m_bAutoDelete = FALSE

              pFrame = CreateNewFrame(pDocument, NULL);

              pDocument->m_bAutoDelete = bAutoDelete;

                                ......

       }

 

       if (lpszPathName == NULL)

       {

              // 为新文档设置默认标题

              SetDefaultTitle(pDocument);

        ……

      //一般的时候重载OnNewDocument初始化一些数据,如果返回FALSE,表示初始化失//败,销毁窗口。

              if (!pDocument->OnNewDocument())

              {

                                                ......

                     if (bCreated)

                            pFrame->DestroyWindow();    // will destroy document

                     return NULL;

              }

       }

       else

       {

              CWaitCursor wait;

 

              // open an existing document

              bWasModified = pDocument->IsModified();

              pDocument->SetModifiedFlag(FALSE);

              //OnOpenDocument函数重新初始化文档对象

              if (!pDocument->OnOpenDocument(lpszPathName))

              {

                     if (bCreated)

                     {

                //新建文档的情况

                            pFrame->DestroyWindow();   

                     }

                     else if (!pDocument->IsModified())

                     {

                            // 文档没有被修改,恢复原来文档的修改标志

                            pDocument->SetModifiedFlag(bWasModified);

                     }

                     else

                     {

                            // 修改了原始的文档

                            SetDefaultTitle(pDocument);

 

                            if (!pDocument->OnNewDocument())

                            {

                                   TRACE0("Error: OnNewDocument failed after trying to open a document - trying to continue.\n");

                            }

                     }

                     return NULL;        // open failed

              }

              pDocument->SetPathName(lpszPathName);

       }

 

       CWinThread* pThread = AfxGetThread();

       if (bCreated && pThread->m_pMainWnd == NULL)

       {

              pThread->m_pMainWnd = pFrame;

       }

       InitialUpdateFrame(pFrame, pDocument, bMakeVisible);

 

       return pDocument;

}

以下是多文档模板的OpenDocumentFile的实现

CDocument* CMultiDocTemplate::OpenDocumentFile(LPCTSTR lpszPathName,

       BOOL bMakeVisible)

{

       //新建一个文档对象

       CDocument* pDocument = CreateNewDocument();

……

 

       BOOL bAutoDelete = pDocument->m_bAutoDelete;

       pDocument->m_bAutoDelete = FALSE;  

       CFrameWnd* pFrame = CreateNewFrame(pDocument, NULL);

       pDocument->m_bAutoDelete = bAutoDelete;

……

 

       if (lpszPathName == NULL)

    //当是新建的时候

       {

              SetDefaultTitle(pDocument);

 

              // avoid creating temporary compound file when starting up invisible

              if (!bMakeVisible)

                     pDocument->m_bEmbedded = TRUE;

 

              if (!pDocument->OnNewDocument())

              {

                     pFrame->DestroyWindow();

                     return NULL;

              }

 

              m_nUntitledCount++;

       }

       else

       {

              // 打开一个已经存在的文件

              CWaitCursor wait;

              if (!pDocument->OnOpenDocument(lpszPathName))

              {

                     // user has be alerted to what failed in OnOpenDocument

                     TRACE0("CDocument::OnOpenDocument returned FALSE.\n");

                     pFrame->DestroyWindow();

                     return NULL;

              }

              pDocument->SetPathName(lpszPathName);

       }

 

       InitialUpdateFrame(pFrame, pDocument, bMakeVisible);

       return pDocument;

}

    从上面看出模板类的OpenDocumentFile函数里,利用CreateNewDocument对象使文档对象与模板对象建立了联系,利用了CreateNewFrame函数使框架窗口与文档、视图、模板发生了联系:

 

CFrameWnd* CDocTemplate::CreateNewFrame(CDocument* pDoc, CFrameWnd* pOther)

{

       if (pDoc != NULL)

              ASSERT_VALID(pDoc);

 

       ASSERT(m_nIDResource != 0); // 必须有资源ID

       CCreateContext context;

       context.m_pCurrentFrame = pOther;

       context.m_pCurrentDoc = pDoc;

       context.m_pNewViewClass = m_pViewClass;

       context.m_pNewDocTemplate = this;

 

       if (m_pFrameClass == NULL)

       {

           ……

       }

       CFrameWnd* pFrame = (CFrameWnd*)m_pFrameClass->CreateObject();

       if (pFrame == NULL)

       {

        ……

              return NULL;

       }

       ASSERT_KINDOF(CFrameWnd, pFrame);

 

       if (context.m_pNewViewClass == NULL)

              TRACE0("Warning: creating frame with no default view.\n");

 

       if (!pFrame->LoadFrame(m_nIDResource,

                     WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE,   // default frame styles

                     NULL, &context))

       {

……

              return NULL;

       }

       return pFrame;

}

总结:在模板里使用自己的数据结构维护着自己拥有的文档对象,并提供了GetFirstDocPositionGetNextDoc函数实现对这些文档的对象的访问。所以,在一个拥有多个文档模板的应用程序中,即使每个模板使用了相同类型的文档类,每个新建或打开的文档在这些文档模板之间也不是共享的。

(三)文档与视图之间的联系

在视图类有一个保护数据成员:CDocument* m_pDocument;,这个文档指针指向视图对象所属的文档,视图里常用的函数GetDocument()就是返回的这个指针;在文档类有一个保护数据成员:CDocument* m_viewList;,它保存的是所有正在显示该文档的视图的指针,通过CDocument的成员函数GetFirstViewPositionGetNextView函数可以实现对这些视图的访问。

在视图被创建的时候,在OnCreate函数里视图和文档发生了关联:

int CView::OnCreate(LPCREATESTRUCT lpcs)

{

       if (CWnd::OnCreate(lpcs) == -1)

              return -1;

       CCreateContext* pContext = (CCreateContext*)lpcs->lpCreateParams;

 

       if (pContext != NULL && pContext->m_pCurrentDoc != NULL)

       {

              pContext->m_pCurrentDoc->AddView(this);

              ASSERT(m_pDocument != NULL);

       }

       else

       {

              TRACE0("Warning: Creating a pane with no CDocument.\n");

       }

 

       return 0;  

}

这个关联是通过文档类的AddView函数实现的:

void CDocument::AddView(CView* pView)

{

    ……

       m_viewList.AddTail(pView);

       pView->m_pDocument = this;

 

       OnChangedViewList();  

}

在这个函数里,首先文档对象先把所添加的视图指针加到自己的视图链表里,然后指向自己的指针赋給了所添加的视图的m_pDocument成员。

众所周知,文档与视图进行通信的方式先调用文档的UpdateAllViews函数,从而调用视图的OnUpdate函数:

void CDocument::UpdateAllViews(CView* pSender, LPARAM lHint, CObject* pHint)

       // walk through all views

{

    //视图链表不能为空且发送者不能为空

       ASSERT(pSender == NULL || !m_viewList.IsEmpty());

       POSITION pos = GetFirstViewPosition();

       while (pos != NULL)

       {

              CView* pView = GetNextView(pos);

              ASSERT_VALID(pView);

      //不调用发送者的OnUpdate函数

              if (pView != pSender)

                     pView->OnUpdate(pSender, lHint, pHint);

       }

}

在视图的OnUpdate函数里默认的实现仅是通知视图进行重画:

Invalidate(TRUE);

我们一般重载这个更新视图的某些数据或进行其他操作,比如更新视图滚动条的滚动范围。

(四)框架窗口与文档、视图之间的联系

在框架窗口被创建的时候,创建了视图,相关的函数如下:

int CFrameWnd::OnCreate(LPCREATESTRUCT lpcs)

{

       CCreateContext* pContext = (CCreateContext*)lpcs->lpCreateParams;

       return OnCreateHelper(lpcs, pContext);

}

int CFrameWnd::OnCreateHelper(LPCREATESTRUCT lpcs, CCreateContext* pContext)

{

       if (CWnd::OnCreate(lpcs) == -1)

              return -1;

 

       // create special children first

       if (!OnCreateClient(lpcs, pContext))

       {

              TRACE0("Failed to create client pane/view for frame.\n");

              return -1;

       }

 

       // post message for initial message string

       PostMessage(WM_SETMESSAGESTRING, AFX_IDS_IDLEMESSAGE);

 

       // make sure the child windows have been properly sized

       RecalcLayout();

 

       return 0;   // create ok

}

BOOL CFrameWnd::OnCreateClient(LPCREATESTRUCT, CCreateContext* pContext)

{

       // default create client will create a view if asked for it

       if (pContext != NULL && pContext->m_pNewViewClass != NULL)

       {

              if (CreateView(pContext, AFX_IDW_PANE_FIRST) == NULL)

                     return FALSE;

       }

       return TRUE;

}

CWnd* CFrameWnd::CreateView(CCreateContext* pContext, UINT nID)

{

 

       CWnd* pView = (CWnd*)pContext->m_pNewViewClass->CreateObject();

       if (pView == NULL)

       {

              return NULL;

       }

       ASSERT_KINDOF(CWnd, pView);

 

       if (!pView->Create(NULL, NULL, AFX_WS_DEFAULT_VIEW,

              CRect(0,0,0,0), this, nID, pContext))

       {

              return NULL;        // can't continue without a view

       }

 

       if (afxData.bWin4 && (pView->GetExStyle() & WS_EX_CLIENTEDGE))

       {

              ModifyStyleEx(WS_EX_CLIENTEDGE, 0, SWP_FRAMECHANGED);

       }

       return pView;

}

在文档模板的OpenDocumentFile函数发生了如下的调用:

InitialUpdateFrame(pFrame, pDocument, bMakeVisible);

文档模板的这个函数的实现为:

pFrame->InitialUpdateFrame(pDoc, bMakeVisible);

实际是调用了框架窗口的同名函数:

void CFrameWnd::InitialUpdateFrame(CDocument* pDoc, BOOL bMakeVisible)

{

       CView* pView = NULL;

       if (GetActiveView() == NULL)

       {

              //取主视图

              CWnd* pWnd = GetDescendantWindow(AFX_IDW_PANE_FIRST, TRUE);

              if (pWnd != NULL && pWnd->IsKindOf(RUNTIME_CLASS(CView)))

              {

        //主视图存在且合法,把当前的主视图设置为活动视图

                     pView = (CView*)pWnd;

                     SetActiveView(pView, FALSE);

              }

       }

 

       if (bMakeVisible)

       {

              SendMessageToDescendants(WM_INITIALUPDATE, 0, 0, TRUE, TRUE);

 

              if (pView != NULL)

<p cla
分享到:
评论

相关推荐

    怎样在VC++中创建基于SDI多框架多视图

    的方案,即在一个程序中同时创建多个文档/视图框架,使每 个框架有其自己的菜单、工具条、文档和视图。这样一方面封 装了各个模块的处理过程和处理数据,另一方面可使程序结构 清晰,开发和维护相对简单。

    深入解析MFC

    如果做到以下几点,你就可以成为一位透彻理解MFC实现细节的专家:探索MFC文档/视图结构的内幕,从而学习视图同步、打印和打印预览;更深入地了解MFC序列化中那些没有文档记录的方面和一些没有文档记录的类,例如...

    《深入解析MFC》 [PDF]

    如果做到以下几点,你就可以成为一位透彻理解MFC实现细节的专家:探索MFC文档/视图结构的内幕,从而学习视图同步、打印和打印预览;更深入地了解MFC序列化中那些没有文档记录的方面和一些没有文档记录的类,例如...

    MFC Windows程序设计(第二版) 源代码 任哲编著 [评价可免费]

     3.3 应用程序的文档/视图结构  3.4 文档类CDocument的派生类  3.5 视图类CView的派生类  3.6 窗口框架类CFrameWnd的派生类  3.7 文档模板类CDocTemplate  3.8 应用程序类的派生类  3.9 MFC文档/视图应用程序...

    深入解析MFC_part3

    如果做到以下几点,你就可以成为一位透彻理解MFC实现细节的专家:探索MFC文档/视图结构的内幕,从而学习视图同步、打印和打印预览;更深入地了解MFC序列化中那些没有文档记录的方面和一些没有文档记录的类,例如...

    深入解析MFC_part2

    如果做到以下几点,你就可以成为一位透彻理解MFC实现细节的专家:探索MFC文档/视图结构的内幕,从而学习视图同步、打印和打印预览;更深入地了解MFC序列化中那些没有文档记录的方面和一些没有文档记录的类,例如...

    深入解析MFC_part1

    如果做到以下几点,你就可以成为一位透彻理解MFC实现细节的专家:探索MFC文档/视图结构的内幕,从而学习视图同步、打印和打印预览;更深入地了解MFC序列化中那些没有文档记录的方面和一些没有文档记录的类,例如...

    基于C++ MFC制作的音乐游戏与其谱面编辑器【100010834】

    谱面编辑器需要利用文档/视图结构,将谱面数据的操作反映到各个相关界面中。谱面中的音符操作与音频控制各自分栏,视图也占一栏,共三栏。音符信息视图中需要列出所有音符,能够设置音符的小节、节拍、持续时间等...

    用vc分割窗口为几个视图界面

    主要是使用vc将一个窗口分割成几个界面 1、启动Visual C++6.0生成一个多文档应用程序Viewex,... 2、修改CViewExApp::InitInstance()函数,为应用程序添加多文档视图结构模板的支持;  3、添加代码,编译运行程序。

    数据库概念结构设计.pdf

    混合策略:将⾃顶向下和⾃底向上结合,⽤⾃顶向下策略设计⼀个全局概念结构,以它们为基础⾃底向上设计各部分概念视图。 常⽤策略:⾃顶向下的需求分析,⾃底向上的概念结构设计。 2.⾃底向上的概念结构设计的步骤 ...

    基于java基于BS结构下的OA流程可视化的研究与实现的毕业设计,BS结构下OA流程包括文档管理、审批流程、任务管理等多个子流程

    BS结构下的OA流程包括文档管理、审批流程、任务管理等多个子流程,这些子流程的流转过程较为复杂,难以直观地展现出来,导致管理者难以对整体流程进行把控和优化。通过对这些子流程进行可视化展现,可以直观地展现流程的...

    三级模式两级映像数据库系统结构.pdf

    外模式(DDL中的Subschema DDL部分) 在RDBMS中就只有⼀个视图(View)来⽀持 当然视图提供的也只是⼀定程度的数据逻辑独⽴性。因为对于视图的更新是有条 件的,因此当应⽤程序有需要修改数据的语句时,仍然不得不...

    数据库系统的结构三级模式结构.pdf

    由于它是各个⽤户的数据视图,所以,如果不同的⽤户在应⽤需求、看待数据的⽅ 式、对数据保密的要求等各⽅⾯存在差异,则对外模式的描述就是不同的。即使是模式中的同⼀数据,其在外模式中的结构、类型、长度、 保密...

    软件架构设计说明书.docx

    本文档全面与系统地表述了图书杂志采购和借阅系统的构架,并通过使用多种视图来从不同角度描述本系统的各个主要方面,以满足图书杂志采购和借阅系统的相关涉众(客户、设计人员等)对本系统的不同关注焦点和需求。...

    [精通QT4编程电子书及源码][PDF][教程]

    中级编程(2D绘图、拖放操作与剪贴板、文件处理、网络编程、多线程、事件机制、数据库以及Qt的模板库和工具类)和高级应用(XML应用、模型/视图结构、高级绘图、进程间通信、Qt插件和脚本应用)。同时,在相关章节也穿插...

    基于c#成绩管理系统完整源码+数据库+设计文档(数据库课程设计).zip

    基于c#成绩管理系统完整源码+数据库+设计文档(数据库课程设计).zip 【资源说明】 该项目是个人毕设项目源码,评审分达到95分,调试运行正常,确保可以运行!放心下载使用。 该项目资源主要针对计算机、自动化等...

    Lotus公式语言函数简介

    @AttachmentLengths 返回表示当前文档中各个附件长度的数字或数字列表 @AtachmentNames 返回附加到文档中的文件的操作系统文件名 @Attachments 返回附加到文档中的文件数目 @Author 返回包含文档姓名的文本列表 @...

    Visual C++/MFC入门教程

    图标,设备无关位图&lt;br/&gt;|------ 2.5 使用各种映射方式 &lt;br/&gt;|------ 2.6 多边形和剪贴区域&lt;br/&gt;+-- 第三章 文档视结构 &lt;br/&gt;|------ 3.1 文档 视图 框架窗口间的关系和消息传送规律&lt;br/&gt;|------ 3.2 接收用户输入 ...

Global site tag (gtag.js) - Google Analytics