深入浅出MFC文档/视图架构之文档
在"文檔/視圖"架構的MFC程序中,文檔是一個CDocument派生對象,它負責存儲應用程序的數據,并把這些信息提供給應用程序的其余部分。CDocument類對文檔的建立及歸檔提供支持并提供了應用程序用于控制其數據的接口,類CDocument的聲明如下:
/ // class CDocument is the main document data abstraction class CDocument : public CCmdTarget {DECLARE_DYNAMIC(CDocument) public:// ConstructorsCDocument();// 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);// Operationsvoid 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 notificationsvirtual void OnChangedViewList(); // after Add or Remove viewvirtual void DeleteContents(); // delete doc items etc// File helpersvirtual 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 continuevirtual void PreCloseFrame(CFrameWnd* pFrame);// Implementation protected:// default implementationCString m_strTitle;CString m_strPathName;CDocTemplate* m_pDocTemplate;CPtrList m_viewList; // list of viewsBOOL m_bModified; // changed since last savedpublic:BOOL m_bAutoDelete; // TRUE => delete document when no more viewsBOOL m_bEmbedded; // TRUE => document is being created by OLE#ifdef _DEBUGvirtual void Dump(CDumpContext&) const;virtual void AssertValid() const;#endif //_DEBUGvirtual ~CDocument();// implementation helpersvirtual BOOL DoSave(LPCTSTR lpszPathName, BOOL bReplace = TRUE);virtual BOOL DoFileSave();virtual void UpdateFrameCounts();void DisconnectViews();void SendInitialUpdate();// overridables for implementationvirtual HMENU GetDefaultMenu(); // get menu depending on statevirtual 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 enablingafx_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 attachedASSERT(m_viewList.Find(pView, NULL) == NULL); // must not be in listm_viewList.AddTail(pView);ASSERT(pView->m_pDocument == NULL); // must be un-attachedpView->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 usm_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 appif (m_viewList.IsEmpty() && m_bAutoDelete){OnCloseDocument();return;}// update the frame counts as neededUpdateFrameCounts(); }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 leftCView* 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 exceptionelsepFile->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-serializeCArchive loadArchive(pFile, CArchive::load | CArchive::bNoFlushOnDelete);loadArchive.m_pDocument = this;loadArchive.m_bForceFlat = FALSE;TRY{CWaitCursor wait;if (pFile->GetLength() != 0)Serialize(loadArchive); // load meloadArchive.Close();ReleaseFile(pFile, FALSE);}CATCH_ALL(e){ReleaseFile(pFile, TRUE);DeleteContents(); // remove failed contentsTRY{ReportSaveLoadException(lpszPathName, e,FALSE, AFX_IDP_FAILED_TO_OPEN_DOC);}END_TRYDELETE_EXCEPTION(e);return FALSE;}END_CATCH_ALLSetModifiedFlag(FALSE); // start off with unmodifiedreturn 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 mesaveArchive.Close();ReleaseFile(pFile, FALSE);}CATCH_ALL(e){ReleaseFile(pFile, TRUE);TRY{ReportSaveLoadException(lpszPathName, e,TRUE, AFX_IDP_FAILED_TO_SAVE_DOC);}END_TRYDELETE_EXCEPTION(e);return FALSE;}END_CATCH_ALLSetModifiedFlag(FALSE); // back to unmodifiedreturn 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 usBOOL bAutoDelete = m_bAutoDelete;m_bAutoDelete = FALSE; // don't destroy document while closing viewswhile (!m_viewList.IsEmpty()){// get frame attached to the viewCView* pView = (CView*)m_viewList.GetHead();ASSERT_VALID(pView);CFrameWnd* pFrame = pView->GetParentFrame();ASSERT_VALID(pFrame);// and close itPreCloseFrame(pFrame);pFrame->DestroyWindow();// will destroy the view as well}m_bAutoDelete = bAutoDelete;// clean up contents of document before destroying the document itselfDeleteContents();// delete the document if necessaryif (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 herear << var1 << var2;}else{// TODO: add loading code herevar2 >> 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 themPOSITION 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所示。
圖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;elsereturn ;// 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描述了整個過程。圖3.2文檔、框架窗口的創建順序
而圖3.3則給出了視圖的創建過程。
圖3.3視圖的創建順序
圖3.1~3.3既描述了文檔/視圖框架對ID_FILE_OPEN及ID_FILE_NEW命令的響應過程,又描述了文檔、框架窗口及視圖的創建。的確,是無法單獨描述文檔的New和Open行為的,因為它和其他對象的創建交錯縱橫。
相信,隨著我們進一步閱讀后續連載,會對上述過程有更清晰的認識。
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎
總結
以上是生活随笔為你收集整理的深入浅出MFC文档/视图架构之文档的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java 圆形按钮,如何在Java中创建
- 下一篇: 中信信用卡信金宝怎么申请?需要什么条件