MFC文档视图结构内幕
更新时间:2024-04-14 04:40:02 阅读量: 综合文库 文档下载
<
////////////////////////////////////////////////////////////////////////////////////
/********* 文章系列:MFC技术内幕系列***********/ /************MFC技术内幕系列之(二)***********/
/**** 文章题目:MFC文档视图结构内幕 *****/ /* Copyright(c)2002 bigwhite */ /* All rights Reserved */ /*********关键字:MFC,文档视图结构************/ /* 时间:2002.7.23 */ /* 注释:本文所涉及的程序源代码均在Microsoft */
/ Visual Studio.Net Enterprise Architect Edition / /* 开发工具包提供的源代码中 */
//////////////////////////////////////////////////////////////////////////////////////////////////////////
引言:侯捷老师的\深入浅出MFC\一书的第8章中有“\MFC的基石。”一说,可以看出文档视图结构在MFC Framework中的地位是多么的重要。本文将以一个标准MFC应用程序向导作成的MDI程序为例,来和大家一起详细挖掘文档视图结构的内幕。
正文:
///////////////////////////////////////////// /* 1.回顾\函数\
///////////////////////////////////////////// 在我的《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, \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预定义了若干个“窗口类模板”,比如
\等,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, \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, \ 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, \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, \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, \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, \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, \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, \
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, \ 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执行期类型识别与动态创建技术内幕》
正在阅读:
MFC文档视图结构内幕04-14
基于偏微分方程的图像处理方法课程教学大纲03-13
外汇交易 - 交易系统研修 经验总结04-14
让顾客主动说买的五个营销技巧08-07
四川巴塘县巴久河水电站监理部 水工隧洞施工监理细则06-06
建筑业安全员C证机考题库之多10-26
国标病媒生物密度监测方法鼠类05-05
《纸船和风筝》教学设计人教版05-31
- 多层物业服务方案
- (审判实务)习惯法与少数民族地区民间纠纷解决问题(孙 潋)
- 人教版新课标六年级下册语文全册教案
- 词语打卡
- photoshop实习报告
- 钢结构设计原理综合测试2
- 2014年期末练习题
- 高中数学中的逆向思维解题方法探讨
- 名师原创 全国通用2014-2015学年高二寒假作业 政治(一)Word版
- 北航《建筑结构检测鉴定与加固》在线作业三
- XX县卫生监督所工程建设项目可行性研究报告
- 小学四年级观察作文经典评语
- 浅谈110KV变电站电气一次设计-程泉焱(1)
- 安全员考试题库
- 国家电网公司变电运维管理规定(试行)
- 义务教育课程标准稿征求意见提纲
- 教学秘书面试技巧
- 钢结构工程施工组织设计
- 水利工程概论论文
- 09届九年级数学第四次模拟试卷
- 视图
- 内幕
- 结构
- 文档
- MFC
- BSC指标库
- 食堂异常事件联络投诉程序
- Common risk factors in the returns on stocks and bonds-Fama&
- 菲律宾金融概况
- 土壤固化稳定化技术路线
- 后勤保障
- 2018年高考地理原创押题预测卷(答案版) - 图文
- 心内科知识300问答
- 中国公募基金现状分析
- 老教师对青年教师进行传帮带的工作总结
- 范文:浅析会计电算化的内部控制制度(姓名 学号)
- 排查落实练六 硫、氮及其化合物一、硫及其化合物
- 小河、丰岩堡水电站2013年防洪应急预案
- 中国皮革绿行业市场前景分析预测年度报告(目录) - 图文
- 浅谈数学思维和方法在初中物理中的应用
- 全市项目建设和招商引资专题培训班学习心得体会 - 1
- 出血习题
- 马尔代夫的美丽与奢华
- 涉越跨国婚姻现状分析
- 勐捧镇忙耿村至蒿子坝村沙石化路面工程建议书