MFC技术内幕系列之(二)---MFC文档视图结构内幕

类别:VC语言 点击:0 评论:0 推荐:

                     ////////////////////////////////////////////////////////////////////////////////////
                     /********* 文章系列:MFC技术内幕系列***********/
                     /************MFC技术内幕系列之(二)***********/
                     /****    文章题目:MFC文档视图结构内幕          *****/
                     /*                           Copyright(c)2002 bigwhite             */
                     /*                                 All rights Reserved                   */
                     /*********关键字:MFC,文档视图结构************/
                     /*                                  时间:2002.7.23                   */
                     /*     注释:本文所涉及的程序源代码均在Microsoft   */
                     /             Visual Studio.Net Enterprise Architect Edition   /
                     /*                   开发工具包提供的源代码中                */
                    
//////////////////////////////////////////////////////////////////////////////////////////////////////////
引言:侯捷老师的"深入浅出MFC"一书的第8章中有“"Document/View"是MFC的基石。”一说,可以看出文档视图结构在MFC Framework中的地位是多么的重要。本文将以一个标准MFC应用程序向导作成的MDI程序为例,来和大家一起详细挖掘文档视图结构的内幕。

正文:
                       /////////////////////////////////////////////
                       /*  1.回顾"InitInstance函数"  */
                       /////////////////////////////////////////////
   在我的《MFC应用程序“生死因果”内幕》一文中,当谈到CMyWinApp::InitInstance()时,我只是粗略的讲了介绍了一下各个函数的功能,而忽略了很多细节,这里让我们在回顾一下CMyWinApp::InitInstance()函数,并将里面与文档视图结构有关的代码深入探讨一下:
   BOOL CMyApp::InitInstance()//只列出了与文档视图结构相关的源代码
  {
 //...

 //文档模板将用作文档、框架窗口和视图之间的连接
 CMultiDocTemplate* pDocTemplate;
 pDocTemplate = new CMultiDocTemplate(IDR_MyTYPE,
  RUNTIME_CLASS(CMyDoc),
  RUNTIME_CLASS(CChildFrame), // 自定义 MDI 子框架
  RUNTIME_CLASS(CMyView));
 AddDocTemplate(pDocTemplate);
 // 创建主 MDI 框架窗口
 CMainFrame* pMainFrame = new CMainFrame;
 if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
  return FALSE;
 m_pMainWnd = pMainFrame;
 // 仅当具有后缀时才调用 DragAcceptFiles
 //  在 MDI 应用程序中,这应在设置 m_pMainWnd 之后立即发生
 // 分析标准外壳命令、DDE、打开文件操作的命令行
 CCommandLineInfo cmdInfo;
 ParseCommandLine(cmdInfo);
 // 调度在命令行中指定的命令。如果
 // 用 /RegServer、/Register、/Unregserver 或 /Unregister 启动应用程序,则返回 FALSE。
 if (!ProcessShellCommand(cmdInfo))
  return FALSE;
 // 主窗口已初始化,因此显示它并对其进行更新
 pMainFrame->ShowWindow(m_nCmdShow);
 pMainFrame->UpdateWindow();
 return TRUE;
 }

                       ////////////////////////////////////////////
                       /*     2.初始化文档模板       */
                       ////////////////////////////////////////////
分析以下代码:
        CMultiDocTemplate* pDocTemplate;
 pDocTemplate = new CMultiDocTemplate(IDR_MyTYPE,
  RUNTIME_CLASS(CMyDoc),
  RUNTIME_CLASS(CChildFrame), // 自定义 MDI 子框架
  RUNTIME_CLASS(CMyView));
    应用程序首先实例化一个CMultiDocTemplate对象,此过程也是对CMultiDocTemplate类数据成员的初始化过程。按调用次序我列出了以下源代码:
    注释1: CDocTemplate构造函数定义在:..\Visual Studio.NET\vc7\atlmfc\src\mfc\doctempl.cpp
 CDocTemplate::CDocTemplate(UINT nIDResource, CRuntimeClass* pDocClass,
           CRuntimeClass* pFrameClass, CRuntimeClass* pViewClass)//部分源代码
 {
 ASSERT_VALID_IDR(nIDResource);
 ASSERT(pDocClass == NULL ||
  pDocClass->IsDerivedFrom(RUNTIME_CLASS(CDocument)));
 ASSERT(pFrameClass == NULL ||
  pFrameClass->IsDerivedFrom(RUNTIME_CLASS(CFrameWnd)));
 ASSERT(pViewClass == NULL ||
  pViewClass->IsDerivedFrom(RUNTIME_CLASS(CView)));

 m_nIDResource = nIDResource;
 ...//
        m_pDocClass = pDocClass;
 m_pFrameClass = pFrameClass;
 m_pViewClass = pViewClass;
 m_pOleFrameClass = NULL;
 m_pOleViewClass = NULL;
        ...//
 }
  以上为CMultiDocTemplate类的基类CDocTemplate构造函数的部分源代码,该函数初始化了四个重要的成员
