Visual C++ 6.0的文件/視結構 (轉)

gugu99發表於2007-08-16
Visual C++ 6.0的文件/視結構 (轉)[@more@]Visual C++ 6.0 以其功能強大、介面友好而倍受員們的青睞。但是,在當前的 基本類庫4.2 版本中,大約有將近200 個類,數千個,加之Microsoft 公司隱藏了一些技術細節,使得人們深入學習MFC變得十分困難。
  MFC的AppWizard可以生成三種型別的應用程式:基於對話方塊的應用、單文件應用(SDI)和多文件應用(MDI)。前兩者的結構較簡單,本文不再贅敘。筆者擬從MFC中的文件/視結構入手,分析一些函式的流程,並解決編制MDI 應用程式過程中的一些常見問題。
(一)、瞭解文件/視結構
  MFC應用程式模型歷經多年以有了相當大的發展。有一個時期,它只是個使用應用程式和主視窗物件的簡單模型。在這個模型中,應用程式的資料作為成員變數保持在視窗類中,在框架視窗的客戶區中,該資料被提交顯示器。隨著MFC2。0的問世,一種應用程式結構的新方式----MFC文件/視結構出現了。在這種結構中,CFrameWnd繁重的任務被委派給幾個不同類,實現了資料和顯示的分離。一般情況下,採用文件/視結構的應用程式至少應由以下物件組成:
