CSDN VC編程經驗總結(轉)

基礎類
文檔視圖結構
(一)、瞭解文檔/視結構
MFC應用程序模型歷經多年以有了相當大的發展。有一個時期,它只是個使用應用程序對象和主窗口對象的簡單模型。在這個模型中,應用程序的數據作爲成員變量保持在框架窗口類中,在框架窗口的客戶區中,該數據被提交顯示器。隨着MFC2。0的問世,一種應用程序結構的新方式----MFC文檔/視結構出現了。在這種結構中,CFrameWnd繁重的任務被委派給幾個不同類,實現了數據存儲和顯示的分離。一般情況下,採用文檔/視結構的應用程序至少應由以下對象組成:
。應用程序是一個CwinApp派生對象,它充當全部應用程序的容器。應用程序沿消息映射網絡分配消息給它的所有子程序。
。框架窗口是一CfrmeWnd派生對象。
。文檔是一個CDocument派生對象,它存儲應用程序的數據,並把這些信息提供給應用程序的其餘部分。
。視窗是Cview派生對象,它與其父框架窗口用戶區對齊。視窗接受用戶對應用程序的輸入並顯示相關聯的文檔數據。
通常,應用程序數據存在於簡單模型中的框架窗口中。在文檔/視方式中,該數據移入稱爲document的獨立數據對象。當然,文檔不一定是文字,文檔是可以表現應用程序使用的數據集的抽象術語。而用戶輸入處理及圖形輸出功能從框架窗口轉向視圖。單獨的視窗完全遮蔽框架窗口的客戶區,這意味着即使程序員直接繪畫至框架窗口的客戶區,視圖仍遮蔽繪畫,在屏幕上不出現任何信息。所以輸出必須通過視圖。框架窗口僅僅是個視圖容器。
CDocument類對文檔的建立及歸檔提供支持並提供應用程序用於控制其數據的接口。MDI應用程序可以處理多個類型的文檔,每個類型的文檔擁有一個相關聯的文檔模板對象。文檔對象駐留在場景後面,提供由視圖對象顯示的信息。文檔至少有一個相關聯的視圖。視圖只能與一個文檔相關聯。
在文檔/視方式中,對象的建立是由文檔模板來管理的,它是CDocTemplate派生對象,建立並維護框架窗口,文檔及視。
MFC調用命令處理程序以響應發生在應用程序中的事件。命令發送的優先級是:
活動的視圖->框架窗口->文檔->應用程序->默認窗口過程(DefWindowsProc)
總之,在文檔/視方式中,文檔和視是分離的,即:文檔用於保存數據,而視是用來顯示這些數據。文檔模板維護它們之間的關西。這種文檔/視結構在開發大型軟件項目時特別有用。
(二)、瞭解與文檔/視結構有關的各種類之間的關係。
在文檔/視應用程序中,CWinApp對象擁有並控制文檔模板,後者產生文檔、框架窗口及視窗。這種相互關係如圖(1)所示:

圖(1)

從用戶的角度來看,“視”實際上是一個普通的窗口。象其他基於Widnows應用的窗口一樣,人們可以改變它的尺寸,對它進行移動,也可以隨時關閉它。若從程序員的角度來看,視實際上是一個從MFC類庫中的Cview類所派生出的類的對象。文檔對象是用來保存數據的,而視對象是用來顯示數據的,並且允許對數據進行編輯。SDI或MDI的文檔類是由Cdocument類派生出來的,它可以有一個或多個視類,而這些視類最終都是由Cview類派生出來的。視對象只有一個與之相聯繫的文檔對象,它所包含的CView::GetDocument函數允許應用在視中得到與之相聯繫的文檔,據此,應用程序可以對文檔類成員函數及公共數據成員進行訪問。如果視對象接受到了一條消息,表示用戶在編輯控制中輸入了新的數據,此時,視就必須通知文檔對象對其內部數據進行相應的更新。
如果文檔數據發生了變化,則所有的視都必須被通知到,以便它們能夠對所顯示的數據進行相應的更新。Cdocument::UpdateAllViews函數即可完成此功能。當該函數被調用時,派生視類的CView::OnUpdate函數被觸發。通常OnUpdate函數要對文檔進行訪問,讀取文檔數據,然後再對視的數據成員或控制進行更新,以便反映出文檔的變化。另外,還可以利用OnUpdate函數使視的部分客戶區無效,以便觸發Cview::OnDraw函數,利用文檔數據來重新對窗口進行繪製。
在MDI應用程序中,可以處理多個文檔類型,即多個文檔模板,每個模板又可以有多個文檔,每個文檔又可以多視顯示。爲管理方便,上一級往往保留了下一級的指針列表。如圖(2)所示:

(2)

解釋如下:
(1)、每個應用程序類(CwinApp的派生類)都保留並維護了一份所有文檔模板的指針列表,這是一個鏈表結構。應用程序爲所要支持的每個文檔類型動態分配一個CMultiDocTemplate 對象,
CmultiDocTemplate(UINT nIDResource,
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 & pos ) 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,保證文檔爲空;調用CObject::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.DoModal()==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 select 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:指向已存在的框架窗口,它是新產生的框架窗口的模型。

--------------------------------------------------------------------------------

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章