您的位置:首页 > 其它

文档/视图结构中的各个部分是如何联系到一起的(1)

2005-02-02 16:45 337 查看
文档/视图结构是MFC中最有特色而又有难度的部分,在这当中涉及了应用、文档模板、文档、视图、MDI框架窗口、MDI子窗口等不同的对象,如果不了解这些部分之间如何关联的话,就可能犯错误,也就很难编出有水平的文档/视图程序。比如我在初学VC编程的时候,为应用程序添加了两个文档模板,两个模板公用一个文档类,只是视图不一样,期望当一个模板的文档的视图改变了文档后,调用UpdateAllViews后也能更新另一个文档模板的视图,结果当然是不行的,原因就是对MFC的文档/视图结构没有深入的了解,了解的最好方法就是阅读一下MFC的源代码。下面就是我的笔记:
(一)应用程序对象与文档模板之间的联系:

首先,在应用程序对象中有一个CDocManager指针类型的共有数据成员m_pDocManager,在CDocManager中维护一个CPtrList类型的链表:m_tempateList,它是一个保护成员。InitInstance函数中调用CWinApp::AddDocTemplate函数,实际上是调用m_pDocManager的AddDocTemplate函数向链表m_templateList添加模板指针。CWinApp提供了GetFirstDocTemplatePosition和GetNextDocTemplate函数实现对m_templateList链表的访问(实际上是调用了CDocManager的相关函数)。

在文件操作方面CWinApp提供的最常用的功能是文件的新建(OnFileNew)和打开(OnFileOpen),它也是调用CDocManager类的同名函数。对于新建,一般的时候在只有一个文档模板的时候,它新建一个空白的文件;如果有多个文档模板的时候,它会出现一个对话框提示选择文档类型。它的源代码如下:

void CDocManager::OnFileNew()

{

if (m_templateList.IsEmpty())

{

.......

return;

}

//取第一个文档模板的指针

CDocTemplate* pTemplate = (CDocTemplate*)m_templateList.GetHead();

if (m_templateList.GetCount() > 1)

{

// 如果多于一个文档模板,出现对话框提示用户去选择

CNewTypeDlg dlg(&m_templateList);

int nID = dlg.DoModal();

if (nID == IDOK)

pTemplate = dlg.m_pSelectedTemplate;

else

return; // none - cancel operation

}

......

//参数为NULL的时候OpenDocument File会新建一个文件

pTemplate->OpenDocumentFile(NULL);

}

打开文件:

void CDocManager::OnFileOpen()

{

// 出现打开文件对话框

CString newName;

if (!DoPromptFileName(newName, AFX_IDS_OPENFILE,

OFN_HIDEREADONLY | OFN_FILEMUSTEXIST, TRUE, NULL))

return; // open cancelled



AfxGetApp()->OpenDocumentFile(newName); //实际也是调用文档模板的同名函数

}

(二)文档模板与文档之间的联系:

从上面看出应用程序对象对文件的新建和打开是依靠文档模板的OpenDocumentFile函数实现的。MFC的模板类是用来联系文档类、视类和框架类的,在它的构造函数就需要这三者的信息:

CDocTemplate ( UINT nIDResource, CRuntimeClass* pDocClass, CRuntimeClass* pFrameClass, CRuntimeClass* pViewClass );

构造函数利用后三个参数为它的三个CruntimeClass*类型的保护成员赋值:

m_pDocClass = pDocClass;

m_pFrameClass = pFrameClass;

m_pViewClass = pViewClass;

文档模板分为单文档模板和多文档模板两种,这两个模板的实现是不同的,除了上面的三个成员,内部有彼此不相同的但是很重要的成员变量。对于多文档模板:CPtrList m_docList;,单文档模板:CDocument* m_pOnlyDoc;。它们都有一个成员函数AddDocument,分别各自的成员进行赋值操作,而在它们的父类的CDocTemplate中则是为它所添加的文档的m_pDocTemplate变量赋值为模板自己的地址:

void CDocTemplate::AddDocument(CDocument* pDoc)

{

ASSERT_VALID(pDoc);

ASSERT(pDoc->m_pDocTemplate == NULL);

pDoc->m_pDocTemplate = this;

}

由于单文档模板只能拥有一个文档,所以它只是维护一个指向自己所拥有的模板的指针:m_pOnlyDoc,AddDocument函数就是要为这个成员赋值:

void CSingleDocTemplate::AddDocument(CDocument* pDoc)

{

......

CDocTemplate::AddDocument(pDoc);

m_pOnlyDoc = pDoc;

}
由于多文档模板可以拥有多个文档,所以它要维护的是包含它所打开的所有文档的指针的链表,所以它的AddDocument的实现为:

void CMultiDocTemplate::AddDocument(CDocument* pDoc)

{

......

CDocTemplate::AddDocument(pDoc);

m_docList..AddTail(pDoc);

}
模板通过m_pOnlyDoc(单文档)或记住了自己所拥有的所有的模板的指针,并通过GetFirstDocPosition和GetNextDoc函数可以实现对它所拥有的文档的访问,同时使文档记住了自己所属文档模板的指针,同时文档提供了GetDocTemplate()函数可以取得它所属的模板。

对AddDocument函数的调用主要是发生在另一个成员函数CreateNewDocument里,它的作用是创建一个新的文档:

CDocument* CDocTemplate::CreateNewDocument()

{

if (m_pDocClass == NULL)

{

……

}

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

……

AddDocument(pDocument);

return pDocument;

}

CreateNewDocument函数主要利用文档类的运行时指针的函数CreateObject创建一个新文档对象,并利用AddDocument将其指针赋給相关的成员,留做以后使用。

