MFC框架如何创建的过程

 

温故而知新,学习MFC框架如何创建的过程

很久没有使用MFC了,以至于都忘记MFC框架复杂的窗口、文档、视的创建过程了。

下面我们跟踪一个MFC MDI的应用程序,来温习或学习一下。

 

使用AppWizard创建一个MDI应用程序,我创建的应用程序叫MDITest,这样MFC生成了如下的类:

类名

作用

CMDITestApp

派生于CWinApp的应用程序类。

CMainFrame

派生于CMDIFrameWnd的MDI框架窗口类。

CMDITestDoc

派生于CDocument的文档类。

CChildFrame

派生于CMDIChildWnd的MDI子窗口类。

CMDITestView

派生于CView的文档显示类。

在运行时刻,CMainFrame, CChildFrame, CMDITestView的窗口关系如下面的表格示出:

CMainFrame

(Menu, Toolbar …)

MDIClient

 

CChildFrame

CMDITestView

   pDocument = *CMDITestDoc   (带有文档的指针)

 

 

 

 

 

[StatusBar]

其中,最外层的是顶层窗口CMainFrame,里面包含一个MDIClient窗口。CChildFrame做为子窗口包含于MDIClient中(可以包含多个),CChildFrame里面则是真实的文档表示窗口CMDITestView了。

 

我们从这里开始:

// CMDITestApp 初始化

BOOL CMDITestApp::InitInstance()

 

做为CWinApp的派生类,通常需要重载InitInstance(), ExitInstance()两个函数,以完成应用的初始化和退出。我们现在关心InitInstance中关于文档模板、窗口处理的部分,而忽略掉一些CommonControl, OLE初始化部分。

 

整个InitInstance代码如下:

BOOL CMDITestApp::InitInstance()

{

     InitCommonControls();       // 这里删减了大量注释和错误处理

     CWinApp::InitInstance();

     AfxOleInit();

     AfxEnableControlContainer();

     SetRegistryKey(_T("应用程序向导生成的本地应用程序"));

     LoadStdProfileSettings(4);  // 加载标准 INI 文件选项(包括 MRU)

 

     TRACE("Before CMultiDocTemplate\n");

     // 注册应用程序的文档模板。文档模板

     // 将用作文档、框架窗口和视图之间的连接

     CMultiDocTemplate* pDocTemplate;

     pDocTemplate = new CMultiDocTemplate(IDR_MDITestTYPE,

         RUNTIME_CLASS(CMDITestDoc),

         RUNTIME_CLASS(CChildFrame), // 自定义 MDI 子框架

         RUNTIME_CLASS(CMDITestView));

     if (!pDocTemplate)

         return FALSE;

     TRACE("Before AddDocTemplate\n");

     AddDocTemplate(pDocTemplate);

 

     // 创建主 MDI 框架窗口

     TRACE("Before new CMainFrame\n");

     CMainFrame* pMainFrame = new CMainFrame;

     TRACE("Before pMainFrame->LoadFrame\n");

     if (!pMainFrame || !pMainFrame->LoadFrame(IDR_MAINFRAME))

         return FALSE;

     m_pMainWnd = pMainFrame;

 

     TRACE("Before ParseCommandLine\n");

     CCommandLineInfo cmdInfo;

     ParseCommandLine(cmdInfo);

 

     // 调度在命令行中指定的命令。如果

     // 用 /RegServer、/Register、/Unregserver 或 /Unregister 启动应用程序,则返回 FALSE。

     TRACE("Before ProcessShellCommand\n");

     if (!ProcessShellCommand(cmdInfo))

         return FALSE;

 

     TRACE("Before pMainFrame->ShowWindow\n");

     // 主窗口已初始化,因此显示它并对其进行更新

     pMainFrame->ShowWindow(m_nCmdShow);

     TRACE("Before pMainFrame->UpdateWindow\n");

     pMainFrame->UpdateWindow();

     return TRUE;

}

 

为了研究整个创建过程,我在其中添加了一些TRACE来跟踪创建顺序。

 

忽略掉开始的乱七八糟的初始化,从CMultiDocTemplate开始:

     CMultiDocTemplate* pDocTemplate = new CMultiDocTemplate(IDR_MDITestTYPE,

         RUNTIME_CLASS(CMDITestDoc),

         RUNTIME_CLASS(CChildFrame), // 自定义 MDI 子框架

         RUNTIME_CLASS(CMDITestView));

     AddDocTemplate(pDocTemplate);

(作了一点点简化)

这里首先创建了一个CMultiDocTemplate —— 文档模板,文档模板包括的三个运行时刻类信息:Document – CMDITestDoc, FrameWnd – CChildFrame, View – CMDITestView。

然后通过AddDocTemplate函数将新创建的文档模板添加到模板管理器之中(我们以后再研究模板管理器)。

 

然后创建主框架窗口CMainFrame:

     CMainFrame* pMainFrame = new CMainFrame;

     if (!pMainFrame || !pMainFrame->LoadFrame(IDR_MAINFRAME))

         return FALSE;

 

其中,需要研究的是LoadFrame的实现,以及里面都做了些什么。我们稍后研究。

 

处理命令行,在这里第一个空文档被建立出来:

     CCommandLineInfo cmdInfo;

     ParseCommandLine(cmdInfo);

 

     // 调度在命令行中指定的命令。如果用 /RegServer、/Register、/Unregserver 或 /Unregister 启动应用程序,则返回 FALSE。

     if (!ProcessShellCommand(cmdInfo))               // ß 这里创建出初始空文档

         return FALSE;

 