m_nIDResource,m_pDocClass,m_pFrameClass和m_pViewClass。
   注释2: CMultiDocTemplate构造函数定义在:..\Visual Studio.NET\vc7\atlmfc\src\mfc\docmulti.cpp
   CMultiDocTemplate::CMultiDocTemplate(UINT nIDResource, CRuntimeClass* pDocClass,
 CRuntimeClass* pFrameClass, CRuntimeClass* pViewClass)
  : CDocTemplate(nIDResource, pDocClass, pFrameClass, pViewClass)
  {
 ASSERT(m_docList.IsEmpty());

 m_hMenuShared = NULL;
 m_hAccelTable = NULL;
 m_nUntitledCount = 0;   // start at 1

 // load resources in constructor if not statically allocated
 if (!CDocManager::bStaticInit)
  LoadTemplate();
  }
   看完以上代码后,来回过头看一看InitInstance函数将什么参数值传给了CMultiDocTemplate的构造函数。
原来是一些RUNTIME_CLASS宏。以下是RUNTIME_CLASS宏的定义:
   注释3: 以下的宏定义在:..\Visual Studio.NET\vc7\atlmfc\include\afx.h中
   #define RUNTIME_CLASS(class_name) _RUNTIME_CLASS(class_name)
   #define _RUNTIME_CLASS(class_name) ((CRuntimeClass*)(&class_name::class##class_name))
   这个地方是个难点,这将涉及到MFC的另一个重要技术---"执行期类型识别"。此项技术我将在
MFC技术内幕系列之(三)---《MFC执行期类型识别与动态创建技术内幕》中详细讲解。回到眼前来,源代码中这样作是为了将CMyDoc,CChildFrame,CMyView各类中的static CRuntimeClass class##class_name地址赋予CMultiDocTemplate类的各CRuntimeClass*指针成员m_pDocClass,m_pFrameClass和m_pViewClass,这位以后的动态创建Document/Frame/View"三口组"打下了基础。
                       ///////////////////////////////////////////
                       /*       3.文档模板列队       */
                       ///////////////////////////////////////////
   文档模板初始化结束后,InitInstance函数调用了CWinApp::AddDocTemplate(pDocTemplate)函数,其主要目的是将以初始化后的那个文档模板加入到文档模板链表中,并由CDocManager类对象进行管理。以下操作就是为了完成此工作。