。應用程式是一個CwinApp派生物件,它充當全部應用程式的容器。應用程式沿訊息對映分配訊息給它的所有子程式。
。框架視窗是一CfrmeWnd派生物件。
。文件是一個CDocument派生物件,它儲存應用程式的資料,並把這些資訊提供給應用程式的其餘部分。
。視窗是Cview派生物件,它與其父框架視窗使用者區對齊。視窗接受使用者對應用程式的輸入並顯示相關聯的文件資料。
通常,應用程式資料存在於簡單模型中的框架視窗中。在文件/視方式中,該資料移入稱為document的獨立資料物件。當然,文件不一定是文字,文件是可以表現應用程式使用的資料集的抽象術語。而使用者輸入處理及圖形輸出功能從框架視窗轉向檢視。單獨的視窗完全遮蔽框架視窗的客戶區,這意味著即使程式設計師直接繪畫至框架視窗的客戶區,檢視仍遮蔽繪畫,在螢幕上不出現任何資訊。所以輸出必須透過檢視。框架視窗僅僅是個檢視容器。
CDocument類對文件的建立及歸檔提供支援並提供應用程式用於控制其資料的介面。MDI應用程式可以處理多個型別的文件,每個型別的文件擁有一個相關聯的文件模板物件。文件物件駐留在場景後面,提供由檢視物件顯示的資訊。文件至少有一個相關聯的檢視。檢視只能與一個文件相關聯。
在文件/視方式中,物件的建立是由文件模板來管理的,它是CDocTemplate派生物件,建立並維護框架視窗,文件及視。
MFC命令處理程式以響應發生在應用程式中的事件。命令傳送的優先順序是:
活動的檢視->框架視窗->文件->應用程式->預設視窗過程(DefProc)
總之,在文件/視方式中,文件和視是分離的,即:文件用於儲存資料,而視是用來顯示這些資料。文件模板維護它們之間的關西。這種文件/視結構在開發大型專案時特別有用。
(二)、瞭解與文件/視結構有關的各種類之間的關係。
在文件/視應用程式中,CWinApp物件擁有並控制文件模板,後者產生文件、框架視窗及視窗。這種相互關係如圖(1)所示:
從使用者的角度來看,“視”實際上是一個普通的視窗。象其他基於Widnows應用的視窗一樣,人們可以改變它的尺寸,對它進行移動,也可以隨時關閉它。若從程式設計師的角度來看,視實際上是一個從MFC類庫中的Cview類所派生出的類的物件。文件物件是用來儲存資料的,而視物件是用來顯示資料的,並且允許對資料進行編輯。SDI或MDI的文件類是由Cdocument類派生出來的,它可以有一個或多個視類,而這些視類最終都是由Cview類派生出來的。視物件只有一個與之相聯絡的文件物件,它所包含的CView::GetDocument函式允許應用在視中得到與之相聯絡的文件,據此,應用程式可以對文件類成員函式及公共資料成員進行訪問。如果視物件接受到了一條訊息,表示使用者在編輯控制中輸入了新的資料,此時,視就必須通知文件物件對其內部資料進行相應的。
如果文件資料發生了變化,則所有的視都必須被通知到,以便它們能夠對所顯示的資料進行相應的更新。Cdocument::UpdateAllViews函式即可完成此功能。當該函式被呼叫時,派生視類的CView::OnUpdate函式被觸發。通常OnUpdate函式要對文件進行訪問,讀取文件資料,然後再對視的資料成員或控制進行更新,以便反映出文件的變化。另外,還可以利用OnUpdate函式使視的部分客戶區無效,以便觸發Cview::OnDraw函式,利用文件資料來重新對視窗進行繪製。
在MDI應用程式中,可以處理多個文件型別,即多個文件模板,每個模板又可以有多個文件,每個文件又可以多視顯示。為管理方便,上一級往往保留了下一級的指標列表。如圖(2)所示:
解釋如下:
(1)、每個應用程式類(CwinApp的派生類)都保留並維護了一份所有文件模板的指標列表,這是一個連結串列結構。應用程式為所要支援的每個文件型別動態分配一個CMultiDocTemplate 物件,
CmultiDocTemplate(UINT nIDRe,
CruntimeClass * pDocClass,
CruntimeClass * pFrameClass,
CruntimeClass * pViewClass );
並在應用程式類的CWinApp::InitInstance成員函式中將每個CMultiDocTemplate物件傳遞給CWinApp::AddDocTemplate。 該函式將一個文件模板加入到應用程式可用文件模板的列表中。函式原形為:
void AddDocTemplate(CdocTemplate * pTemplate);
應用程式可以用CWinApp::GetFirstDocTemplatePostion獲得應用程式註冊的第一個文件模板的位置,利用該值來呼叫CWinApp::GetNextDocTemplate函式,獲得第一個CDocTemplate物件指標。函式原形如下:
POSITION GetFirstDocTemplate( ) const;
CDocTemplate *GetNextDocTemplate( POSITION & p) const;
第二個函式返回由pos 標識的文件模板。POSITION是MFC定義的一個用於迭代或物件指標檢索的值。透過這兩個函式,應用程式可以遍歷整個文件模板列表。如果被檢索的文件模板是模板列表中的最後一個,則pos引數被置為NULL。
(2)、一個文件模板可以有多個文件,每個文件模板都保留並維護了一個所有對應文件的指標列表。應用程式可以用CDocTemplate::GetFirstDocPosition函式獲得與文件模板相關的文件集合中第一個文件的位置,並用POSITION值作為CDocTemplate::GetNextDoc的引數來重複遍歷與模板相關的文件列表。函式原形為:
viaual POSITION GetFirstDocPosition( ) const = 0;
visual Cdocument *GetNextDoc(POSITION & rPos) const = 0;
如果列表為空,則rPos被置為NULL.
(3)、在文件中可以呼叫CDocument::GetDocTemplate獲得指向該文件模板的指標。函式原形如下:
CDocTemplate * GetDocTemplate ( ) const;
如果該文件不屬於文件模板管理,則返回值為NULL。
(4)、一個文件可以有多個視。每一個文件都保留並維護一個所有相關視的列表。CDocument::AddView將一個視連線到文件上,將該視加入到文件相聯絡的視的列表中,並將視的文件指標指向該文件。當有File/New、File/Open、Windows/New或Window/Split的命令而將一個新建立的視的物件連線到文件上時, MFC會自動呼叫該函式,框架透過文件/視的結構將文件和視聯絡起來。當然,程式設計師也可以根據自己的需要呼叫該函式。
Virtual POSITION GetFirstViewPosition( ) const;
Virtual CViw * GetNextView( POSITION &rPosition) cosnt;
應用程式可以呼叫CDocument::GetFirstViewPosition返回與呼叫文件相聯絡的視的列表中的第一個視的位置,並呼叫CDocument::GetNextView返回指定位置的視,並將rPositon的值置為列表中下一個視的POSITION值。如果找到的視為列表中的最後一個視,則將rPosition置為NULL.
當在文件上新增一個視或刪除一個視時,MFC會呼叫OnChangeViewList函式。如果被刪除的視是該文件的最後一個視,則刪除該文件。
(5)、一個視只能有一個文件。在視中,呼叫CView::GetDocument可以獲得一個指向視的文件的指標。函式原形如下:
CDocument *GetDocument ( ) const;
如果該視不與任何文件相,則返回NULL.
(6)、MDI框架視窗透過呼叫CFrameWnd::GetActiveDocument 可以獲得與當前活動的視相連的CDocument 指標。函式原形如下:
virtual CDocument * GetActiveDocument( );
(7)、透過呼叫CFrameWnd::GetActiveView 可以獲得指向與CFrameWnd框架視窗連線的活動視的指標,如果是被CMDIFrameWnd框架視窗呼叫,則返回NULL。MDI框架視窗可以首先呼叫MDIGetActive找到活動的MDI子視窗,然後找到該子視窗的活動視。函式原形如下:
virtual Cdocument * GetActiveDocument( );
(8)、MDI框架視窗透過呼叫CFrameWnd::GetActiveFrame, 可以獲得一個指向MDI框架視窗的活動多文件介面子視窗的指標。
(9)、CMDIChildWnd呼叫GetMDIFrame獲得MDI框架視窗(CMDIFrameWnd)。
(10)、CWinApp 呼叫AfxGetMainWnd得到指向應用程式的活動主視窗的指標。
下面一段程式碼,就是利用CDocTemplate、CDocument和CView之間的存取關係,遍歷整個文件模板、文件以及視。
CMyApp * pMyApp = (CMyApp *)AfxGetApp();
POSITION p = pMyApp->GetFirstDocTemplatePosition();
while(p!= NULL) {
CDocTemplate * pDocTemplate = pMyApp->GetNextDocTemplate(p);
POSITION p1 = pDocTemplate->GetFirstDocPosition();
while(p1 != NULL) {
CDocument * pDocument = pDocTemplate->GetNextDoc(p1);
POSITION p2 = pDocument->GetFirstViewPosition();
while(p2 != NULL) {
CView * pView = pDocument->GetNextView(p2);
}
}
}
(圖4)、遍歷整個文件模板、文件和視
在應用程式的任何地方,程式設計師都可以呼叫AfxGetApp( )獲得應用程式的物件指標。由於本文著重介紹文件/視的關係,至於框架視窗之間的關係沒能列全,讀者可以查相應的文件。
(三)、瞭解CwinApp::OnFileNew、CwinApp::OnFileOpen和Window/New的程式流程。
(1)、CwinApp::OnFileNew和CwinApp::OnFileOpen函式的簡單流程。
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
在CWinApp::OnFile/new 或CwinApp::OnFileOpen函式中,核心操作是CDocTemplate::OpenDocument函式。其函式原型為:
virtual CDocument* CDocTemplate::OpenDocumentFile(LPCTSTR lpszPathName, BOOL bMakeVisible = TRUE ) = 0;
圖(4)中星號標註之後即是該函式的流程,簡要介紹如下:
(1)、CDocTemplate::CreateNewDocument函式建立一個新文件,其型別與文件模板相關,並透過函式CDocTemplate::AddDocument加入該文件模板的文件指標列表中。此時,文件類的建構函式被,程式可以在此進行文件的初始化。
(2)、函式CDocTemplate::CreateNewFrame呼叫MDI子視窗類(CMDIChildWnd)的建構函式,生成MDI子視窗物件。接著呼叫CMDIChildWnd::PreCreateWindow。然後,生成一個CCreateContext物件,(CcreateContext是MFC框架所使用的一種結構,它將構成文件和視的聯絡起來。後文將詳細介紹之。)並將該物件值傳給CMDIChildWnd::OnCreateClient函式。MFC呼叫此函式,用CCreateContext物件提供的資訊建立一個或多個CView物件。此時,各視的建構函式被依次呼叫。
(3)、接著,判斷lpszPathName是否為空。分為兩種情況:
(a)、若為空,則表明要建立一個新文件:呼叫SetDefaultTitle函式裝載文件的預設標題,並顯示在文件的標題欄中;然後執行CDocument::OnNewDocument。該函式呼叫DeleteContents以保證文件為空,然後置新文件為清潔。可以過載該函式。
(b)、否則,表明要開啟一個已存在的文件:呼叫CDocument::OnOpenDocument開啟指定的;執行DeleteContext,保證文件為空;呼叫C::Serialize讀入該檔案的內容。(程式設計師可在此進行檔案的讀入操作。當然,也可以在CDocument::OnOpenDocument中讀入檔案)。然後置文件為清潔;最後,呼叫CDocTemplate::SetPathName,並把檔名加入到最近檔案列表中。
(4)、呼叫CDocTemplate::InitialUpdateFrame函式,使框架視窗中的各個視收到OnInitialUpdate呼叫。框架視窗的主視(子窗ID等於AFX_IDW_PANE_FIRST的視)被啟用。程式設計師可以在此對視物件進行初始化。
(2)、Window/New命令的程式流程
當主框架視窗上有子視窗時,選擇Window/New命令可以生成該活動子視窗的影像。它們有相同的文件模板、相同的文件。其流程如下:
 
 
 
 
執行Window/New的過程與File/New的過程差不多。所不同的是,File/New須要建立一個新文件,而Window/New則是獲得已存在的MDI子視窗的文件。因此以前存在的視和New以後生成的視均為該文件的視,都是該文件的內容的顯示。當呼叫CDocument::UpdateAllViews函式時,它們(視)的OnUpdate函式都將被啟用。此時,在該文件的視指標列表中,將有多於一個的視(具體數目視Window/New執行的次數而定)。讀者可以利用(圖3)中的程式碼跟蹤程式結果。
 