我们一会会重点研究ProcessShellCommand。

 

最后,显示主窗口:

     pMainFrame->ShowWindow(m_nCmdShow);

     pMainFrame->UpdateWindow();

 

至此,WinApp::InitInstance()完成了自己的工作。

 

上面遗留了三个待研究的分支,让我们现在去研究它们:

1、  CDocTemplate

2、  CFrameWnd::LoadFrame

3、  CWnd::ProcessShellCommand

 

 

 

研究CDocTemplate

 

我们的例子中是构造了一个CMultiDocTemplate,它是从CDocTemplate派生而来,所以我们主要研究CDocTemplate。

CDocTemplate的几个关键属性列表如下:

     CRuntimeClass* m_pDocClass;         // class for creating new documents

     CRuntimeClass* m_pFrameClass;       // class for creating new frames

     CRuntimeClass* m_pViewClass;        // class for creating new views

 

其中:

m_pDocClass

表示文档类类型,在此例子中就是CMDITestDoc

m_pFrameClass

表示容纳View窗口的框架窗口类类型,此例中为CChildFrame

m_pViewClass

表示显示文档的View视类类型,此例中为CMDITestView

 

我们可以这样认为,CDocTemplate用于描述Frame-View-Doc的关系。当然它还有一大堆别的属性,我们暂时先忽略。

 

一会还会看到CDocTemplate的创建文档、框架、视的过程,放在ProcessShellCommand中研究。

 

 

研究LoadFrame

 

让我们继续研究CFrameWnd::LoadFrame是怎么运作的。使用的方法是跟踪进入。。。

BOOL CMDIFrameWnd::LoadFrame(UINT nIDResource, DWORD dwDefaultStyle,

     CWnd* pParentWnd, CCreateContext* pContext)

{

     // 调用基类 CFrameWnd 的 LoadFrame, pContext 在创建主窗口时 = NULL

     //   pParentWnd = NULL

     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;

}

注意,我们的MDITest Application的主窗口CMainFrame是从CMDIFrameWnd派生的,所以进入到这里,参考代码中红色的注释部分。继续跟踪进入CFrameWnd::LoadFrame。

 

BOOL CFrameWnd::LoadFrame(UINT nIDResource, DWORD dwDefaultStyle,

     CWnd* pParentWnd, CCreateContext* pContext)

{

     // only do this once

     ASSERT_VALID_IDR(nIDResource);    // nIDResource = 128, IDR_MAINFRAME

     ASSERT(m_nIDHelp == 0 || m_nIDHelp == nIDResource);

 

     m_nIDHelp = nIDResource;    // ID for help context (+HID_BASE_RESOURCE)

 

     CString strFullString;

     if (strFullString.LoadString(nIDResource))  // = “MDITest”

         AfxExtractSubString(m_strTitle, strFullString, 0);    // 取得第一个子串

 

     VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG));

 

     // attempt to create the window

     // GetIconWndClass 会调用 virtual PreCreateWindow 函数,别处也会调用,从而

     // 使得子类的PreCreateWindow 将被调用多次

     LPCTSTR lpszClass = GetIconWndClass(dwDefaultStyle, nIDResource);

     CString strTitle = m_strTitle;

     // 调用 CFrameWnd::Create() 实际创建出窗口。

     // 注意:在这里将给 CMainFrame 发送 WM_CREATE 等多个消息。触发 CMainFrame 的

     //   OnCreate 处理等。

     if (!Create(lpszClass, strTitle, dwDefaultStyle, rectDefault,

       pParentWnd, MAKEINTRESOURCE(nIDResource), 0L, pContext))

     {

         return FALSE;   // will self destruct on failure normally

     }

 

     // save the default menu handle, 好像 CMDIFrameWnd 也保存了一次?

     ASSERT(m_hWnd != NULL);

     m_hMenuDefault = ::GetMenu(m_hWnd);

 

     // load accelerator resource

     LoadAccelTable(MAKEINTRESOURCE(nIDResource));

 

     // WM_INITIALUPDATE 是 MFC 发明的消息,参见后面的说明。

     if (pContext == NULL)   // send initial update

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

 

     return TRUE;

}

 

以下是从TN024: MFC-Defined Messages And Resources中抽取的部分说明:

WM_INITIALUPDATE

This message is sent by the document template to all descendants of a frame window when it is safe for them to do their initial update. It maps to a call to CView::OnInitialUpdate but can be used in other CWnd-derived classes for other one-shot updating.

wParam

Not used (0)

lParam

Not used (0)

returns

Not used (0)

 

归纳一下,LoadFrame中进行了如下事情:

1、  注册窗口类(AfxDeferRegisterClass)

2、  实际创建窗口(Create)

3、  处理菜单、快捷键,发送WM_INITIALUPDATE消息给所有子窗口。实际将在CView中处理此消息。(例如:在ToolBar上面放一个FormView,可能就能收到这个消息并处利?)

 

至此,CMainFrame已经成功创建,菜单已经装载,工具条、状态行等已经在CMainFrame::OnCreate中创建。让我们接着研究第一个子窗口是怎么被创建出来的,该过程和CMainFrame::LoadFrame比起来就不那么直接了。

 

 

研究CWnd::ProcessShellCommand

 

第一个MDI子窗口是从这里面建立出来的,这实在是缺乏直观性。不过MFC就是这样,没办法。

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;

 

     case CCommandLineInfo::FileOpen:                // 忽略

     case CCommandLineInfo::FilePrintTo:            // 忽略

     case CCommandLineInfo::FilePrint:

     case CCommandLineInfo::FileDDE:

     case CCommandLineInfo::AppRegister:

     case CCommandLineInfo::AppUnregister:

     }

     return bResult;

}