注释1: 以下函数定义在:..\Visual Studio.NET\vc7\atlmfc\src\mfc\appui2.cpp
void CWinApp::AddDocTemplate(CDocTemplate* pTemplate)//将CMultiDocTemplate* pDocTemplate
{                                                    //传给pTemplate
 if (m_pDocManager == NULL)
  m_pDocManager = new CDocManager;
 m_pDocManager->AddDocTemplate(pTemplate);
}
注释2: 以下函数定义在:..\Visual Studio.NET\vc7\atlmfc\src\mfc\docmgr.cpp
 CDocManager::CDocManager()
 {
  }//目前是一个空函数;

 void CDocManager::AddDocTemplate(CDocTemplate* pTemplate)//部分源代码
 {
 if (pTemplate == NULL)
 {
  ...//
 }
 else
 {
  ASSERT_VALID(pTemplate);
  ASSERT(m_templateList.Find(pTemplate, NULL) == NULL);// must not be in list
  pTemplate->LoadTemplate();
  m_templateList.AddTail(pTemplate);//CPtrList m_templateList is a member                                                             //of CDocManager
 }
 }

                       ///////////////////////////////////////////////
                       /*    4.创建程序主框架窗口    */
                       ///////////////////////////////////////////////
    应用程序实例化了一个CMainFrame类对象,并调用LoadFrame函数加载窗口资源创建主框架窗口。以下是创建主框架窗口的流程。
    创建窗口的主要代码是:pMainFrame->LoadFrame(IDR_MAINFRAME);LoadFrame函数是MFC包装了窗口创建过程的函数,在后面动态创建Child窗口时,它还将披挂上阵(但稍有不同)。下面是它的源代码,让我们仔细分析一下:
  注释1: 以下函数定义在:..\Visual Studio.NET\vc7\atlmfc\src\mfc\winmdi.cpp
   
  BOOL CMDIFrameWnd::LoadFrame(UINT nIDResource, DWORD dwDefaultStyle,
 CWnd* pParentWnd, CCreateContext* pContext)          
 {
 if (!CFrameWnd::LoadFrame(nIDResource, dwDefaultStyle,
   pParentWnd, pContext))
  return FALSE;

 // save menu to use when no active MDI child window is present
 ASSERT(m_hWnd != NULL);
 m_hMenuDefault = ::GetMenu(m_hWnd);
 if (m_hMenuDefault == NULL)
  TRACE(traceAppMsg, 0, "Warning: CMDIFrameWnd without a default menu.\n");
 return TRUE;
 }
  CMDIFrameWnd::LoadFrame调用了其基类CFrameWnd的LoadFrame,并将参数原封不动的传给它。

  注释2: 以下函数定义在:..\Visual Studio.NET\vc7\atlmfc\src\mfc\winfrm.cpp
 BOOL CFrameWnd::LoadFrame(UINT nIDResource, DWORD dwDefaultStyle,
 CWnd* pParentWnd, CCreateContext* pContext)          //部分源代码
 {
 ...//
 CString strFullString;
 if (strFullString.LoadString(nIDResource))
  AfxExtractSubString(m_strTitle, strFullString, 0);    // first sub-string

 VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG));

 // attempt to create the window
 LPCTSTR lpszClass = GetIconWndClass(dwDefaultStyle, nIDResource);
 CString strTitle = m_strTitle;
 if (!Create(lpszClass, strTitle, dwDefaultStyle, rectDefault,
   pParentWnd, MAKEINTRESOURCE(nIDResource), 0L, pContext))
 {
  return FALSE;   // will self destruct on failure normally
 }

 ...//
 if (pContext == NULL)   // send initial update
  SendMessageToDescendants(WM_INITIALUPDATE, 0, 0, TRUE, TRUE);

 return TRUE;
 }
                       //////////////////////////////////////////////////////
                       /* 4.1注册应用程序主框架窗口类 */
                       //////////////////////////////////////////////////////
   在传统的Win32API编程中,创建窗口一般步骤是定义窗口类,注册窗口类,并调用::CreateWindow函数来创建。前面说过LoadFrame函数封装了MFC创建窗口的过程,那么也就是说LoadFrame函数将负责定义窗口类,注册窗口类等琐碎工作。下面我们就通过挖掘源代码来看看LoadFrame函数是如何完成这些工作的。
   LoadFrame首先调用AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG),
   注释1: 以下宏定义在:..\Visual Studio.NET\vc7\atlmfc\src\mfc\afximpl.h
       #define AfxDeferRegisterClass(fClass) AfxEndDeferRegisterClass(fClass)
  
   注释2: 以下函数定义在:..\Visual Studio.NET\vc7\atlmfc\src\mfc\wincore.cpp
 
  BOOL AFXAPI AfxEndDeferRegisterClass(LONG fToRegister)//部分源代码
  {
 // mask off all classes that are already registered
 AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
 fToRegister &= ~pModuleState->m_fRegisteredClasses;
 if (fToRegister == 0)
  return TRUE;

 LONG fRegisteredClasses = 0;

 // common initialization
 WNDCLASS wndcls;
 memset(&wndcls, 0, sizeof(WNDCLASS));   // start with NULL defaults
 wndcls.lpfnWndProc = DefWindowProc;
 wndcls.hInstance = AfxGetInstanceHandle();
 wndcls.hCursor = afxData.hcurArrow;

 INITCOMMONCONTROLSEX init;
 init.dwSize = sizeof(init);

 // work to register classes as specified by fToRegister, populate fRegisteredClasses as           we go
 if (fToRegister & AFX_WND_REG)
 {
  // Child windows - no brush, no icon, safest default class styles
  wndcls.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
  wndcls.lpszClassName = _afxWnd;
  if (AfxRegisterClass(&wndcls))
   fRegisteredClasses |= AFX_WND_REG;
 }
 ...//
 if (fToRegister & AFX_WNDMDIFRAME_REG)
 {
  // MDI Frame window (also used for splitter window)
  wndcls.style = CS_DBLCLKS;
  wndcls.hbrBackground = NULL;
  if (_AfxRegisterWithIcon(&wndcls, _afxWndMDIFrame, AFX_IDI_STD_MDIFRAME))
   fRegisteredClasses |= AFX_WNDMDIFRAME_REG;
 }
 if (fToRegister & AFX_WNDFRAMEORVIEW_REG)
 {
  // SDI Frame or MDI Child windows or views - normal colors
  wndcls.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
  wndcls.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
  if (_AfxRegisterWithIcon(&wndcls, _afxWndFrameOrView, AFX_IDI_STD_FRAME))
   fRegisteredClasses |= AFX_WNDFRAMEORVIEW_REG;
 }  
        
       ...//
 // must have registered at least as mamy classes as requested
 return (fToRegister & fRegisteredClasses) == fToRegister;
   }
   MFC预定义了若干个“窗口类模板”,比如"AFX_WNDMDIFRAME_REG","AFX_WNDFRAMEORVIEW_REG"等,MFC在
