<form id="hz9zz"></form>
  • <form id="hz9zz"></form>

      <nobr id="hz9zz"></nobr>

      <form id="hz9zz"></form>

    1. 明輝手游網中心:是一個免費提供流行視頻軟件教程、在線學習分享的學習平臺!

      文檔/視圖結構中的各個局部是如何聯系到一起的

      [摘要]文檔/視圖結構是MFC中最有特色而又有難度的部分,在這當中涉及了應用、文檔模板、文檔、視圖、MDI框架窗口、MDI子窗口等不同的對象,如果不了解這些部分之間如何關聯的話,就可能犯錯誤,也就很難編出有水平的文檔/視圖程序。比如我在初學VC編程的時候,為應用程序添加了兩個文檔模板,兩個模板公用一個文檔...
           文檔/視圖結構是MFC中最有特色而又有難度的部分,在這當中涉及了應用、文檔模板、文檔、視圖、MDI框架窗口、MDI子窗口等不同的對象,如果不了解這些部分之間如何關聯的話,就可能犯錯誤,也就很難編出有水平的文檔/視圖程序。比如我在初學VC編程的時候,為應用程序添加了兩個文檔模板,兩個模板公用一個文檔類,只是視圖不一樣,期望當一個模板的文檔的視圖改變了文檔后,調用UpdateAllViews后也能更新另一個文檔模板的視圖,結果當然是不行的,原因就是對MFC的文檔/視圖結構沒有深入的了解,了解的最好方法就是閱讀一下MFC的源代碼。下面就是我的筆記:

      (一)應用程序對象與文檔模板之間的聯系:

              首先,在應用程序對象中有一個CDocManager指針類型的共有數據成員m_pDocManager,在CDocManager中維護一個CPtrList類型的鏈表:m_tempateList,它是一個保護成員。InitInstance函數中調用CWinApp::AddDocTemplate函數,實際上是調用m_pDocManager的AddDocTemplate函數向鏈表m_templateList添加模板指針。CWinApp提供了GetFirstDocTemplatePosition和GetNextDocTemplate函數實現對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(單文檔)或記住了自己所擁有的所有的模板的指針,并通過GetFirstDocPosition和GetNextDoc函數可以實現對它所擁有的文檔的訪問,同時使文檔記住了自己所屬文檔模板的指針,同時文檔提供了GetDocTemplate()函數可以取得它所屬的模板。

      對AddDocument函數的調用主要是發生在另一個成員函數CreateNewDocument里,它的作用是創建一個新的文檔:

      CDocument* CDocTemplate::CreateNewDocument()

      {

             if (m_pDocClass == NULL)

             {

               ……

             }

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

          ……

             AddDocument(pDocument);

             return pDocument;

      }

      CreateNewDocument函數主要利用文檔類的運行時指針的函數CreateObject創建一個新文檔對象,并利用AddDocument將其指針賦給相關的成員,留做以后使用。

          在應用程序的OnFileNew和OnFileOpen函數都使用了模板的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;

      }

      總結:在模板里使用自己的數據結構維護著自己擁有的文檔對象,并提供了GetFirstDocPosition和GetNextDoc函數實現對這些文檔的對象的訪問。所以,在一個擁有多個文檔模板的應用程序中,即使每個模板使用了相同類型的文檔類,每個新建或打開的文檔在這些文檔模板之間也不是共享的。

      (三)文檔與視圖之間的聯系

      在視圖類有一個保護數據成員:CDocument* m_pDocument;,這個文檔指針指向視圖對象所屬的文檔,視圖里常用的函數GetDocument()就是返回的這個指針;在文檔類有一個保護數據成員:CDocument* m_viewList;,它保存的是所有正在顯示該文檔的視圖的指針,通過CDocument的成員函數GetFirstViewPosition和GetNextView函數可以實現對這些視圖的訪問。

      在視圖被創建的時候,在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)

                           pView->OnActivateFrame(WA_INACTIVE, this);

              ……

                    ActivateFrame(nCmdShow);

                    if (pView != NULL)

                           pView->OnActivateView(TRUE, pView, pView);

             }



             // update frame counts and frame title (may already have been visible)

             if (pDoc != NULL)

                    pDoc->UpdateFrameCounts();

             OnUpdateFrameTitle(TRUE);

      }

      上面的函數中對視圖的操作主要是用SetActiveView設置了活動視圖,并且調用了視圖的OnActivateFrame函數。在CFrameWnd類中維護著一個保護成員:CView* m_pViewActive;,SetAcitveView函數主要就是對它進行操作:

      void CFrameWnd::SetActiveView(CView* pViewNew, BOOL bNotify)

      {

             CView* pViewOld = m_pViewActive;

             if (pViewNew == pViewOld)

                    return;     // do not re-activate if SetActiveView called more than once



             m_pViewActive = NULL;   // no active for the following processing



             // deactivate the old one

             if (pViewOld != NULL)

                    pViewOld->OnActivateView(FALSE, pViewNew, pViewOld);



             if (m_pViewActive != NULL)

                    return;     // already set

             m_pViewActive = pViewNew;



             // activate

             if (pViewNew != NULL && bNotify)

                    pViewNew->OnActivateView(TRUE, pViewNew, pViewOld);

      }

      CFrameWnd還有另一個函數返回這個成員:

      CView* CFrameWnd::GetActiveView() const

      {

             ASSERT(m_pViewActive == NULL

                    m_pViewActive->IsKindOf(RUNTIME_CLASS(CView)));

             return m_pViewActive;

      }

      CframeWnd還有一個函數能取得當前活動的文檔,它是通過活動視圖間接得到的:

      CDocument* CFrameWnd::GetActiveDocument()

      {

             ASSERT_VALID(this);

             CView* pView = GetActiveView();

             if (pView != NULL)

                    return pView->GetDocument();

             return NULL;

      }

      (五)MDI主窗口和子窗口之間的關聯:

      在MDI子窗口創建的時候,指定了它與MDI之間的關系:

      BOOL CMDIChildWnd::Create(LPCTSTR lpszClassName,

             LPCTSTR lpszWindowName, DWORD dwStyle,

             const RECT& rect, CMDIFrameWnd* pParentWnd,

             CCreateContext* pContext)

      {

             if (pParentWnd == NULL)

             {

                    CWnd* pMainWnd = AfxGetThread()->m_pMainWnd;

                    ASSERT(pMainWnd != NULL);

                    ASSERT_KINDOF(CMDIFrameWnd, pMainWnd);

                    pParentWnd = (CMDIFrameWnd*)pMainWnd;

             }

          ……

             pParentWnd->RecalcLayout();



             CREATESTRUCT cs;

      ……

      //指定了所屬的MDI子窗口

             cs.hwndParent = pParentWnd->m_hWnd;

          ……

             cs.lpCreateParams = (LPVOID)pContext;



             if (!PreCreateWindow(cs))

             {

                    PostNcDestroy();

                    return FALSE;

             }

             MDICREATESTRUCT mcs;

          ……

             mcs.style = cs.style & ~(WS_MAXIMIZE WS_VISIBLE);

             mcs.lParam = (LONG)cs.lpCreateParams;



             AfxHookWindowCreate(this);

          //發送WM_MDICREATE消息,創建了MDI子窗口

             HWND hWnd = (HWND)::SendMessage(pParentWnd->m_hWndMDIClient,

                    WM_MDICREATE, 0, (LPARAM)&mcs);

             if (!AfxUnhookWindowCreate())

                    PostNcDestroy();        // cleanup if MDICREATE fails too soon

          ……

             return TRUE;

      }

      當MDI子窗口創建了以后,MDI主窗口就可以用自己的函數實現對子窗口的管理,例如取得當前的活動子窗口是通過發送WM_MDIGETACTIVE消息實現的


      日韩精品一区二区三区高清