进入到ProcessShellCommand,要处理很多种不同命令,我们忽略其它命令,单独看FileNew部分。

注意:实际进入到了AfxGetApp()->OnCmdMsg(ID_FILE_NEW, 0, NULL, NULL)之中。

 

AfxGetApp()实际返回了CMDITestApp的唯一实例,它从CWinApp – CWinThread – CCmdTarget – CObject 派生而来。我们没有重载OnCmdMsg,所以进入到CCmdTarget的OnCmdMsg处理中。为了研究,我们删减了一些代码。

BOOL CCmdTarget::OnCmdMsg(UINT nID, int nCode, void* pExtra,

     AFX_CMDHANDLERINFO* pHandlerInfo)

{

     // 这里删减了一些代码

     // determine the message number and code (packed into nCode)

     const AFX_MSGMAP* pMessageMap;

     const AFX_MSGMAP_ENTRY* lpEntry;

     UINT nMsg = 0;

     // 这里删减了一些代码,处理后 nMsg = WM_COMMAND

     // 为了简化,删减了一些断言等。以下循环用于查找处理此消息的入口。

     for (pMessageMap = GetMessageMap(); pMessageMap->pfnGetBaseMap != NULL;

       pMessageMap = (*pMessageMap->pfnGetBaseMap)())

     {

         lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries, nMsg, nCode, nID);

         if (lpEntry != NULL)

         {

              // 找到了消息处理项入口,分发此消息。

              return _AfxDispatchCmdMsg(this, nID, nCode,

                   lpEntry->pfn, pExtra, lpEntry->nSig, pHandlerInfo);

         }

     }

     return FALSE;   // 未找到则不处理

}

最终MFC很愉快地找到了一个入口项,       CWinApp::OnFileNew(void)       要处理这个消息。继续进入到_AfxDispatchCmdMsg中去看看。

 

AFX_STATIC BOOL AFXAPI _AfxDispatchCmdMsg(CCmdTarget* pTarget, UINT nID, int nCode,

     AFX_PMSG pfn, void* pExtra, UINT_PTR nSig, AFX_CMDHANDLERINFO* pHandlerInfo)

         // return TRUE to stop routing

{

     union MessageMapFunctions mmf;

     mmf.pfn = pfn;

     BOOL bResult = TRUE; // default is ok

 

     if (pHandlerInfo != NULL)

     {

         // just fill in the information, don't do it

         pHandlerInfo->pTarget = pTarget;

         pHandlerInfo->pmf = mmf.pfn;

         return TRUE;

     }

 

     switch (nSig)

     {

     case AfxSigCmd_v:

         // normal command or control notification

         ASSERT(CN_COMMAND == 0);        // CN_COMMAND same as BN_CLICKED

         ASSERT(pExtra == NULL);

         (pTarget->*mmf.pfnCmd_v_v)();         // ß 实际调用 pTarget 指向的这个成员函数

         break;

     // 下面还有大量的多种 AfxSigCmd_xxx,忽略掉它们。

     default:    // illegal

         ASSERT(FALSE); return 0; break;

     }

     return bResult;

}

 

其中 (pTarget->*mmf.pfn_Cmd_v_v)() 对CWinApp::OnFileNew() 产生调用,pTarget = CMDITestApp类实例。调用进入如下:

 

void CWinApp::OnFileNew()

{

     if (m_pDocManager != NULL)

         m_pDocManager->OnFileNew();

}

 

进入进入到CDocManager::OnFileNew()

 

void CDocManager::OnFileNew()

{

     if (m_templateList.IsEmpty())

          // 提示没有模板并返回

     CDocTemplate* pTemplate = (CDocTemplate*)m_templateList.GetHead();    // 第一个

     if (m_templateList.GetCount() > 1)

          // 弹出一个对话框(很难看的)提示用户选择一个文档模板

 

     // 在这个例子里面,pTemplate 就是 CMDITestApp::InitInstance() 里面创建的那个模板

     pTemplate->OpenDocumentFile(NULL);

}

 

在进入CMultiDocTemplate::OpenDocumentFile之前,我观察了一下调用堆栈,结果如下:

>   mfc71d.dll!CDocManager::OnFileNew()  行852  C++

    mfc71d.dll!CWinApp::OnFileNew()  行25   C++

    mfc71d.dll!_AfxDispatchCmdMsg(CCmdTarget * pTarget=0x0042cae8, unsigned int nID=57600, int nCode=0, void (void)* pfn=0x0041153c, void * pExtra=0x00000000, unsigned int nSig=53, AFX_CMDHANDLERINFO * pHandlerInfo=0x00000000)  行89   C++

    mfc71d.dll!CCmdTarget::OnCmdMsg(unsigned int nID=57600, int nCode=0, void * pExtra=0x00000000, AFX_CMDHANDLERINFO * pHandlerInfo=0x00000000)  行396 + 0x27    C++

    mfc71d.dll!CWinApp::ProcessShellCommand(CCommandLineInfo & rCmdInfo={...})  行27 + 0x1e C++

    MDITest.exe!CMDITestApp::InitInstance()  行101 + 0xc    C++

希望我还没有迷路:)

 

 

CMultiDocTemplate::OpenDocumentFile 又是很多很多代码,让我们选择一些。

CDocument* CMultiDocTemplate::OpenDocumentFile(LPCTSTR lpszPathName,

     BOOL bMakeVisible)