(四)、幾種情況的討論
上面,筆者就MFC中文件/視的關係進行了分析,下面,筆者將結合具體情況進行討論:
(1)、如何根據自己的要求來選擇文件模板,及相應的視和文件。
在通常的MDI應用程式中,只有一個文件模板,程式設計師只能開啟一種型別的文件。因此,程式設計師只要呼叫File/New或者File/Open建立或者開啟文件即可,至於文件、視和框架視窗之間的關係,由文件模板在幕後控制,不須要對文件模板進行操作。但是,如果應用程式需要處理多種型別的文件,並且何時開啟何種文件均需程式設計師手工控制,此時,程式設計師必須對文件模板進行。
例如,筆者需要處理AVI和BMP兩種檔案型別。AVI和BMP的資料存放格式不同,不能用同一的資料結構來描述,因此,把它們的資料都存入一個文件是不合適的。同時,由於AVI是圖象序列,BMP僅是一幅圖象,它們的顯示是肯定不一樣的,即它門的視不同。基於此,筆者決定分別建立兩套文件模板,兩套框架視窗,兩套文件和兩套視,分別用於AVI和BMP的資料存放和顯示。程式可以根據使用者選擇的檔名來分別處理AVI和BMP。具體步驟如下:
(Step 1)、在應用程式類(CWinApp)的派生類中增加文件模板成員變數,以便對文件模板進行操作。
class C3dlcsApp : public CWinApp
{ 。。。 。。。
public:
CMultiDocTemplate * m_pAVIDocTemplate;
CMultiDocTemplate * m_pBMPDocTemplate;
}
(Step 2)、在主框架中增加選單響應:
void CMainFrame::OnFileOpen() {
CFileDialog my(true);
if(my.odal()==IDOK) {
CString FileName = my.GetPathName();
CString FileExt = my.GetFileExt();
if((FileExt == "AVI") || (FileExt == "avi")) {
CMyApp * pMyApp = (CMyApp *)AfxGetApp();
CMultiDocTemplate*pAVIDocTemplate=pMyApp->m_pAVIDocTemplate;
pAVIDocTemplate->OpenDocumentFile(FileName);
}
else if((FileExt == "BMP") || (FileExt == "bmp")) {
CMyApp * p3dlcsApp = (CMyApp *)AfxGetApp();
CMultiDocTemplate* pDATDocTemplate=pMyApp->m_pBMPDocTemplate;
pDATDocTemplate->OpenDocumentFile(FileName);
}
else {
AfxMessageBox("Yor a file not supported!");
return;
}
}
}
筆者把使用者輸入檔名的字尾作為分支條件,如果是AVI檔案,則先獲得關於AVI檔案的文件模板,然後呼叫CDocTemplate::OpenUpdateFrame (lpszFileName)函式開啟此文件。正如前面所分析,此函式將依次生成新文件,新框架,在CMDIChildWnd::OnCreateClient中建立視,最後向框架中所有的視傳送初始化訊息,使其顯示在螢幕上。如果是BMP檔案,操作類似。
當然,程式設計師也可以在程式的任何位置實現此操作:透過全域性函式AfxGetApp 獲得應用程式物件指標,從而獲得相應的文件模板指標。
由於由AppWizard生成的應用程式會預設呼叫CWinApp::OnFileNew,所以當程式開始執行時,會在主框架上顯示一個新的空視窗。如果想去掉這個空視窗,只須過載CWinApp::OnFileNew函式,不許要任何程式碼,即可。
 