LoadFrame函数中调用AfxEndDeferRegisterClass函数为你的应用程序预注册了适当的窗口类。本例中预注册的窗口类为AFX_WNDFRAMEORVIEW_REG。(注意是预注册,如果你在后面更改了CREATESTRUCT结构的域成员,MFC还会根据你的更改重新为你的应用程序正式注册新的窗口类,稍候会有详细叙述)
  
   预注册完窗口类,MFC将判断你是否想更改窗口类的各参数。若你更改了,则MFC会重新注册新类;否则源预注册的窗口类就将成为正式的窗口类。下面我们来看看MFC的判断过程:此判断过程由GetIconWndClass开始
  LPCTSTR lpszClass = GetIconWndClass(dwDefaultStyle, nIDResource);
  注释3: 以下函数定义在:..\Visual Studio.NET\vc7\atlmfc\src\mfc\winfrm.cpp
 
  LPCTSTR CFrameWnd::GetIconWndClass(DWORD dwDefaultStyle, UINT nIDResource)//部分源代码
 {
 ...//
 HICON hIcon = ::LoadIcon(hInst, MAKEINTRESOURCE(nIDResource));
 if (hIcon != NULL)
 {
  CREATESTRUCT cs;
  memset(&cs, 0, sizeof(CREATESTRUCT));
  cs.style = dwDefaultStyle;
  PreCreateWindow(cs);
   // will fill lpszClassName with default WNDCLASS name
   // ignore instance handle from PreCreateWindow.

  WNDCLASS wndcls;
  if (cs.lpszClass != NULL &&
   GetClassInfo(AfxGetInstanceHandle(), cs.lpszClass, &wndcls) &&
   wndcls.hIcon != hIcon)
  {
   // register a very similar WNDCLASS
   return AfxRegisterWndClass(wndcls.style,
    wndcls.hCursor, wndcls.hbrBackground, hIcon);
  }
 }
 return NULL;        // just use the default
}
   GetIconWndClass函数将调用CMainFrame::PreCreateWindow(CREATESTRUCT& cs)来看看应用程序是否修改了CREATESTRUCT结构的域成员。CMainFrame::PreCreateWindow调用   CMDIFrameWnd::PreCreateWindow(CREATESTRUCT& cs),后者的代码如下:
  BOOL CMDIFrameWnd::PreCreateWindow(CREATESTRUCT& cs)//in winmdi.cpp
  {
 if (cs.lpszClass == NULL)
 {
  VERIFY(AfxDeferRegisterClass(AFX_WNDMDIFRAME_REG));
  cs.lpszClass = _afxWndMDIFrame;
 }
 return TRUE;
  }
  MFC将为应用程序注册AFX_WNDMDIFRAME_REG预定义窗口类,并设置cs.lpszClass = _afxWndMDIFrame。
