Chinaunix首页 | 论坛 | 博客
  • 博客访问: 640204
  • 博文数量: 263
  • 博客积分: 6000
  • 博客等级: 准将
  • 技术积分: 2555
  • 用 户 组: 普通用户
  • 注册时间: 2008-02-26 11:20
文章分类

全部博文(263)

文章存档

2011年(10)

2010年(19)

2009年(170)

2008年(64)

我的朋友

分类: WINDOWS

2009-10-27 22:34:10

很久没有使用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比起来就不那么直接了。 
阅读(827) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~