{

     // 以下代码删减了验证、断言部分

     CDocument* pDocument = CreateNewDocument();              // 创建文档对象

     CFrameWnd* pFrame = CreateNewFrame(pDocument, NULL);    // 创建框架窗口

 

     if (lpszPathName == NULL)

     {

         pDocument->OnNewDocument();           // 初始化文档

     }

     else

          // 打开已有文档

 

     InitialUpdateFrame(pFrame, pDocument, bMakeVisible);

     return pDocument;

}

 

 

看一看CreateNewDocument()

CDocument* CDocTemplate::CreateNewDocument()

{

     // default implementation constructs one from CRuntimeClass

     if (m_pDocClass == NULL)

          // 错误提示啦

     // CRuntimeClass* m_pDocClass -> CreateObject 实例化文档类。

     // 在此例子中既是 CMDITestDoc

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

     AddDocument(pDocument);      // 添加到模板里的文档列表,MultiDocTemplate 保存此一文档

     return pDocument;

}

 

 

CMDITestDoc有如下的定义,仅能从CRuntimeClass里面创建的。

class CMDITestDoc : public CDocument

{

protected: // 仅从序列化创建

     CMDITestDoc();               // 被保护的构造函数

     DECLARE_DYNCREATE(CMDITestDoc)             // 支持从 CRuntimeClass 信息中创建。

 

 

再接着进行CreateNewFrame。

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

{

     // create a frame wired to the specified document

     CCreateContext context;           // 这个 CreateContext 传递到 LoadFrame 中

     context.m_pCurrentFrame = pOther;         // 此例中 = NULL

     context.m_pCurrentDoc = pDoc;              // = 刚才创建的文档

     context.m_pNewViewClass = m_pViewClass;   // 显示此文档的视类的类型

     context.m_pNewDocTemplate = this;

 

     if (m_pFrameClass == NULL)

          // 提示错误并返回

     // 利用 CRuntimeClass 信息创建框架窗口对象,此例中为 CChildFrame

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

 

     // 这里,我们又看到了 LoadFrame , 参考前面的 LoadFrame 吧

     // 在这里面,View 窗口也被产生出来。参考 TRACE 输出。

     pFrame->LoadFrame(m_nIDResource,

              WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE,   // default frame styles

              NULL, &context);

     return pFrame;

}

 

 

LoadFrame之后View窗口将被创建出来,接着进入到CMDITestDoc::OnNewDocument中,现在仅仅是一个空的函数,没有特定代码。

BOOL CMDITestDoc::OnNewDocument()

{

   TRACE("CMDITestDoc::OnNewDocument() entry\n");

     if (!CDocument::OnNewDocument())

         return FALSE;

 

     // TODO: 在此添加重新初始化代码

     // (SDI 文档将重用该文档)

 

     return TRUE;

}

 

最后是CDocTemplate::InitialUpdateFrame,这里面主要是激活新建的框架、文档、视,看得挺头疼的。

void CDocTemplate::InitialUpdateFrame(CFrameWnd* pFrame, CDocument* pDoc,

     BOOL bMakeVisible)

{

     // just delagate to implementation in CFrameWnd

     pFrame->InitialUpdateFrame(pDoc, bMakeVisible);

}

 

现在,文档、框架窗口、视窗口全部被创建出来,我们胜利的返回到ProcessShellCommand处。显示和更新主窗口,完成了WinApp::InitInstance :

     // 主窗口已初始化,因此显示它并对其进行更新

     pMainFrame->ShowWindow(m_nCmdShow);

     pMainFrame->UpdateWindow();

 

 

 

看一下至此的TRACE输出,中间的DLL加载被去掉了:

Before CMultiDocTemplate

Before AddDocTemplate

Before new CMainFrame

CMainFrame::CMainFrame()

Before pMainFrame->LoadFrame

CMainFrame::PreCreateWindow entry         // 注意:PreCreateWindow 被两次调用

CMainFrame::PreCreateWindow entry

CMainFrame::OnCreate entry before CMDIFrameWnd::OnCreate

CMainFrame::OnCreate before m_wndToolBar.CreateEx

CMainFrame::OnCreate before m_wndStatusBar.Create

Before ParseCommandLine

Before ProcessShellCommand

CMDITestDoc::CMDITestDoc()       // 文档对象被创建

CChildFrame::CChildFrame()       // 子框架窗口被创建

CChildFrame::PreCreateWindow entry

CChildFrame::PreCreateWindow entry

CChildFrame::PreCreateWindow entry

CMDITestView::CMDITestView() entry   // 子框架窗口的 OnCreate 中创建了 View 窗口

CMDITestView::PreCreateWindow entry

CMDITestDoc::OnNewDocument() entry

Before pMainFrame->ShowWindow

Before pMainFrame->UpdateWindow

 

// 退出时的 TRACE

CMDITestView::~CMDITestView()

CChildFrame::~CChildFrame()

CMDITestDoc::~CMDITestDoc()

CMainFrame::~CMainFrame()

 

  热门文章
  推荐信息
  • [商业源码]湖南某软件公司商业POS通系统(Delphi)
  • [商业源码]某医院HIS管理系统(Delphi)
  • [商业源码]惠特ERP管理系统(VB)
  • [商业源码]某指纹考勤管理系统(Delphi)
  • [商业源码]某市公路运输管理系统(.NET)
  • [商业源码]某大型商业连锁系统(PB)
  • [商业源码]某网络营销软件(VB)
  • [商业源码]新中大财务管理系统(PB)
  • [商业源码]SUNSHINE医院管理系统(PB)
  • [商业源码]大型WEB进销存系统(ASP)
  • [商业源码]某企业大型ERP系统(VB)
  • [商业源码]大型企业内部管理系统(VB)
  • [商业源码]伯乐人力资源管理系统2000(VB)
  • [商业源码]邮件管理与群发系统(VB)
  • [商业源码]报表设计系统2000(VB)