在应用程序的代码中我更改了cs结构:
  BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
 {
 if( !CMDIFrameWnd::PreCreateWindow(cs) )
  return FALSE;
 // TODO: 在此处通过修改 CREATESTRUCT cs 来修改窗口类或
 // 样式
 cs.dwExStyle=~WS_EX_CLIENTEDGE;
 return TRUE;
 }  
 
 CMainFrame::PreCreateWindow返回后,GetIconWndClass函数调用GetClassInfo函数重新收集cs信息(此时的信息已是更改后的了),并调用AfxRegisterWndClass函数重新注册该窗口类(此窗口类为该应用程序的正式窗口类)。到此为止窗口类注册完毕,以后程序还会调用一次CMainFrame::PreCreateWindow,不过那此只是"过门不如"而已。
                       /////////////////////////////////////////////////
                       /*    4.2主框架窗口创建开始    */
                       /////////////////////////////////////////////////

  开始进入创建框架窗口的实质阶段。看LoadFrame函数做了什么?原来它调用了Create函数。
  注释1: 以下函数定义在:..\Visual Studio.NET\vc7\atlmfc\src\mfc\winfrm.cpp
   BOOL CFrameWnd::Create(LPCTSTR lpszClassName,
 LPCTSTR lpszWindowName,
 DWORD dwStyle,
 const RECT& rect,
 CWnd* pParentWnd,
 LPCTSTR lpszMenuName,
 DWORD dwExStyle,
 CCreateContext* pContext)
  {
 HMENU hMenu = NULL;
 if (lpszMenuName != NULL)
 {
  // load in a menu that will get destroyed when window gets destroyed
  HINSTANCE hInst = AfxFindResourceHandle(lpszMenuName, RT_MENU);
  if ((hMenu = ::LoadMenu(hInst, lpszMenuName)) == NULL)
  {
   TRACE(traceAppMsg, 0, "Warning: failed to load menu for CFrameWnd.\n");
   PostNcDestroy();            // perhaps delete the C++ object
   return FALSE;
  }
 }

 m_strTitle = lpszWindowName;    // save title for later

 if (!CreateEx(dwExStyle, lpszClassName, lpszWindowName, dwStyle,
  rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top,
  pParentWnd->GetSafeHwnd(), hMenu, (LPVOID)pContext))
 {
  TRACE(traceAppMsg, 0, "Warning: failed to create CFrameWnd.\n");
  if (hMenu != NULL)
   DestroyMenu(hMenu);
  return FALSE;
 }

 return TRUE;
  }

  简单地说CFrameWnd::Create函数调用了基类的CWnd::CreateEx;
  注释2: 以下函数定义在:..\Visual Studio.NET\vc7\atlmfc\src\mfc\wincore.cpp
  BOOL CWnd::CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName,
 LPCTSTR lpszWindowName, DWORD dwStyle,
 int x, int y, int nWidth, int nHeight,
 HWND hWndParent, HMENU nIDorHMenu, LPVOID lpParam)//部分源代码
 {
 // allow modification of several common create parameters
 CREATESTRUCT cs;
 cs.dwExStyle = dwExStyle;
 cs.lpszClass = lpszClassName;
 cs.lpszName = lpszWindowName;
 cs.style = dwStyle;
 cs.x = x;
 cs.y = y;
 cs.cx = nWidth;
 cs.cy = nHeight;
 cs.hwndParent = hWndParent;
 cs.hMenu = nIDorHMenu;
 cs.hInstance = AfxGetInstanceHandle();
 cs.lpCreateParams = lpParam;

 if (!PreCreateWindow(cs))
 {
  PostNcDestroy();
  return FALSE;
 }

 AfxHookWindowCreate(this);
 HWND hWnd = ::CreateWindowEx(cs.dwExStyle, cs.lpszClass,
   cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy,
   cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams);
        ...//
       
        return TRUE;
 }

 BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
 {
 if( !CMDIFrameWnd::PreCreateWindow(cs) )
  return FALSE;
 // TODO: 在此处通过修改 CREATESTRUCT cs 来修改窗口类或
 // 样式
        cs.dwExStyle=~WS_EX_CLIENTEDGE;
 return TRUE;
 }

 BOOL CMDIFrameWnd::PreCreateWindow(CREATESTRUCT& cs)
 {
 if (cs.lpszClass == NULL)
 {
  VERIFY(AfxDeferRegisterClass(AFX_WNDMDIFRAME_REG));
  cs.lpszClass = _afxWndMDIFrame;
 }
 return TRUE;
 }
   CWnd::CreateEx调用了CMainFrame::PreCreateWindow,但此次AfxDeferRegisterClass将不会被调用。也就是我上面所说的“过门不入”。

    CWnd::CreateEx函数还调用了AfxHookWindowCreate(this);后者是干什么的呢?其实它与消息映射和命令传递有关,我将在MFC技术内幕系列之(四)--《MFC消息映射与消息传递内幕》一文中详解。
  
   CWnd::CreateEx调用Win32API ::CreateWindowEx函数(传统的Win32API程序员一定不陌生这个函数),
就这样主框架窗口创建结束。

                       //////////////////////////////////////////////
                       /*     5.标准外壳命令解析      */
                       ///////////////////////////////////////////////
    MFC向导制作的标准MDI应用程序启动时,应用程序会自动启动一个子窗口框架(实际上是一套文档模板),这是为何呢?下面我将详细讲解一下这个创建过程.
    其实这一过程也是在CMyWinApp::InitInstance()函数中完成的,看看下面代码:

        CCommandLineInfo cmdInfo;
 ParseCommandLine(cmdInfo);
        if (!ProcessShellCommand(cmdInfo))
  return FALSE;
   函数首先实例化一个CCommandLineInfo类对象cmdInfo,让我们看看CCommandLineInfo是个什么东东? 
 //in afxwin.h
 class CCommandLineInfo : public CObject//部分源代码
{
 public:
 // Sets default values
 CCommandLineInfo();
        ...//
 BOOL m_bShowSplash;
 BOOL m_bRunEmbedded;
 BOOL m_bRunAutomated;
 enum { FileNew, FileOpen, FilePrint, FilePrintTo, FileDDE, AppRegister,
  AppUnregister, FileNothing = -1 } m_nShellCommand;