(2)、切分視窗與文件/視結構
一個文件可以有多個視,切分視窗即是表示多視的一種方法。 切分視窗是透過類CSplitterWnd來表示的,對Window來說,CSplitterWnd物件是一個真正的視窗,它完全佔據了框架視窗的客戶區域,而視視窗則佔據了切分視窗的窗片區域。切分視窗並不參與命令傳遞機制,(窗片中)活動的視窗從邏輯上來看直接被連到了它的框架視窗中。
切分視窗可以分為動態和靜態兩種。前者較簡單,本文僅討論後者。建立切分視窗的步驟如下:
(Step 1)、在自己的框架視窗中宣告成員變數,用以對切分視窗進行操作。
class CMyFrame : public CMDIChildWnd
{ 。。。 。。。
CSplitterWnd m_Splitter;
CSplitterWnd m_Splitter2;
}
(Step 2)、過載CMDIChildWnd::OnCreateClient函式,建立切分視窗。
BOOL CMyFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext)
{
BOOL btn = m_Splitter.CreateStatic(this,1,2);
btn |= m_Splitter.CreateView(0,0, RUNTIME_CLASS(CAVIDispView), CSize(100,100), pContext);
m_Splitter2.CreateStatic(&m_Splitter,
2, 1,
WS_CHILD | WS_VISIBLE | WS_BORDER,
m_Splitter.IdFromRowCol(0, 1));
btn |= m_Splitter2.CreateView(0, 0, RUNTIME_CLASS(CBMPView),
CSize(100,100), pContext);
btn |= m_Splitter2.CreateView(1, 0, RUNTIME_CLASS(CAVIView),
CSize(100,100), pContext);
return btn;
//return CMDIChildWnd::OnCreateClient(lpcs, pContext);
}
CFrameWnd::OnCreateClient函式原形為:
virtual BOOL OnCreateClient(LPCREATESTRUCT lpcs, CcreateContext * pContext);
預設的CMDIChildWnd::OnCreateClient函式根據pContext引數提供的資訊,呼叫CFrameWnd::CreateView函式建立一個視。可以過載該函式,載入CCreateContext物件中傳遞的值,或改變框架視窗主客戶區中控制的建立方式。在上面的程式中,筆者 建立了3個切分視窗。比如開啟了一個名為“a.avi”的文件,此時該文件將有3個視,一個框架視窗。如果執行了Window/New操作,則此時有一個文件,6個視和2個框架視窗。若該文件呼叫CDocument::UpdateAllViews函式,則這6個視的CView::OnUpdate函式都會被激發。
(3)、關於CCreateContext的討論。
CCreateContext是MFC框架所使用的一種結構,它將構成文件/視的元件聯絡起來。這個結構包括指向文件的指標,框架視窗,視以及文件模板,它還包含一個指向CRuntimeClass的指標,以指明所建立的視的型別。其資料成員如下:
m_pNewViewClass:指向建立上下文的視的CRuntimeClass的指標。
m_pCurrentDoc:指向文件物件的指標,以和新視聯絡起來。
m_pNewDocTemplate:指向與框架視窗的建立相聯絡文件模板的指標。
m_pLastView:指向已存在的視,它是新產生的視的模型。
m_pCurrentFrame:指向已存在的框架視窗,它是新產生的框架視窗的模型。
程式設計師可以透過改變CCreateContext物件的值,來建立更加靈活的視。由於過程較複雜,筆者不再贅許敘,讀者可參閱相關的Visual C++ Help文件。
(五)、結束語
Visual C++ 6.0的文件/視結構代表了一種新的方式,其核心是文件與視的分離,即資料存放與顯示(操作)的分離。在MFC類庫中,各個物件之間的關係很複雜,但,只要深入瞭解後,會發現它們之間是相互聯絡的,可以相互存取的。如果大家想設計出靈活、健壯的應用程式,就必須深入瞭解MFC。跟蹤原始碼就是一個較好的方法。文件/視的關係的確非常複雜,如果能知道每個函式是在哪呼叫的,執行了何種操作,就能遊人刃有餘,寫出優美的應用程式。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10748419/viewspace-960749/,如需轉載,請註明出處,否則將追究法律責任。

相關文章