剖析MFC的文档视图结构

////////////////////////////////////////////////////////////////////////////////////

/********* 文章系列: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.NETvc7atlmfcsrcmfcdoctempl.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.NETvc7atlmfcsrcmfcdocmulti.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.NETvc7atlmfcincludeafx.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.NETvc7atlmfcsrcmfcappui2.cpp

void CWinApp::AddDocTemplate(CDocTemplate* pTemplate)//将CMultiDocTemplate* pDocTemplate

{ //传给pTemplate

if (m_pDocManager == NULL)

m_pDocManager = new CDocManager;

m_pDocManager->AddDocTemplate(pTemplate);

}

注释2: 以下函数定义在:..Visual Studio.NETvc7atlmfcsrcmfcdocmgr.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.NETvc7atlmfcsrcmfcwinmdi.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.
");

return TRUE;

}

CMDIFrameWnd::LoadFrame调用了其基类CFrameWnd的LoadFrame,并将参数原封不动的传给它。

注释2: 以下函数定义在:..Visual Studio.NETvc7atlmfcsrcmfcwinfrm.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.NETvc7atlmfcsrcmfcafximpl.h

#define AfxDeferRegisterClass(fClass) AfxEndDeferRegisterClass(fClass)



注释2: 以下函数定义在:..Visual Studio.NETvc7atlmfcsrcmfcwincore.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.NETvc7atlmfcsrcmfcwinfrm.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.NETvc7atlmfcsrcmfcwinfrm.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.
");

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.
");

if (hMenu != NULL)

DestroyMenu(hMenu);

return FALSE;

}

return TRUE;

}

简单地说CFrameWnd::Create函数调用了基类的CWnd::CreateEx;

注释2: 以下函数定义在:..Visual Studio.NETvc7atlmfcsrcmfcwincore.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.
");

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.
");

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.
",

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.
");

// 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.
");

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.
",

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.
");

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“三口组”,其生成过程与我上述讲的一般不二。
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章