 // not valid for FileNew
 CString m_strFileName;

 // valid only for FilePrintTo
 CString m_strPrinterName;
 CString m_strDriverName;
 CString m_strPortName;

 ~CCommandLineInfo();
        // Implementation
        ...//
 };

 再让我们来看看它的构造函数的实现:

//in appcore.cpp
 CCommandLineInfo::CCommandLineInfo()
 {
 m_bShowSplash = TRUE;
 m_bRunEmbedded = FALSE;
 m_bRunAutomated = FALSE;
 m_nShellCommand = FileNew;
 }
 m_nShellCommand = FileNew;这一句对我们最重要;至于CWinApp::ParseCommandLine我想用MFC文档中的一句话来解释:
   Call this member function to parse the command line and send the parameters, one at a time, to CCommandLineInfo::ParseParam.
   下面我们来看看外壳命令解析的主角:CWinApp::ProcessShellCommand
//in appui2.cpp
//DDE and ShellExecute support

BOOL CWinApp::ProcessShellCommand(CCommandLineInfo& rCmdInfo)//部分源代码
{
 BOOL bResult = TRUE;
 switch (rCmdInfo.m_nShellCommand)
 {
 case CCommandLineInfo::FileNew:
  if (!AfxGetApp()->OnCmdMsg(ID_FILE_NEW, 0, NULL, NULL))
   OnFileNew();
  if (m_pMainWnd == NULL)
   bResult = FALSE;
  break;

  // If we've been asked to open a file, call OpenDocumentFile()

 case CCommandLineInfo::FileOpen:
  if (!OpenDocumentFile(rCmdInfo.m_strFileName))
   bResult = FALSE;
  break;
        case CCommandLineInfo::FilePrintTo:
 case CCommandLineInfo::FilePrint:
  ...//
        case CCommandLineInfo::FileDDE:
  ...//
 case CCommandLineInfo::AppRegister:
  ...//
        case CCommandLineInfo::AppUnregister:
  ...//
         }
 return bResult;
}
   挖掘源代码的确是了解MFC运行内幕的最好手段,大家一看源代码便知道如之何了。CCommandLineInfo构造函数中m_nShellCommand = FileNew;所以在ProcessShellCommand中对应的代码自然就一目了然了:CWinApp::OnFileNew()被调用了。

                       //////////////////////////////////////////////////
                       /*   6.一套文档/视图即将诞生   */
                       //////////////////////////////////////////////////
    上文说CWinApp::OnFileNew()被调用了,那么就让我来看看其代码吧!
 //in appdlg.cpp
 void CWinApp::OnFileNew()
 {
    if (m_pDocManager != NULL)
 m_pDocManager->OnFileNew();
 }
  //in docmgr.cpp
void CDocManager::OnFileNew()//部分源代码
{
 ...//
       CDocTemplate* pTemplate = (CDocTemplate*)m_templateList.GetHead();
 if (m_templateList.GetCount() > 1)
 {
  // more than one document template to choose from
  // bring up dialog prompting user
  CNewTypeDlg dlg(&m_templateList);
  INT_PTR nID = dlg.DoModal();
  if (nID == IDOK)
   pTemplate = dlg.m_pSelectedTemplate;
  else
   return;     // none - cancel operation
 }

 ASSERT(pTemplate != NULL);
 ASSERT_KINDOF(CDocTemplate, pTemplate);
        pTemplate->OpenDocumentFile(NULL);
  // if returns NULL, the user has already been alerted
}
 //in docmulti.cpp