在应用程序的OnFileNew和OnFileOpen函数都使用了模板的OpenDocumentFile函数,而且在实际编程的时候也大都使用这个函数。在MSDN的文档说这个函数当参数不为NULL的时候打开文件,否则就用上面所说的CreateNewDocument函数创建一个新文档,那么它是如何实现的呢?

CDocument* CSingleDocTemplate::OpenDocumentFile(LPCTSTR lpszPathName,

BOOL bMakeVisible)

{

CDocument* pDocument = NULL;

CFrameWnd* pFrame = NULL;

BOOL bCreated = FALSE; // => doc and frame created

BOOL bWasModified = FALSE;

//如果已经有打开的文档,就会询问否保存文件

if (m_pOnlyDoc != NULL)

{

pDocument = m_pOnlyDoc;

if (!pDocument->SaveModified())

return NULL;



pFrame = (CFrameWnd*)AfxGetMainWnd();

......

}

//创建新文件

else

{

pDocument = CreateNewDocument();

ASSERT(pFrame == NULL);

bCreated = TRUE;

}

......

//如果第一次创建文档则也要创建框架窗口。

if (pFrame == NULL)

{

ASSERT(bCreated);



// create frame - set as main document frame

BOOL bAutoDelete = pDocument->m_bAutoDelete;

pDocument->m_bAutoDelete = FALSE;

pFrame = CreateNewFrame(pDocument, NULL);

pDocument->m_bAutoDelete = bAutoDelete;

......

}



if (lpszPathName == NULL)

{

// 为新文档设置默认标题

SetDefaultTitle(pDocument);

……

//一般的时候重载OnNewDocument初始化一些数据,如果返回FALSE,表示初始化失//败,销毁窗口。

if (!pDocument->OnNewDocument())

{

......

if (bCreated)

pFrame->DestroyWindow(); // will destroy document

return NULL;

}

}

else

{

CWaitCursor wait;



// open an existing document

bWasModified = pDocument->IsModified();

pDocument->SetModifiedFlag(FALSE);

//OnOpenDocument函数重新初始化文档对象

if (!pDocument->OnOpenDocument(lpszPathName))

{

if (bCreated)

{

//新建文档的情况

pFrame->DestroyWindow();

}

else if (!pDocument->IsModified())

{

// 文档没有被修改,恢复原来文档的修改标志

pDocument->SetModifiedFlag(bWasModified);

}

else

{

// 修改了原始的文档

SetDefaultTitle(pDocument);



if (!pDocument->OnNewDocument())

{

TRACE0("Error: OnNewDocument failed after trying to open a document - trying to continue./n");

}

}

return NULL; // open failed

}

pDocument->SetPathName(lpszPathName);

}



CWinThread* pThread = AfxGetThread();

if (bCreated && pThread->m_pMainWnd == NULL)

{

pThread->m_pMainWnd = pFrame;

}

InitialUpdateFrame(pFrame, pDocument, bMakeVisible);



return pDocument;

}

以下是多文档模板的OpenDocumentFile的实现

CDocument* CMultiDocTemplate::OpenDocumentFile(LPCTSTR lpszPathName,

BOOL bMakeVisible)

{

//新建一个文档对象

CDocument* pDocument = CreateNewDocument();

……



BOOL bAutoDelete = pDocument->m_bAutoDelete;

pDocument->m_bAutoDelete = FALSE;

CFrameWnd* pFrame = CreateNewFrame(pDocument, NULL);

pDocument->m_bAutoDelete = bAutoDelete;

……



if (lpszPathName == NULL)

//当是新建的时候

{

SetDefaultTitle(pDocument);



// avoid creating temporary compound file when starting up invisible

if (!bMakeVisible)

pDocument->m_bEmbedded = TRUE;



if (!pDocument->OnNewDocument())

{

pFrame->DestroyWindow();

return NULL;

}



m_nUntitledCount++;

}

else

{

// 打开一个已经存在的文件

CWaitCursor wait;

if (!pDocument->OnOpenDocument(lpszPathName))

{

// user has be alerted to what failed in OnOpenDocument

TRACE0("CDocument::OnOpenDocument returned FALSE./n");

pFrame->DestroyWindow();

return NULL;

}

pDocument->SetPathName(lpszPathName);

}



InitialUpdateFrame(pFrame, pDocument, bMakeVisible);

return pDocument;

}

从上面看出模板类的OpenDocumentFile函数里,利用CreateNewDocument对象使文档对象与模板对象建立了联系,利用了CreateNewFrame函数使框架窗口与文档、视图、模板发生了联系:



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

{

if (pDoc != NULL)

ASSERT_VALID(pDoc);



ASSERT(m_nIDResource != 0); // 必须有资源ID

CCreateContext context;

context.m_pCurrentFrame = pOther;

context.m_pCurrentDoc = pDoc;

context.m_pNewViewClass = m_pViewClass;

context.m_pNewDocTemplate = this;



if (m_pFrameClass == NULL)

{

……

}

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

if (pFrame == NULL)

{

……

return NULL;

}

ASSERT_KINDOF(CFrameWnd, pFrame);



if (context.m_pNewViewClass == NULL)

TRACE0("Warning: creating frame with no default view./n");



if (!pFrame->LoadFrame(m_nIDResource,

WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE, // default frame styles

NULL, &context))

{

……

return NULL;

}

return pFrame;

}

总结:在模板里使用自己的数据结构维护着自己拥有的文档对象,并提供了GetFirstDocPosition和GetNextDoc函数实现对这些文档的对象的访问。所以,在一个拥有多个文档模板的应用程序中,即使每个模板使用了相同类型的文档类,每个新建或打开的文档在这些文档模板之间也不是共享的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: