深入浅出MFC“文档/视图”架构(3)

――文档

 

 

作者:宋宝华 e-mail:21cnbao@21cn.com

 

 

1.文档类CDocument

在“文档/视图”架构的MFC程序中,文档是一个CDocument派生对象,它负责存储应用程序的数据,并把这些信息提供给应用程序的其余部分。CDocument类对文档的建立及归档提供支持并提供了应用程序用于控制其数据的接口,类CDocument的声明如下:

/

// class CDocument is the main document data abstraction

class CDocument : public CCmdTarget

{

DECLARE_DYNAMIC(CDocument)

public:

// Constructors

CDocument();

// Attributes

public:

const CString& GetTitle() const;

virtual void SetTitle(LPCTSTR lpszTitle);

const CString& GetPathName() const;

virtual void SetPathName(LPCTSTR lpszPathName, BOOL bAddToMRU = TRUE);

CDocTemplate* GetDocTemplate() const;

virtual BOOL IsModified();

virtual void SetModifiedFlag(BOOL bModified = TRUE);

// Operations

void AddView(CView* pView);

void RemoveView(CView* pView);

virtual POSITION GetFirstViewPosition() const;

virtual CView* GetNextView(POSITION& rPosition) const;

// Update Views (simple update - DAG only)

void UpdateAllViews(CView* pSender, LPARAM lHint = 0L,

CObject* pHint = NULL);

// Overridables

// Special notifications

virtual void OnChangedViewList(); // after Add or Remove view

virtual void DeleteContents(); // delete doc items etc

// File helpers

virtual BOOL OnNewDocument();

virtual BOOL OnOpenDocument(LPCTSTR lpszPathName);

virtual BOOL OnSaveDocument(LPCTSTR lpszPathName);

virtual void OnCloseDocument();

virtual void ReportSaveLoadException(LPCTSTR lpszPathName,

CException* e, BOOL bSaving, UINT nIDPDefault);

virtual CFile* GetFile(LPCTSTR lpszFileName, UINT nOpenFlags,

CFileException* pError);

virtual void ReleaseFile(CFile* pFile, BOOL bAbort);

// advanced overridables, closing down frame/doc, etc.

virtual BOOL CanCloseFrame(CFrameWnd* pFrame);

virtual BOOL SaveModified(); // return TRUE if ok to continue

virtual void PreCloseFrame(CFrameWnd* pFrame);

// Implementation

protected:

// default implementation

CString m_strTitle;

CString m_strPathName;

CDocTemplate* m_pDocTemplate;

CPtrList m_viewList; // list of views

BOOL m_bModified; // changed since last saved

public:

BOOL m_bAutoDelete; // TRUE => delete document when no more views

BOOL m_bEmbedded; // TRUE => document is being created by OLE

#ifdef _DEBUG

virtual void Dump(CDumpContext&) const;

virtual void AssertValid() const;

#endif //_DEBUG

virtual ~CDocument();

// implementation helpers

virtual BOOL DoSave(LPCTSTR lpszPathName, BOOL bReplace = TRUE);

virtual BOOL DoFileSave();

virtual void UpdateFrameCounts();

void DisconnectViews();

void SendInitialUpdate();

// overridables for implementation

virtual HMENU GetDefaultMenu(); // get menu depending on state

virtual HACCEL GetDefaultAccelerator();

virtual void OnIdle();

virtual void OnFinalRelease();

virtual BOOL OnCmdMsg(UINT nID, int nCode, void* pExtra,

AFX_CMDHANDLERINFO* pHandlerInfo);

friend class CDocTemplate;

protected:

// file menu commands

//{{AFX_MSG(CDocument)

afx_msg void OnFileClose();

afx_msg void OnFileSave();

afx_msg void OnFileSaveAs();

//}}AFX_MSG

// mail enabling

afx_msg void OnFileSendMail();

afx_msg void OnUpdateFileSendMail(CCmdUI* pCmdUI);

DECLARE_MESSAGE_MAP()

};

一个文档可以有多个视图,每一个文档都维护一个与之相关视图的链表(CptrList类型的 m_viewList实例)。CDocument::AddView将一个视图连接到文档上,并将视图的文档指针指向该文档:

void CDocument::AddView(CView* pView)

{

ASSERT_VALID(pView);

ASSERT(pView->m_pDocument == NULL); // must not be already attached

ASSERT(m_viewList.Find(pView, NULL) == NULL); // must not be in list

m_viewList.AddTail(pView);

ASSERT(pView->m_pDocument == NULL); // must be un-attached

pView->m_pDocument = this;

OnChangedViewList(); // must be the last thing done to the document

}

CDocument::RemoveView则完成与CDocument::AddView相反的工作:

void CDocument::RemoveView(CView* pView)

{

ASSERT_VALID(pView);

ASSERT(pView->m_pDocument == this); // must be attached to us

m_viewList.RemoveAt(m_viewList.Find(pView));

pView->m_pDocument = NULL;

OnChangedViewList(); // must be the last thing done to the document

}

从CDocument::AddView和CDocument::RemoveView函数可以看出,在与文档关联的视图被移走或新加入时CDocument::OnChangedViewList将被调用:

void CDocument::OnChangedViewList()

{

// if no more views on the document, delete ourself

// not called if directly closing the document or terminating the app

if (m_viewList.IsEmpty() && m_bAutoDelete)

{

OnCloseDocument();

return;

}

// update the frame counts as needed

UpdateFrameCounts();

}

CDocument::DisconnectViews将所有的视图都与文档“失连”:

void CDocument::DisconnectViews()

{

while (!m_viewList.IsEmpty())

{

CView* pView = (CView*)m_viewList.RemoveHead();

ASSERT_VALID(pView);

ASSERT_KINDOF(CView, pView);

pView->m_pDocument = NULL;

}

}

实际上,类CDocument对视图的管理与类CDocManager对文档模板的管理及CDocTemplate对文档的管理非常类似,少不了的,类CDocument中可遍历对应的视图(出现GetFirstXXX和GetNextXXX两个函数):

POSITION CDocument::GetFirstViewPosition() const

{

return m_viewList.GetHeadPosition();

}

CView* CDocument::GetNextView(POSITION& rPosition) const

{

ASSERT(rPosition != BEFORE_START_POSITION);

// use CDocument::GetFirstViewPosition instead !

if (rPosition == NULL)

return NULL; // nothing left

CView* pView = (CView*)m_viewList.GetNext(rPosition);

ASSERT_KINDOF(CView, pView);

return pView;

}

CDocument::GetFile和CDocument::ReleaseFile函数完成对参数lpszFileName指定文档的打开与关闭操作:

CFile* CDocument::GetFile(LPCTSTR lpszFileName, UINT nOpenFlags,

CFileException* pError)

{

CMirrorFile* pFile = new CMirrorFile;

ASSERT(pFile != NULL);

if (!pFile->Open(lpszFileName, nOpenFlags, pError))

{

delete pFile;

pFile = NULL;

}

return pFile;

}

void CDocument::ReleaseFile(CFile* pFile, BOOL bAbort)

{

ASSERT_KINDOF(CFile, pFile);

if (bAbort)

pFile->Abort(); // will not throw an exception

else

pFile->Close();

delete pFile;

}

CDocument类的OnNewDocument、OnOpenDocument、OnSaveDocument及OnCloseDocument这一组成员函数用于创建、打开、保存或关闭一个文档。在这一组函数中,上面的CDocument::GetFile和CDocument::ReleaseFile两个函数得以调用:

BOOL CDocument::OnOpenDocument(LPCTSTR lpszPathName)

{

if (IsModified())

TRACE0("Warning: OnOpenDocument replaces an unsaved document./n");

CFileException fe;

CFile* pFile = GetFile(lpszPathName,

CFile::modeRead|CFile::shareDenyWrite, &fe);

if (pFile == NULL)

{

ReportSaveLoadException(lpszPathName, &fe,

FALSE, AFX_IDP_FAILED_TO_OPEN_DOC);

return FALSE;

}

DeleteContents();

SetModifiedFlag(); // dirty during de-serialize

CArchive loadArchive(pFile, CArchive::load | CArchive::bNoFlushOnDelete);

loadArchive.m_pDocument = this;

loadArchive.m_bForceFlat = FALSE;

TRY

{

CWaitCursor wait;

if (pFile->GetLength() != 0)

Serialize(loadArchive); // load me

loadArchive.Close();

ReleaseFile(pFile, FALSE);

}

CATCH_ALL(e)

{

ReleaseFile(pFile, TRUE);

DeleteContents(); // remove failed contents

TRY

{

ReportSaveLoadException(lpszPathName, e,

FALSE, AFX_IDP_FAILED_TO_OPEN_DOC);

}

END_TRY

DELETE_EXCEPTION(e);

return FALSE;

}

END_CATCH_ALL

SetModifiedFlag(FALSE); // start off with unmodified

return TRUE;

}

打开文档的函数CDocument::OnOpenDocument完成的工作包括如下几步:

(1)打开文件对象;

(2)调用DeleteDontents();

(3)建立与此文件对象相关联的CArchive对象;

(4)调用应用程序文档对象的Serialize()函数;

(5)关闭CArchive对象、文件对象。

BOOL CDocument::OnSaveDocument(LPCTSTR lpszPathName)

{

CFileException fe;

CFile* pFile = NULL;

pFile = GetFile(lpszPathName, CFile::modeCreate |

CFile::modeReadWrite | CFile::shareExclusive, &fe);

if (pFile == NULL)

{

ReportSaveLoadException(lpszPathName, &fe,

TRUE, AFX_IDP_INVALID_FILENAME);

return FALSE;

}

CArchive saveArchive(pFile, CArchive::store | CArchive::bNoFlushOnDelete);

saveArchive.m_pDocument = this;

saveArchive.m_bForceFlat = FALSE;

TRY

{

CWaitCursor wait;

Serialize(saveArchive); // save me

saveArchive.Close();

ReleaseFile(pFile, FALSE);

}

CATCH_ALL(e)

{

ReleaseFile(pFile, TRUE);

TRY

{

ReportSaveLoadException(lpszPathName, e,

TRUE, AFX_IDP_FAILED_TO_SAVE_DOC);

}

END_TRY

DELETE_EXCEPTION(e);

return FALSE;

}

END_CATCH_ALL

SetModifiedFlag(FALSE); // back to unmodified

return TRUE; // success

}

保存文档的函数CDocument::OnSaveDocument完成的工作包括如下几步:

(1)创建或打开文件对象;

(2)建立相对应的CArchive对象;

(3)调用应用程序文档对象的序列化函数Serialize();

(4)关闭文件对象、CArchive对象;

(5)设置文件未修改标志。

void CDocument::OnCloseDocument()

// must close all views now (no prompting) - usually destroys this

{

// destroy all frames viewing this document

// the last destroy may destroy us

BOOL bAutoDelete = m_bAutoDelete;

m_bAutoDelete = FALSE; // don't destroy document while closing views

while (!m_viewList.IsEmpty())

{

// get frame attached to the view

CView* pView = (CView*)m_viewList.GetHead();

ASSERT_VALID(pView);

CFrameWnd* pFrame = pView->GetParentFrame();

ASSERT_VALID(pFrame);

// and close it

PreCloseFrame(pFrame);

pFrame->DestroyWindow();

// will destroy the view as well

}

m_bAutoDelete = bAutoDelete;

// clean up contents of document before destroying the document itself

DeleteContents();

// delete the document if necessary

if (m_bAutoDelete)

delete this;

}

CDocument::OnCloseDocument函数的程序流程为:

(1)通过文档对象所对应的视图,得到显示该文档视图的框架窗口的指针;

(2)关闭并销毁这些框架窗口;

(3)判断文档对象的自动删除变量m_bAutoDelete是否为真,如果为真,则以delete this语句销毁文档对象本身。

实际上,真正实现文档存储和读取(相对于磁盘)的函数是Serialize,这个函数通常会被CDocument的派生类重载(加入必要的代码,用以保存对象的数据成员到CArchive对象以及从CArchive对象载入对象的数据成员状态):

void CExampleDoc::Serialize(CArchive& ar)

{

if (ar.IsStoring())

{

// TODO: add storing code here

ar << var1 << var2;

}

else

{

// TODO: add loading code here

var2 >> var1 >> ar;

}

}

地球人都知道,文档与视图进行通信的方式是调用文档类的UpdateAllViews函数:

void CDocument::UpdateAllViews(CView* pSender, LPARAM lHint, CObject* pHint)

// walk through all views

{

ASSERT(pSender == NULL || !m_viewList.IsEmpty());

// must have views if sent by one of them

POSITION pos = GetFirstViewPosition();

while (pos != NULL)

{

CView* pView = GetNextView(pos);

ASSERT_VALID(pView);

if (pView != pSender)

pView->OnUpdate(pSender, lHint, pHint);

}

}

UpdateAllViews函数遍历视图列表,对每个视图都调用其OnUpdate函数实现视图的更新显示。

2.文档的OPEN/NEW

 

 

从连载2可以看出,在应用程序类CWinapp的声明中包含文件的New和Open函数:

afx_msg void OnFileNew();

afx_msg void OnFileOpen();

而在文档模板管理者类CDocManager中也包含文件的New和Open函数:

virtual void OnFileNew();

virtual void OnFileOpen();

virtual CDocument* OpenDocumentFile(LPCTSTR lpszFileName); // open named file

而文档模板类CDocTemplate也不例外:

virtual CDocument* OpenDocumentFile(

LPCTSTR lpszPathName, BOOL bMakeVisible = TRUE) = 0;

// open named file

// if lpszPathName == NULL => create new file with this type

virtual CDocument* CreateNewDocument();

复杂的是,我们在CDocument类中再次看到了New和Open相关函数:

virtual BOOL OnNewDocument();

virtual BOOL OnOpenDocument(LPCTSTR lpszPathName);

在这众多的函数中,究竟文档的创建者和打开者是谁?“文档/视图”框架程序“File”菜单上的“New”和“Open”命令究竟对应着怎样的函数调用行为?这一切都使我们陷入迷惘!

实际上“文档/视图”框架程序新文档及其关联视图和框架窗口的创建是应用程序对象、文档模板、新创建的文档和新创建的框架窗口相互合作的结果。具体而言,应用程序对象创建了文档模板;文档模板则创建了文档及框架窗口;框架窗口创建了视图。

在用户按下ID_FILE_OPEN及ID_FILE_NEW菜单(或工具栏)命令后,CWinApp(派生)类的OnFileNew、OnFileOpen函数首先被执行,其进行的行为是选择合适的文档模板,如图3.1所示。

srqc

图3.1文档模板的选择

实际上,图3.1中所示的“使用文件扩展名选择文档模板”、“是一个文档模板吗?”的行为都要借助于CDocManager类的相关函数,因为只有CDocManager类才维护了文档模板的列表。CDocManager::OnFileNew的行为可描述为:

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

}

之后,文档模板类的virtual CDocument* OpenDocumentFile(LPCTSTR lpszPathName, BOOL bMakeVisible = TRUE) = 0函数进行文档的创建工作,如果lpszPathName == NULL,是文档New行为;相反,则是Open行为。在创建框架后,文档模板根据是Open还是New行为分别调用CDocument的OnOpenDocument、OnNewDocument函数。图3.2描述了整个过程。

srqc02

图3.2文档、框架窗口的创建顺序

而图3.3则给出了视图的创建过程。

srqc03

图3.3视图的创建顺序

图3.1~3.3既描述了文档/视图框架对ID_FILE_OPEN及ID_FILE_NEW命令的响应过程,又描述了文档、框架窗口及视图的创建。的确,是无法单独描述文档的New和Open行为的,因为它和其他对象的创建交错纵横。

相信,随着我们进一步阅读后续连载,会对上述过程有更清晰的认识。

Trackback: http://tb.donews.net/TrackBack.aspx?PostId=808859

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值