CDocument* CMultiDocTemplate::OpenDocumentFile(LPCTSTR lpszPathName,
 BOOL bMakeVisible)//部分源代码
{
 CDocument* pDocument = CreateNewDocument();
 ...//
        BOOL bAutoDelete = pDocument->m_bAutoDelete;
 pDocument->m_bAutoDelete = FALSE;   // don't destroy if something goes wrong
 CFrameWnd* pFrame = CreateNewFrame(pDocument, NULL);
 pDocument->m_bAutoDelete = bAutoDelete;
 ...//

 if (lpszPathName == NULL)
 {
  // create a new document - with default document name
  SetDefaultTitle(pDocument);

  // avoid creating temporary compound file when starting up invisible
  if (!bMakeVisible)
   pDocument->m_bEmbedded = TRUE;

  if (!pDocument->OnNewDocument())
  {
   // user has be alerted to what failed in OnNewDocument
   TRACE(traceAppMsg, 0, "CDocument::OnNewDocument returned FALSE.\n");
   pFrame->DestroyWindow();
   return NULL;
  }

  // it worked, now bump untitled count
  m_nUntitledCount++;
 }
 else
 {
  // open an existing document
  CWaitCursor wait;
  if (!pDocument->OnOpenDocument(lpszPathName))
  {
   // user has be alerted to what failed in OnOpenDocument
   TRACE(traceAppMsg, 0, "CDocument::OnOpenDocument returned FALSE.\n");
   pFrame->DestroyWindow();
   return NULL;
  }
  pDocument->SetPathName(lpszPathName);
 }

 InitialUpdateFrame(pFrame, pDocument, bMakeVisible);
 return pDocument;
}
                       //////////////////////////////////////////////
                       /*     6.1.子文档动态生成      */
                       //////////////////////////////////////////////
   CMultiDocTemplate::OpenDocumentFile调用了CreateNewDocument(),这就是子文档动态生成的主函数。
  //in doctempl.cpp
 CDocument* CDocTemplate::CreateNewDocument()//部分源代码
 {
 // default implementation constructs one from CRuntimeClass
 ...//
 CDocument* pDocument = (CDocument*)m_pDocClass->CreateObject();
 ...//
 AddDocument(pDocument);//将动态生成的文档对象的指针加入到应用程序的文档列表中
 return pDocument;
 }
  CDocument* pDocument = (CDocument*)m_pDocClass->CreateObject();这一句就是动态产生的核心,它借助于CRuntimeClass动态生成一个CDocument对象。其动态生成的奥秘我将在MFC技术内幕系列之(三)---
《MFC执行期类型识别与动态创建技术内幕》一文中详解。

                       //////////////////////////////////////////////////
                       /*   6.2.子窗口框架动态生成    */
                       /////////////////////////////////////////////////
  CMultiDocTemplate::OpenDocumentFile调用了CreateNewFrame,这就是子窗口框架动态生成的主函数。

 // Default frame creation
 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
 CCreateContext context;
 context.m_pCurrentFrame = pOther;
 context.m_pCurrentDoc = pDoc;
 context.m_pNewViewClass = m_pViewClass;
 context.m_pNewDocTemplate = this;
        ...//
 CFrameWnd* pFrame = (CFrameWnd*)m_pFrameClass->CreateObject();
 if (pFrame == NULL)
 {
  TRACE(traceAppMsg, 0, "Warning: Dynamic create of frame %hs failed.\n",
   m_pFrameClass->m_lpszClassName);
  return NULL;
 }
 ASSERT_KINDOF(CFrameWnd, pFrame);
        ...//
 
 // create new from resource
 if (!pFrame->LoadFrame(m_nIDResource,
   WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE,   // default frame styles
   NULL, &context))
 {
  TRACE(traceAppMsg, 0, "Warning: CDocTemplate couldn't create a frame.\n");
  // frame will be deleted in PostNcDestroy cleanup
  return NULL;
 }

 // it worked !
 return pFrame;
}
   CFrameWnd* pFrame = (CFrameWnd*)m_pFrameClass->CreateObject();这一句就是动态产生的核心,它借助于CRuntimeClass动态生成一个CDocument对象。其动态生成的奥秘我将在MFC技术内幕系列之(三)---
《MFC执行期类型识别与动态创建技术内幕》一文中详解。之后函数调用LoadFrame来创建子窗口。其过程与创建主框架窗口的过程大致相同,但也有一些不同的地方,下面我就将说说这点不同。

                       //////////////////////////////////////////////
                       /*     6.3.子视图动态生成      */
                       //////////////////////////////////////////////
   瞪大眼睛仔细察看OpenDocumentFile的源代码,疑惑了,"怎么没有类似CView* pView =CreateNewView();
的代码?","那么子视图是如何生成的呢"下面我就为你详细解释一下吧!其实子视图动态生成函数被放到另一个地方了。让我们详细来看看吧。
   其实,关键还是在LoadFrame,但与创建主窗口框架的那个LoadFrame不同的是传进了一个不同的参数
&context,你回过头看看主窗口框架的那个LoadFrame,调用它时使用了默认参数,而那个默认参数值为NULL,
下面看看CCreateContext 结构。
//in afxext.h
struct CCreateContext   // Creation information structure
 // All fields are optional and may be NULL
{
 // for creating new views
 CRuntimeClass* m_pNewViewClass; // runtime class of view to create or NULL
 CDocument* m_pCurrentDoc;

 // for creating MDI children (CMDIChildWnd::LoadFrame)
 CDocTemplate* m_pNewDocTemplate;

 // for sharing view/frame state from the original view/frame
 CView* m_pLastView;
 CFrameWnd* m_pCurrentFrame;
// Implementation
 CCreateContext();
};
  而在CDocTemplate::CreateNewFrame中初始化了该结构如下:
      CCreateContext context;
 context.m_pCurrentFrame = pOther;
 context.m_pCurrentDoc = pDoc;
 context.m_pNewViewClass = m_pViewClass;
 context.m_pNewDocTemplate = this;
       context.m_pNewViewClass = m_pViewClass;//关键的成员
  下面看看这个创建的具体过程:
      LoadFrame(...,&context)-->CFrameWnd::Create(...,&context)--> CWnd::CreateEx(...,&context)
-->::CreateWindowEx
     ::CreateWindowEx API函数将产生WM_CREATE消息,并将&context传递之,CMainFrame::OnCreate将响应消息,并引起一系列的函数调用,看下面:
//in mainfrm.cpp
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
 if (CMDIFrameWnd::OnCreate(lpCreateStruct) == -1)
  return -1;
 ...//
 return 0;
}
// in winfrm.cpp
int CFrameWnd::OnCreate(LPCREATESTRUCT lpcs)
{
 CCreateContext* pContext = (CCreateContext*)lpcs->lpCreateParams;
 return OnCreateHelper(lpcs, pContext);
}
// in winfrm.cpp
int CFrameWnd::OnCreateHelper(LPCREATESTRUCT lpcs, CCreateContext* pContext)//部分源代码
{
 if (CWnd::OnCreate(lpcs) == -1)
  return -1;

 // create special children first
 if (!OnCreateClient(lpcs, pContext))
 {
  TRACE(traceAppMsg, 0, "Failed to create client pane/view for frame.\n");
  return -1;
 }

 ...//
        return 0;   // create ok
}
// in winfrm.cpp
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;
}
// in winfrm.cpp
CWnd* CFrameWnd::CreateView(CCreateContext* pContext, UINT nID)//部分源代码
{
 ...//

 // Note: can be a CWnd with PostNcDestroy self cleanup
 CWnd* pView = (CWnd*)pContext->m_pNewViewClass->CreateObject();
 if (pView == NULL)
 {
  TRACE(traceAppMsg, 0, "Warning: Dynamic create of view type %hs failed.\n",
   pContext->m_pNewViewClass->m_lpszClassName);
  return NULL;
 }
 ASSERT_KINDOF(CWnd, pView);

 // views are always created with a border!
 if (!pView->Create(NULL, NULL, AFX_WS_DEFAULT_VIEW,
  CRect(0,0,0,0), this, nID, pContext))
 {
  TRACE(traceAppMsg, 0, "Warning: could not create view for frame.\n");
  return NULL;        // can't continue without a view
 }

 ...//
 return pView;
}
  CWnd* pView = (CWnd*)pContext->m_pNewViewClass->CreateObject();核心函数终于出现了。子视图动态生成完毕。

                       /////////////////////////////////////////
                       /*         7.收尾工作          */
                       /////////////////////////////////////////
   至此,一套完整的Document/ChildFrame/View结构生成,此“三口组”共属同一套文档模板,如果你要定义另一套不同的文档模档需再定义另一组不同“三口组”(ChildFrame可以使用相同的)。并调用AddDocTemplate将该文档模板加入到应用程序的文档模板列表。比如:

       CMultiDocTemplate* pOtherDocTemplate;
 pOtherDocTemplate = new CMultiDocTemplate(IDR_MyOtherTYPE,
  RUNTIME_CLASS(CMyOtherDoc),
  RUNTIME_CLASS(CChildFrame), // 自定义 MDI 子框架
  RUNTIME_CLASS(CMyOtherView));
 AddDocTemplate(pOtherDocTemplate);

“三口组”生成后程序调用ShowWindow,UpdateWindow将应用程序的主窗口展现在你眼前。

  注释:当你在File菜单中选择new或在工具栏中单击“新建”时,应用程序将选择当前默认的文档模板并以它为基础动态生成 Document/ChildFrame/View“三口组”,其生成过程与我上述讲的一般不二。
                       ////////////////////////////////////////
                       /*       8. "下集预告"        */
                       ////////////////////////////////////////

   MFC技术内幕系列之(三)---《MFC执行期类型识别与动态创建技术内幕》
                       

 

 

 

 

 


 

本文地址:http://com.8s8s.com/it/it2315.htm