    温故而知新,学习MFC框架如何创建的过程

    发表: 不详   阅读:次  关键字:   字体:[ ]

    很久没有使用MFC了,以至于都忘记MFC框架复杂的窗口、文档、视的创建过程了。

    下面我们跟踪一个MFC MDI的应用程序,来温习或学习一下。

     

    使用AppWizard创建一个MDI应用程序,我创建的应用程序叫MDITest,这样MFC生成了如下的类:

    类名

    作用

    CMDITestApp

    派生于CWinApp的应用程序类。

    CMainFrame

    派生于CMDIFrameWnd的MDI框架窗口类。

    CMDITestDoc

    派生于CDocument的文档类。

    CChildFrame

    派生于CMDIChildWnd的MDI子窗口类。

    CMDITestView

    派生于CView的文档显示类。

    在运行时刻,CMainFrame, CChildFrame, CMDITestView的窗口关系如下面的表格示出:

    CMainFrame

    (Menu, Toolbar …)

    MDIClient

     

    CChildFrame

    CMDITestView

       pDocument = *CMDITestDoc   (带有文档的指针)

     

     

     

     

     

    [StatusBar]

    其中,最外层的是顶层窗口CMainFrame,里面包含一个MDIClient窗口。CChildFrame做为子窗口包含于MDIClient中(可以包含多个),CChildFrame里面则是真实的文档表示窗口CMDITestView了。

     

    我们从这里开始:

    // CMDITestApp 初始化

    BOOL CMDITestApp::InitInstance()

     

    做为CWinApp的派生类,通常需要重载InitInstance(), ExitInstance()两个函数,以完成应用的初始化和退出。我们现在关心InitInstance中关于文档模板、窗口处理的部分,而忽略掉一些CommonControl, OLE初始化部分。

     

    整个InitInstance代码如下:

    BOOL CMDITestApp::InitInstance()

    {

         InitCommonControls();       // 这里删减了大量注释和错误处理

         CWinApp::InitInstance();

         AfxOleInit();

         AfxEnableControlContainer();

         SetRegistryKey(_T("应用程序向导生成的本地应用程序"));

         LoadStdProfileSettings(4);  // 加载标准 INI 文件选项(包括 MRU)

     

         TRACE("Before CMultiDocTemplate\n");

         // 注册应用程序的文档模板。文档模板

         // 将用作文档、框架窗口和视图之间的连接

         CMultiDocTemplate* pDocTemplate;

         pDocTemplate = new CMultiDocTemplate(IDR_MDITestTYPE,

             RUNTIME_CLASS(CMDITestDoc),

             RUNTIME_CLASS(CChildFrame), // 自定义 MDI 子框架

             RUNTIME_CLASS(CMDITestView));

         if (!pDocTemplate)

             return FALSE;

         TRACE("Before AddDocTemplate\n");

         AddDocTemplate(pDocTemplate);

     

         // 创建主 MDI 框架窗口

         TRACE("Before new CMainFrame\n");

         CMainFrame* pMainFrame = new CMainFrame;

         TRACE("Before pMainFrame->LoadFrame\n");

         if (!pMainFrame || !pMainFrame->LoadFrame(IDR_MAINFRAME))

             return FALSE;

         m_pMainWnd = pMainFrame;

     

         TRACE("Before ParseCommandLine\n");

         CCommandLineInfo cmdInfo;

         ParseCommandLine(cmdInfo);

     

         // 调度在命令行中指定的命令。如果

         // 用 /RegServer、/Register、/Unregserver 或 /Unregister 启动应用程序,则返回 FALSE。

         TRACE("Before ProcessShellCommand\n");

         if (!ProcessShellCommand(cmdInfo))

             return FALSE;

     

         TRACE("Before pMainFrame->ShowWindow\n");

         // 主窗口已初始化,因此显示它并对其进行更新

         pMainFrame->ShowWindow(m_nCmdShow);

         TRACE("Before pMainFrame->UpdateWindow\n");

         pMainFrame->UpdateWindow();

         return TRUE;

    }

     

    为了研究整个创建过程,我在其中添加了一些TRACE来跟踪创建顺序。

     

    忽略掉开始的乱七八糟的初始化,从CMultiDocTemplate开始:

         CMultiDocTemplate* pDocTemplate = new CMultiDocTemplate(IDR_MDITestTYPE,

             RUNTIME_CLASS(CMDITestDoc),

             RUNTIME_CLASS(CChildFrame), // 自定义 MDI 子框架

             RUNTIME_CLASS(CMDITestView));

         AddDocTemplate(pDocTemplate);

    (作了一点点简化)

    这里首先创建了一个CMultiDocTemplate —— 文档模板,文档模板包括的三个运行时刻类信息:Document – CMDITestDoc, FrameWnd – CChildFrame, View – CMDITestView。

    然后通过AddDocTemplate函数将新创建的文档模板添加到模板管理器之中(我们以后再研究模板管理器)。

     

    然后创建主框架窗口CMainFrame:

         CMainFrame* pMainFrame = new CMainFrame;

         if (!pMainFrame || !pMainFrame->LoadFrame(IDR_MAINFRAME))

             return FALSE;

     

    其中,需要研究的是LoadFrame的实现,以及里面都做了些什么。我们稍后研究。

     

    处理命令行,在这里第一个空文档被建立出来:

         CCommandLineInfo cmdInfo;

         ParseCommandLine(cmdInfo);

     

         // 调度在命令行中指定的命令。如果用 /RegServer、/Register、/Unregserver 或 /Unregister 启动应用程序,则返回 FALSE。

         if (!ProcessShellCommand(cmdInfo))               // ß 这里创建出初始空文档

             return FALSE;

     

    我们一会会重点研究ProcessShellCommand。

     

    最后,显示主窗口:

         pMainFrame->ShowWindow(m_nCmdShow);

         pMainFrame->UpdateWindow();

     

    至此,WinApp::InitInstance()完成了自己的工作。

     

    上面遗留了三个待研究的分支,让我们现在去研究它们:

    1、  CDocTemplate

    2、  CFrameWnd::LoadFrame

    3、  CWnd::ProcessShellCommand

     

     

     

    研究CDocTemplate

     

    我们的例子中是构造了一个CMultiDocTemplate,它是从CDocTemplate派生而来,所以我们主要研究CDocTemplate。

    CDocTemplate的几个关键属性列表如下:

         CRuntimeClass* m_pDocClass;         // class for creating new documents

         CRuntimeClass* m_pFrameClass;       // class for creating new frames

         CRuntimeClass* m_pViewClass;        // class for creating new views

     

    其中:

    m_pDocClass

    表示文档类类型,在此例子中就是CMDITestDoc

    m_pFrameClass

    表示容纳View窗口的框架窗口类类型,此例中为CChildFrame

    m_pViewClass

    表示显示文档的View视类类型,此例中为CMDITestView

     

    我们可以这样认为,CDocTemplate用于描述Frame-View-Doc的关系。当然它还有一大堆别的属性,我们暂时先忽略。

     

    一会还会看到CDocTemplate的创建文档、框架、视的过程,放在ProcessShellCommand中研究。

     

     

    研究LoadFrame

     

    让我们继续研究CFrameWnd::LoadFrame是怎么运作的。使用的方法是跟踪进入。。。

    BOOL CMDIFrameWnd::LoadFrame(UINT nIDResource, DWORD dwDefaultStyle,

         CWnd* pParentWnd, CCreateContext* pContext)

    {

         // 调用基类 CFrameWnd 的 LoadFrame, pContext 在创建主窗口时 = NULL

         //   pParentWnd = NULL

         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;

    }

    注意,我们的MDITest Application的主窗口CMainFrame是从CMDIFrameWnd派生的,所以进入到这里,参考代码中红色的注释部分。继续跟踪进入CFrameWnd::LoadFrame。

     

    BOOL CFrameWnd::LoadFrame(UINT nIDResource, DWORD dwDefaultStyle,

         CWnd* pParentWnd, CCreateContext* pContext)

    {

         // only do this once

         ASSERT_VALID_IDR(nIDResource);    // nIDResource = 128, IDR_MAINFRAME

         ASSERT(m_nIDHelp == 0 || m_nIDHelp == nIDResource);

     

         m_nIDHelp = nIDResource;    // ID for help context (+HID_BASE_RESOURCE)

     

         CString strFullString;

         if (strFullString.LoadString(nIDResource))  // = “MDITest”

             AfxExtractSubString(m_strTitle, strFullString, 0);    // 取得第一个子串

     

         VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG));

     

         // attempt to create the window

         // GetIconWndClass 会调用 virtual PreCreateWindow 函数,别处也会调用,从而

         // 使得子类的PreCreateWindow 将被调用多次

         LPCTSTR lpszClass = GetIconWndClass(dwDefaultStyle, nIDResource);

         CString strTitle = m_strTitle;

         // 调用 CFrameWnd::Create() 实际创建出窗口。

         // 注意:在这里将给 CMainFrame 发送 WM_CREATE 等多个消息。触发 CMainFrame 的

         //   OnCreate 处理等。

         if (!Create(lpszClass, strTitle, dwDefaultStyle, rectDefault,

           pParentWnd, MAKEINTRESOURCE(nIDResource), 0L, pContext))

         {

             return FALSE;   // will self destruct on failure normally

         }

     

         // save the default menu handle, 好像 CMDIFrameWnd 也保存了一次?

         ASSERT(m_hWnd != NULL);

         m_hMenuDefault = ::GetMenu(m_hWnd);

     

         // load accelerator resource

         LoadAccelTable(MAKEINTRESOURCE(nIDResource));

     

         // WM_INITIALUPDATE 是 MFC 发明的消息,参见后面的说明。

         if (pContext == NULL)   // send initial update

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

     

         return TRUE;

    }

     

    以下是从TN024: MFC-Defined Messages And Resources中抽取的部分说明:

    WM_INITIALUPDATE

    This message is sent by the document template to all descendants of a frame window when it is safe for them to do their initial update. It maps to a call to CView::OnInitialUpdate but can be used in other CWnd-derived classes for other one-shot updating.

    wParam

    Not used (0)

    lParam

    Not used (0)

    returns

    Not used (0)

     

    归纳一下,LoadFrame中进行了如下事情:

    1、  注册窗口类(AfxDeferRegisterClass)

    2、  实际创建窗口(Create)

    3、  处理菜单、快捷键,发送WM_INITIALUPDATE消息给所有子窗口。实际将在CView中处理此消息。(例如:在ToolBar上面放一个FormView,可能就能收到这个消息并处利?)

     

    至此,CMainFrame已经成功创建,菜单已经装载,工具条、状态行等已经在CMainFrame::OnCreate中创建。让我们接着研究第一个子窗口是怎么被创建出来的,该过程和CMainFrame::LoadFrame比起来就不那么直接了。

     

     

    研究CWnd::ProcessShellCommand

     

    第一个MDI子窗口是从这里面建立出来的,这实在是缺乏直观性。不过MFC就是这样,没办法。

    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;

     

         case CCommandLineInfo::FileOpen:                // 忽略

         case CCommandLineInfo::FilePrintTo:            // 忽略

         case CCommandLineInfo::FilePrint:

         case CCommandLineInfo::FileDDE:

         case CCommandLineInfo::AppRegister:

         case CCommandLineInfo::AppUnregister:

         }

         return bResult;

    }

    进入到ProcessShellCommand,要处理很多种不同命令,我们忽略其它命令,单独看FileNew部分。

    注意:实际进入到了AfxGetApp()->OnCmdMsg(ID_FILE_NEW, 0, NULL, NULL)之中。

     

    AfxGetApp()实际返回了CMDITestApp的唯一实例,它从CWinApp – CWinThread – CCmdTarget – CObject 派生而来。我们没有重载OnCmdMsg,所以进入到CCmdTarget的OnCmdMsg处理中。为了研究,我们删减了一些代码。

    BOOL CCmdTarget::OnCmdMsg(UINT nID, int nCode, void* pExtra,

         AFX_CMDHANDLERINFO* pHandlerInfo)

    {

         // 这里删减了一些代码

         // determine the message number and code (packed into nCode)

         const AFX_MSGMAP* pMessageMap;

         const AFX_MSGMAP_ENTRY* lpEntry;

         UINT nMsg = 0;

         // 这里删减了一些代码,处理后 nMsg = WM_COMMAND

         // 为了简化,删减了一些断言等。以下循环用于查找处理此消息的入口。

         for (pMessageMap = GetMessageMap(); pMessageMap->pfnGetBaseMap != NULL;

           pMessageMap = (*pMessageMap->pfnGetBaseMap)())

         {

             lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries, nMsg, nCode, nID);

             if (lpEntry != NULL)

             {

                  // 找到了消息处理项入口,分发此消息。

                  return _AfxDispatchCmdMsg(this, nID, nCode,

                       lpEntry->pfn, pExtra, lpEntry->nSig, pHandlerInfo);

             }

         }

         return FALSE;   // 未找到则不处理

    }

    最终MFC很愉快地找到了一个入口项,       CWinApp::OnFileNew(void)       要处理这个消息。继续进入到_AfxDispatchCmdMsg中去看看。

     

    AFX_STATIC BOOL AFXAPI _AfxDispatchCmdMsg(CCmdTarget* pTarget, UINT nID, int nCode,

         AFX_PMSG pfn, void* pExtra, UINT_PTR nSig, AFX_CMDHANDLERINFO* pHandlerInfo)

             // return TRUE to stop routing

    {

         union MessageMapFunctions mmf;

         mmf.pfn = pfn;

         BOOL bResult = TRUE; // default is ok

     

         if (pHandlerInfo != NULL)

         {

             // just fill in the information, don't do it

             pHandlerInfo->pTarget = pTarget;

             pHandlerInfo->pmf = mmf.pfn;

             return TRUE;

         }

     

         switch (nSig)

         {

         case AfxSigCmd_v:

             // normal command or control notification

             ASSERT(CN_COMMAND == 0);        // CN_COMMAND same as BN_CLICKED

             ASSERT(pExtra == NULL);

             (pTarget->*mmf.pfnCmd_v_v)();         // ß 实际调用 pTarget 指向的这个成员函数

             break;

         // 下面还有大量的多种 AfxSigCmd_xxx,忽略掉它们。

         default:    // illegal

             ASSERT(FALSE); return 0; break;

         }

         return bResult;

    }

     

    其中 (pTarget->*mmf.pfn_Cmd_v_v)() 对CWinApp::OnFileNew() 产生调用,pTarget = CMDITestApp类实例。调用进入如下:

     

    void CWinApp::OnFileNew()

    {

         if (m_pDocManager != NULL)

             m_pDocManager->OnFileNew();

    }

     

    进入进入到CDocManager::OnFileNew()

     

    void CDocManager::OnFileNew()

    {

         if (m_templateList.IsEmpty())

              // 提示没有模板并返回

         CDocTemplate* pTemplate = (CDocTemplate*)m_templateList.GetHead();    // 第一个

         if (m_templateList.GetCount() > 1)

              // 弹出一个对话框(很难看的)提示用户选择一个文档模板

     

         // 在这个例子里面,pTemplate 就是 CMDITestApp::InitInstance() 里面创建的那个模板

         pTemplate->OpenDocumentFile(NULL);

    }

     

    在进入CMultiDocTemplate::OpenDocumentFile之前,我观察了一下调用堆栈,结果如下:

    >   mfc71d.dll!CDocManager::OnFileNew()  行852  C++

        mfc71d.dll!CWinApp::OnFileNew()  行25   C++

        mfc71d.dll!_AfxDispatchCmdMsg(CCmdTarget * pTarget=0x0042cae8, unsigned int nID=57600, int nCode=0, void (void)* pfn=0x0041153c, void * pExtra=0x00000000, unsigned int nSig=53, AFX_CMDHANDLERINFO * pHandlerInfo=0x00000000)  行89   C++

        mfc71d.dll!CCmdTarget::OnCmdMsg(unsigned int nID=57600, int nCode=0, void * pExtra=0x00000000, AFX_CMDHANDLERINFO * pHandlerInfo=0x00000000)  行396 + 0x27    C++

        mfc71d.dll!CWinApp::ProcessShellCommand(CCommandLineInfo & rCmdInfo={...})  行27 + 0x1e C++

        MDITest.exe!CMDITestApp::InitInstance()  行101 + 0xc    C++

    希望我还没有迷路:)

     

     

    CMultiDocTemplate::OpenDocumentFile 又是很多很多代码,让我们选择一些。

    CDocument* CMultiDocTemplate::OpenDocumentFile(LPCTSTR lpszPathName,

         BOOL bMakeVisible)

    {

         // 以下代码删减了验证、断言部分

         CDocument* pDocument = CreateNewDocument();              // 创建文档对象

         CFrameWnd* pFrame = CreateNewFrame(pDocument, NULL);    // 创建框架窗口

     

         if (lpszPathName == NULL)

         {

             pDocument->OnNewDocument();           // 初始化文档

         }

         else

              // 打开已有文档

     

         InitialUpdateFrame(pFrame, pDocument, bMakeVisible);

         return pDocument;

    }

     

     

    看一看CreateNewDocument()

    CDocument* CDocTemplate::CreateNewDocument()

    {

         // default implementation constructs one from CRuntimeClass

         if (m_pDocClass == NULL)

              // 错误提示啦

         // CRuntimeClass* m_pDocClass -> CreateObject 实例化文档类。

         // 在此例子中既是 CMDITestDoc

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

         AddDocument(pDocument);      // 添加到模板里的文档列表,MultiDocTemplate 保存此一文档

         return pDocument;

    }

     

     

    CMDITestDoc有如下的定义,仅能从CRuntimeClass里面创建的。

    class CMDITestDoc : public CDocument

    {

    protected: // 仅从序列化创建

         CMDITestDoc();               // 被保护的构造函数

         DECLARE_DYNCREATE(CMDITestDoc)             // 支持从 CRuntimeClass 信息中创建。

     

     

    再接着进行CreateNewFrame。

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

    {

         // create a frame wired to the specified document

         CCreateContext context;           // 这个 CreateContext 传递到 LoadFrame 中

         context.m_pCurrentFrame = pOther;         // 此例中 = NULL

         context.m_pCurrentDoc = pDoc;              // = 刚才创建的文档

         context.m_pNewViewClass = m_pViewClass;   // 显示此文档的视类的类型

         context.m_pNewDocTemplate = this;

     

         if (m_pFrameClass == NULL)

              // 提示错误并返回

         // 利用 CRuntimeClass 信息创建框架窗口对象,此例中为 CChildFrame

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

     

         // 这里,我们又看到了 LoadFrame , 参考前面的 LoadFrame 吧

         // 在这里面,View 窗口也被产生出来。参考 TRACE 输出。

         pFrame->LoadFrame(m_nIDResource,

                  WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE,   // default frame styles

                  NULL, &context);

         return pFrame;

    }

     

     

    LoadFrame之后View窗口将被创建出来,接着进入到CMDITestDoc::OnNewDocument中,现在仅仅是一个空的函数,没有特定代码。

    BOOL CMDITestDoc::OnNewDocument()

    {

       TRACE("CMDITestDoc::OnNewDocument() entry\n");

         if (!CDocument::OnNewDocument())

             return FALSE;

     

         // TODO: 在此添加重新初始化代码

         // (SDI 文档将重用该文档)

     

         return TRUE;

    }

     

    最后是CDocTemplate::InitialUpdateFrame,这里面主要是激活新建的框架、文档、视,看得挺头疼的。

    void CDocTemplate::InitialUpdateFrame(CFrameWnd* pFrame, CDocument* pDoc,

         BOOL bMakeVisible)

    {

         // just delagate to implementation in CFrameWnd

         pFrame->InitialUpdateFrame(pDoc, bMakeVisible);

    }

     

    现在,文档、框架窗口、视窗口全部被创建出来,我们胜利的返回到ProcessShellCommand处。显示和更新主窗口,完成了WinApp::InitInstance :

         // 主窗口已初始化,因此显示它并对其进行更新

         pMainFrame->ShowWindow(m_nCmdShow);

         pMainFrame->UpdateWindow();

     

     

     

    看一下至此的TRACE输出,中间的DLL加载被去掉了:

    Before CMultiDocTemplate

    Before AddDocTemplate

    Before new CMainFrame

    CMainFrame::CMainFrame()

    Before pMainFrame->LoadFrame

    CMainFrame::PreCreateWindow entry         // 注意:PreCreateWindow 被两次调用

    CMainFrame::PreCreateWindow entry

    CMainFrame::OnCreate entry before CMDIFrameWnd::OnCreate

    CMainFrame::OnCreate before m_wndToolBar.CreateEx

    CMainFrame::OnCreate before m_wndStatusBar.Create

    Before ParseCommandLine

    Before ProcessShellCommand

    CMDITestDoc::CMDITestDoc()       // 文档对象被创建

    CChildFrame::CChildFrame()       // 子框架窗口被创建

    CChildFrame::PreCreateWindow entry

    CChildFrame::PreCreateWindow entry

    CChildFrame::PreCreateWindow entry

    CMDITestView::CMDITestView() entry   // 子框架窗口的 OnCreate 中创建了 View 窗口

    CMDITestView::PreCreateWindow entry

    CMDITestDoc::OnNewDocument() entry

    Before pMainFrame->ShowWindow

    Before pMainFrame->UpdateWindow

     

    // 退出时的 TRACE

    CMDITestView::~CMDITestView()

    CChildFrame::~CChildFrame()

    CMDITestDoc::~CMDITestDoc()

    CMainFrame::~CMainFrame()

     

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值