MFC對(duì)象的創(chuàng)建
前面幾章介紹了MFC的核心概念和思想,即介紹了MFC對(duì)Windows對(duì)象的封裝方法和特點(diǎn);MFC對(duì)象的動(dòng)態(tài)創(chuàng)建、序列化;MFC消息映射機(jī)制。
現(xiàn)在,考查MFC的應(yīng)用程序結(jié)構(gòu)體系,即以文檔-視為核心的編程模式。學(xué)習(xí)本章,應(yīng)該弄清楚以下問(wèn)題:
MFC中諸多MFC對(duì)象的關(guān)系:應(yīng)用程序?qū)ο?,文檔對(duì)象,邊框窗口對(duì)象,文檔邊框窗口對(duì)象,視對(duì)象,文檔模板對(duì)象等。
MFC對(duì)象的創(chuàng)建和銷毀:由什么對(duì)象創(chuàng)建或銷毀什么對(duì)象,何時(shí)創(chuàng)建,何時(shí)銷毀?
MFC提供了那些接口來(lái)支持其編程模式?
- MFC對(duì)象的關(guān)系
- 創(chuàng)建關(guān)系
這里討論應(yīng)用程序、文檔模板、邊框窗口、視、文檔等的創(chuàng)建關(guān)系。圖5-1大略地表示了創(chuàng)建順序,但表5-1更直接地顯示了創(chuàng)建與被創(chuàng)建的關(guān)系。
表5-1 MFC對(duì)象的創(chuàng)建關(guān)系
創(chuàng)建者 |
被創(chuàng)建的對(duì)象 |
應(yīng)用程序?qū)ο? |
文檔模板 |
文檔模板 |
文檔 |
文檔模板 |
邊框窗口 |
邊框窗口 |
視 |
- 交互作用關(guān)系
應(yīng)用程序?qū)ο笥幸粋€(gè)文檔模板列表,存放一個(gè)或多個(gè)文檔模板對(duì)象;文檔模板對(duì)象有一個(gè)打開(kāi)文檔列表,存放一個(gè)或多個(gè)已經(jīng)打開(kāi)的文檔對(duì)象;文檔對(duì)象有一個(gè)視列表,存放顯示該文檔數(shù)據(jù)的一個(gè)或多個(gè)視對(duì)象;還有一個(gè)指針指向創(chuàng)建該文檔的文檔模板對(duì)象;視有一個(gè)指向其關(guān)聯(lián)文檔的指針,視是一個(gè)子窗口,其父窗口是邊框窗口(或者文檔邊框窗口);文檔邊框窗口有一個(gè)指向其當(dāng)前活動(dòng)視的指針;文檔邊框窗口是邊框窗口的子窗口。
Windows 管理所有已經(jīng)打開(kāi)的窗口,把消息或事件發(fā)送給目標(biāo)窗口。通常,命令消息發(fā)送給主邊框窗口。
圖5-2大略地表示了上述關(guān)系:
MFC提供了一些函數(shù)來(lái)維護(hù)這些關(guān)系。
表5-2列出了從一個(gè)對(duì)象得到相關(guān)對(duì)象的方法。
表5-2 從一個(gè)對(duì)象得到另一個(gè)對(duì)象的方法
本對(duì)象 |
要得到的對(duì)象 |
使用的成員函數(shù) |
CDocument對(duì)象 |
視列表 |
GetFirstViewPosition
GetNextView |
文檔模板 |
GetDocTemplate |
CView對(duì)象 |
文檔對(duì)象 |
GetDocument |
邊框窗口 |
GetParentFrame |
CMDIChildWnd或
CFrameWnd對(duì)象 |
活動(dòng)視 |
GetActiveView |
活動(dòng)視的文檔 |
GetActiveDocument |
CMDIFrameWnd對(duì)象 |
活動(dòng)文檔邊框窗口 |
MDIGetActive |
表5-3 從一個(gè)對(duì)象通知另一個(gè)對(duì)象的方法:
本對(duì)象 |
要通知的對(duì)象/動(dòng)作 |
使用的成員函數(shù) |
CView對(duì)象 |
通知文檔更新所有視 |
CDocument::UpdateAllViews |
CDocument對(duì)象 |
更新一個(gè)視 |
CView::OnUpdate |
CFrameWnd或
CMDIFrameWnd對(duì)象 |
通知一個(gè)視為活動(dòng)視 |
CView::OnActivateView |
設(shè)置一個(gè)視為活動(dòng)視 |
SetActivateView |
可以通過(guò)表5-2得到相關(guān)對(duì)象,再調(diào)用表5-3中相應(yīng)的函數(shù)。例如:視在接受了新數(shù)據(jù)或者數(shù)據(jù)被修改之后,使用表5-2中的函數(shù)GetDocument得到關(guān)聯(lián)文檔對(duì)象,然后調(diào)用表5-3中的文檔函數(shù)UpdateAllViews更新其他和文檔對(duì)象關(guān)聯(lián)的視。
在表5-2和表5-3中,CView對(duì)象指CView或派生類的實(shí)例;成員函數(shù)列中如果沒(méi)有指定類屬,就是第一列對(duì)象的類的成員函數(shù)。
- MFC提供的接口
MFC編程就是把一些應(yīng)用程序特有的東西填入MFC框架。MFC提供了兩種填入的方法:一種就是使用前一章論述的消息映射,消息映射給應(yīng)用程序的各種對(duì)象處理各種消息的機(jī)會(huì);另一種就是使用虛擬函數(shù),MFC在實(shí)現(xiàn)許多功能或者處理消息、事件的過(guò)程中,調(diào)用了虛擬函數(shù)來(lái)完成一些任務(wù),這樣就給了派生類覆蓋這些虛擬函數(shù)實(shí)現(xiàn)特定處理的機(jī)會(huì)。
下面兩節(jié)將列出兩類接口,有兩個(gè)目的:一是為了讓讀者獲得整體印象,二是后文將涉及到或者討論其中的許多函數(shù)時(shí),不顯得突兀。
- 虛擬函數(shù)接口
幾乎每一個(gè)MFC類都定義和使用了虛擬成員函數(shù),程序員可以在派生類中覆蓋它們。一般,MFC提供了這些函數(shù)的缺省實(shí)現(xiàn),所以覆蓋函數(shù)應(yīng)該調(diào)用基類的實(shí)現(xiàn)。這里給出一個(gè)MFC常用虛擬函數(shù)的總覽表(見(jiàn)表5-4),更詳細(xì)的信息或它們的缺省實(shí)現(xiàn)動(dòng)作參見(jiàn)MFC文檔。由于基類的虛擬函數(shù)被派生類繼承,所以在派生類中不作重復(fù)說(shuō)明。
覆蓋基類的虛擬函數(shù)可以通過(guò)ClassWizard進(jìn)行,不過(guò),并非所有的函數(shù)都可以這樣,有的必須手工加入函數(shù)聲明和實(shí)現(xiàn)。
表5-4 常見(jiàn)MFC類的虛擬函數(shù)接口
類 |
虛擬函數(shù) |
覆蓋的目的和功能 |
CCmdTarget |
OnCmdMsg |
發(fā)送、派發(fā)命令消息 |
OnFinalRelease |
OLE用途,引用為0時(shí)作清理工作 |
CWinThread |
ExitInstance |
在線程退出時(shí)作清理工作 |
InitInstance |
在線程開(kāi)始時(shí)作初始化 |
OnIdle |
執(zhí)行thread-specific idle-time處理 |
PreTranslateMessage |
在消息送給Windows函數(shù)TranslateMessage and DispatchMessage.之前進(jìn)行消息過(guò)濾 |
IsIdleMessage |
檢查是否是某個(gè)特別的消息 |
ProcessWndProcException |
截獲線程消息/命令處理中的例外 |
ProcessMessageFilter |
線程消息過(guò)濾 |
Run |
實(shí)現(xiàn)線程特定的消息循環(huán) |
CWinApp |
HideApplication |
關(guān)閉所有的窗口之前隱藏應(yīng)用程序 |
CloseAllDocument |
退出程序之前關(guān)閉所有文檔 |
轉(zhuǎn)下頁(yè) |
續(xù)表 |
|
SaveModifiedDocument |
框架窗口關(guān)閉時(shí)用來(lái)保存文檔 |
DoMessageBox |
實(shí)現(xiàn)客戶化的messagebox |
DoWaitCursor |
關(guān)閉或打開(kāi)等待光標(biāo) |
OnDDeCommand |
響應(yīng)DDE命令 |
WinHelp |
調(diào)用WinHelp函數(shù) |
CWnd |
WindowProc |
提供一個(gè)窗口過(guò)程 |
DefWindowProc |
為應(yīng)用程序不處理的消息提供缺省處理 |
PostNcDestroy |
在窗口銷毀之后被消息處理函數(shù)OnNcDestroy調(diào)用 |
OnNotify |
處理通知消息WM_NOTIFY |
OnChildNotify |
父窗口調(diào)用它給控制子窗口一個(gè)機(jī)會(huì)來(lái)處理通知反射消息 |
DoDataExchange |
Updata調(diào)用它來(lái)進(jìn)行對(duì)話框數(shù)據(jù)交換和驗(yàn)證 |
CFrameWnd |
GetMessageBar |
返回一個(gè)指向框架窗口的狀態(tài)條的指針 |
OnCreateClient |
創(chuàng)建框架的客戶窗口 |
OnSetPreviewMode |
設(shè)置程序的主框架窗口進(jìn)入或退出打印預(yù)覽模式 |
NegotiateBorderSpace |
協(xié)調(diào)邊框窗口的邊框空間的大小(OLE用途) |
CMDIFrameWnd |
CreateClient |
創(chuàng)建CMDIFrameWnd的MDICLIENT窗,被CWnd的消息處理函數(shù)OnCreate調(diào)用. |
轉(zhuǎn)下頁(yè) |
續(xù)表 |
|
GetWindowMenuPopup |
返回窗口的彈出式菜單 |
CDialog |
OnInitDialog |
對(duì)話框窗口的初始化 |
OnSetFont |
設(shè)置對(duì)話框控制的文本字體 |
OnOK |
模式對(duì)話框的OK按鈕按下后進(jìn)行的處理 |
OnCancel |
模式對(duì)話框的CANCEL按鈕按下后進(jìn)行的處理 |
CView |
IsSelected |
測(cè)試是否有一個(gè)文檔被選擇(OLE支持) |
OnActivateView |
視窗口激活時(shí)調(diào)用 |
OnActivateFrame |
當(dāng)包含視窗口的框架窗口變成活動(dòng)或非活動(dòng)窗口時(shí)調(diào)用 |
OnBeginPrinting |
打印工作開(kāi)始時(shí)調(diào)用,用來(lái)分配GDI資源 |
OnDraw |
用來(lái)屏幕顯示、打印、打印預(yù)覽文檔內(nèi)容 |
OnEndPrinting |
打印工作結(jié)束時(shí)調(diào)用,釋放GDI資源 |
OnEndPrintPreview |
退出打印預(yù)覽模式時(shí)調(diào)用 |
OnPrepareDC |
OnDraw或OnPrint之前調(diào)用,用來(lái)準(zhǔn)備設(shè)備描述表 |
OnPreparePrinting |
文檔打印或者打印預(yù)覽前調(diào)用,可用來(lái)初始化打印對(duì)話框 |
OnPrint |
用來(lái)打印或打印預(yù)覽文檔 |
OnUpdate |
用來(lái)通知一個(gè)視的關(guān)聯(lián)文檔內(nèi)容已經(jīng)變化 |
CDocTemplate |
MatchDocType |
確定文檔類型和文檔模板匹配時(shí)的可信程度 |
轉(zhuǎn)下頁(yè) |
續(xù)表 |
|
CreateNewDocument |
創(chuàng)建一個(gè)新的文檔 |
CreateNewFrame |
創(chuàng)建一個(gè)包含文檔和視的框架窗口 |
InitialUpdateFrame |
初始化框架窗口,必要時(shí)使它可見(jiàn) |
SaveAllModified |
保存所有和模板相關(guān)的而且修改了的文檔 |
CloseAllDocuments |
關(guān)閉所有和模板相關(guān)的文檔 |
OpenDocumentFile |
打開(kāi)指定路徑的文件 |
SetDefaultTitle |
設(shè)置文檔窗口缺省顯示的標(biāo)題 |
CDocument |
CanCloseFrame |
在關(guān)閉顯示該文檔的邊框窗口之前調(diào)用 |
DeleteContents |
用來(lái)清除文檔的內(nèi)容 |
OnChangedViewList |
在與文檔關(guān)聯(lián)的視圖被移走或新加入時(shí)調(diào)用 |
OnCloseDocument |
用來(lái)關(guān)閉文檔 |
OnNewDocument |
用來(lái)創(chuàng)建新文檔 |
OnOpenDocument |
用來(lái)打開(kāi)文檔 |
OnSaveDocument |
以來(lái)保存文檔 |
ReportSaveLoadException |
處理打開(kāi)、保存文檔操作失敗時(shí)的例外 |
GetFile |
返回一個(gè)指向Cfile對(duì)象的指針 |
ReleaseFile |
釋放一個(gè)文件以便其他應(yīng)用程序可以使用 |
SaveModified |
用來(lái)詢問(wèn)用戶文檔是否需要保存 |
PreCloseFrame |
在框架窗口關(guān)閉之前調(diào)用 |
- 消息映射方法和標(biāo)準(zhǔn)命令消息
窗口對(duì)象可以響應(yīng)以“WM_”為前綴的標(biāo)準(zhǔn)Windows消息,消息處理函數(shù)名稱以“ON”為前綴。不同類型的Windows窗口處理的Windows消息是有所不同的,因此,不同類型的MFC窗口實(shí)現(xiàn)的消息處理函數(shù)也有所不同。例如,多文檔邊框窗口能處理WM_MDIACTIVATE消息,其他類型窗口就不能。程序員從一定的MFC窗口派生自己的窗口類,對(duì)感興趣的消息,覆蓋基類的消息處理函數(shù),實(shí)現(xiàn)自己的消息處理函數(shù)。
所有的命令目標(biāo)(CCmdTarger或?qū)С鲱悓?duì)象)可以響應(yīng)命令消息,程序員可以指定應(yīng)用程序?qū)ο蟆⒖蚣艽翱趯?duì)象、視對(duì)象或文檔對(duì)象等來(lái)處理某條命令消息。一般地,盡量由與命令消息關(guān)系密切的對(duì)象來(lái)處理,例如隱藏/顯示工具欄由框架窗口處理,打開(kāi)文件由應(yīng)用程序?qū)ο筇幚?,?shù)據(jù)變化的操作由文檔對(duì)象處理。
對(duì)話框的控制子窗口可以響應(yīng)各類通知消息。
對(duì)于命令消息,MFC實(shí)現(xiàn)了一系列標(biāo)準(zhǔn)命令消息處理函數(shù)。標(biāo)準(zhǔn)命令I(lǐng)D在afxres.h中定義。表5-5列出了MFC標(biāo)準(zhǔn)命令的實(shí)現(xiàn),從ID或者函數(shù)名可以大致地看出該函數(shù)的目的、功用,具體的實(shí)現(xiàn)有的后續(xù)章節(jié)會(huì)講解,詳細(xì)參見(jiàn)MFC技術(shù)文檔。
程序員可以自己來(lái)處理這些標(biāo)準(zhǔn)消息,也可以通過(guò)不同的類或從不同的類導(dǎo)出自己的類來(lái)處理這些消息,不過(guò)最好遵循MFC的缺省實(shí)現(xiàn)。比如處理ID_FILE_NEW命令,最好由CWinApp的派生類處理。
表5-5 標(biāo)準(zhǔn)命令消息處理函數(shù)
ID |
函數(shù) |
實(shí)現(xiàn)函數(shù)的類 |
ID_FILE_NEW |
OnFileNew |
CWinApp |
ID_FILE_OPEN |
OnFileOpen |
CWinApp |
ID_FILE_CLOSE |
OnFileClose |
CDocument |
ID_FILE_SAVE |
OnFileSave |
CDocument |
ID_FILE_SAVE_AS |
OnFileSaveAs |
CDocument |
ID_FILE_SAVE_COPY_AS |
OnFileSaveCopyAs |
COleServerDoc |
ID_FILE_UPDATE |
OnUpdateDocument |
COleServerDoc |
ID_FILE_PAGE_SETUP |
OnFilePrintSetup |
CWinApp |
轉(zhuǎn)下頁(yè) |
續(xù)表 |
ID_FILE_PRINT |
OnFilePrint |
CView |
ID_FILE_PRINT_PREVIEW |
OnFilePrintPreview |
CView |
ID_FILE_MRU_FILE1...FILE16 |
OnUpdateRecentFileMenu |
CWinApp |
ID_EDIT_CLEAR |
|
CView沒(méi)有實(shí)現(xiàn), |
ID_EDIT_CLEAR_ALL |
|
但是,如果有實(shí)現(xiàn) |
ID_EDIT_COPY |
|
函數(shù),就是派生類 |
ID_EDIT_CUT |
|
CEditView的 |
ID_EDIT_FIND |
|
實(shí)現(xiàn)函數(shù) |
ID_EDIT_PASTE_LINK |
|
|
ID_EDIT_PASTE_SPECIAL |
|
|
ID_EDIT_REPEAT |
|
|
ID_EDIT_REPLACE |
|
|
ID_EDIT_SELET_ALL |
|
|
ID_EDIT_UNDO |
|
|
ID_WINDOW_NEW |
OnWindowNew |
CMDIFrameWnd |
ID_WINDOW_ARRANGE |
OnMDIWindowCmd |
CMDIFrameWnd |
ID_WINDOW_CASCADE |
|
|
ID_WINDOW_TILE_HORZ |
|
|
ID_WINDOW_TILE_VERT |
|
|
ID_WINDOW_SPLIT |
|
CSplitterWnd |
ID_APP_ABOUT |
|
|
ID_APP_EXIT |
OnAppExit |
CWinApp |
ID_HELP_INDEX |
OnHelpIndex |
CWinApp |
ID_HELP_USING |
OnHelpUsing |
CWinApp |
ID_CONTEXT_HELP |
OnContextHelp |
CWinApp |
轉(zhuǎn)下頁(yè) |
續(xù)表 |
ID_HELP |
OnHelp |
CWinApp |
ID_DEFAULT_HELP |
OnHelpIndex |
CWinApp |
ID_NEXT_PANE |
OnNextPaneCmd |
CSplitterWnd |
ID_PREV_PANE |
OnNextPaneCmd |
CSplitterWnd |
ID_OLE_INSERT_NEW |
|
|
ID_OLE_EDIT_LINKS |
|
|
ID_OLE_VERB_FIRST...LAST |
|
|
ID_VIEW_TOOLBAR |
|
CFrameWnd |
ID_VIEW_STATUS_BAR |
|
CFrameWnd |
ID_INDICATOR_CAPS
ID_INDICATOR_NUM
ID_INDICATOR_SCRL
ID_INDICATOR_KANA |
OnUpdateKeyIndicator |
CFrameWnd |
- MFC對(duì)象的創(chuàng)建過(guò)程
應(yīng)用程序使用MFC的接口是把一些自己的特殊處理填入MFC框架,這些處理或者在應(yīng)用程序啟動(dòng)和初始化的時(shí)候被調(diào)用,或者在程序啟動(dòng)之后和用戶交互的過(guò)程中被調(diào)用,或者在程序退出和作清理工作的時(shí)候被調(diào)用。這三個(gè)階段中,和用戶交互階段是各個(gè)程序自己的事情,自然都不一樣,但是程序的啟動(dòng)和退出兩個(gè)階段是MFC框架所實(shí)現(xiàn)的,是MFC框架的一部分,各個(gè)程序都遵循同樣的步驟和規(guī)則。顯然,清楚MFC框架對(duì)這兩個(gè)階段的處理是很有必要的,它可以幫助深入理解MFC框架,更好地使用MFC框架,更有效地實(shí)現(xiàn)應(yīng)用程序特定的處理。
MFC程序啟動(dòng)和初始化過(guò)程就是創(chuàng)建MFC對(duì)象和Windows對(duì)象、建立各種對(duì)象之間的關(guān)系、把窗口顯示在屏幕上的過(guò)程,退出過(guò)程就是關(guān)閉窗口、銷毀所創(chuàng)建的Windows對(duì)象和MFC對(duì)象的過(guò)程。所以,下面要討論幾種常用MFC對(duì)象的結(jié)構(gòu),它們是構(gòu)成一個(gè)文檔-視模式應(yīng)用程序的重要部件。
- 應(yīng)用程序中典型對(duì)象的結(jié)構(gòu)
本節(jié)將主要分析應(yīng)用程序?qū)ο蟆⑽臋n對(duì)象、文檔模板等的數(shù)據(jù)結(jié)構(gòu)。通過(guò)考察類的結(jié)構(gòu),特別是成員變量結(jié)構(gòu),弄清它的功能、目的以及和其他類的關(guān)系;另外,在后續(xù)有關(guān)分析中必定會(huì)提到這些成員變量,這里先作個(gè)說(shuō)明,到時(shí)也不會(huì)顯得突兀。
下面幾節(jié)以表格的形式來(lái)描述各個(gè)類的成員變量。表格中,第一列打鉤的表示是MFC類庫(kù)文檔有說(shuō)明的;沒(méi)打鉤的在文檔中沒(méi)有說(shuō)明,如果是public,則可以直接訪問(wèn),但隨著MFC版本的變化,以后MFC可能不支持這些成員;第二列是訪問(wèn)屬性;第三列是成員變量名稱;第四列是成員變量的數(shù)據(jù)類型;第五列是對(duì)成員變量的功能、用途的簡(jiǎn)要描述。
- 應(yīng)用程序類的成員變量
應(yīng)用程序?qū)ο蟮臄?shù)據(jù)成員表由兩部分組成,第一部分是CWinThread的成員變量,如表5-6所示,CWinApp繼承了CWinThread的數(shù)據(jù)成員。第二部分是CWinApp自己定義的成員變量,如表5-7所示。
表5-6 CwinThread的成員變量
|
訪問(wèn)限制 |
變量名稱 |
類型 |
解釋 |
√ |
public |
m_bAutoDelete |
BOOL |
指定線程結(jié)束時(shí)是否銷毀線程對(duì)象本身 |
√ |
public |
m_hThread |
HANDLE |
當(dāng)前線程的句柄 |
√ |
public |
m_nThreadID |
UINT |
當(dāng)前線程的ID |
√ |
public |
m_pMainWnd |
CWnd* |
指向應(yīng)用程序主窗口的指針 |
√ |
public |
m_pActiveWnd |
CWnd* |
當(dāng)OLE SERVER就地激活時(shí)指向客戶程序主窗口的指針 |
|
public |
m_msgCur |
MSG |
當(dāng)前消息(MSG結(jié)構(gòu)) |
|
public |
m_pThreadParams |
LPVOID |
傳遞給線程開(kāi)始函數(shù)的參數(shù) |
|
public |
m_pfnThreadProc |
函數(shù)指針1 |
線程開(kāi)始函數(shù),AFX_THREADPROC類型 |
|
public |
m_lpfnOleTermOrFreeLib |
函數(shù)指針2 |
OLE用途,void (AFXAPI * fn)(BOOL,BOOL) |
|
public |
m_pMessageFilter |
指針 |
OLE消息過(guò)濾,指向COleMessageFilter對(duì)象 |
|
protected |
m_ptCursorLast |
CPoint |
最新鼠標(biāo)位置 |
|
protected |
m_nMsgLast |
UINT |
消息隊(duì)列中最新接收到的消息 |
表5-7 CWinApp的成員變量
|
訪問(wèn)限制 |
變量名稱 |
類型 |
解釋 |
√ |
public |
m_pszAppName |
LPCTSTR |
應(yīng)用程序名稱 |
√ |
public |
m_hInstance |
HINSTANCE |
標(biāo)志應(yīng)用程序當(dāng)前實(shí)例句柄 |
√ |
public |
m_hPrevInstance |
HINSTANCE |
32位程序設(shè)為空 |
√ |
public |
m_lpCmdLine |
LPTSTR |
指向應(yīng)用程序的命令行字符串 |
√ |
public |
m_nCmdShow |
int |
指定窗口開(kāi)始的顯示方式 |
√ |
public |
m_bHelpMode |
BOOL |
標(biāo)識(shí)用戶是否在上下文幫助模式 |
√ |
public |
m_pszExeName |
LPCTSTR |
應(yīng)用程序的模塊名 |
√ |
public |
m_pszHelpFilePath |
LPCTSTR |
應(yīng)用程序的幫助文件名,缺省時(shí)同模塊名 |
√ |
public |
m_pszProfileName |
LPCTSTR |
應(yīng)用程序的INI文件名,缺省時(shí)同應(yīng)用程序名 |
√ |
public |
m_pszRegistryKey |
LPCTSTR |
Register入口,如果不指定,使用INI文件。 |
|
public |
m_pDocManager; |
CDocManager * |
指向一個(gè)文檔模板管理器 |
|
protected |
m_hDevMode |
HGLOBAL |
打印設(shè)備模式 |
|
protected |
m_hDevNames |
HGLOBAL |
打印設(shè)備名稱 |
|
protected |
m_dwPromptContext |
DWORD |
被MESSAGE BOX覆蓋的幫助上下文 |
|
protected |
m_nWaitCursorCount |
int |
等待光標(biāo)計(jì)數(shù) |
|
protected |
m_hcurWaitCursorRestore |
HCURSOR |
保存的光標(biāo),在等待光標(biāo)之后恢復(fù) |
|
protected |
m_pRecentFileList |
指針 |
指向CRecentFileList對(duì)象,最近打開(kāi)的文件列表 |
|
public |
m_atomApp |
ATOM |
DDE用途 |
|
public |
m_atomSystemTopic |
m_atomApp |
DDE用途 |
|
public |
m_nNumPreviewPages |
UINT |
缺省被打印的頁(yè)面 |
|
public |
m_nSafetyPoolSize |
size_t |
理想尺寸 |
|
public |
m_lpfnDaoTerm |
函數(shù)指針 |
DAO初始化設(shè)置時(shí)使用 |
- CDocument的成員變量
表5-8 文檔對(duì)象的屬性。
|
訪問(wèn)限制 |
變量名稱 |
類型 |
解釋 |
|
protected |
m_strTitle |
CString |
文檔標(biāo)題 |
|
protected |
m_strPathName |
CString |
文檔路徑 |
|
protected |
m_pDocTemplate |
CDocTemplate* |
指向文檔模板的指針 |
|
protected |
m_viewList |
CPtrList |
關(guān)聯(lián)的視窗口列表 |
|
protected |
m_bModified |
BOOL |
文檔是否有變化、需要存盤 |
|
public |
m_bAutoDelete |
BOOL |
關(guān)聯(lián)視都關(guān)閉時(shí)是否刪除文檔對(duì)象 |
|
public |
m_bEmbedded |
BOOL |
文檔是否由OLE創(chuàng)建 |
- 文檔模板的屬性
表5-9列出了文檔模板的成員變量,5-10列出了單文檔模板的成員變量,5-11列出了多文檔模板的成員變量。單、多文檔模板繼承了文檔模板的成員變量。
表5-9 文檔模板的數(shù)據(jù)成員
|
訪問(wèn)限制 |
變量名稱 |
類型 |
解釋 |
|
public |
m_bAutoDelete |
BOOL |
|
|
public |
m_pAttachedFactory |
CObject * |
|
|
public |
m_hMenuInPlace |
HMENU |
就地激活時(shí),OLE客戶程序的菜單 |
|
public |
m_hAccelInPlace |
HACCEL |
就地激活時(shí),OLE客戶程序的快捷鍵 |
|
public |
m_hMenuEmbedding |
HMENU |
|
|
public |
m_hAccelEmbedding |
HACCEL |
|
|
public |
m_hMenuInPlaceServer |
HMENU |
|
|
public |
m_hAccelInPlaceServer |
HACCEL |
|
|
protected |
m_nIDResource |
UINT |
框架、菜單、快捷鍵等的資源ID |
|
protected |
m_nIDServerResource |
UINT |
|
|
public |
m_nIDEmbeddingResource |
UINT |
|
|
public |
m_nIDContainerResource |
UINT |
|
|
public |
m_pDocClass |
CRuntimeClass* |
指向文檔類的動(dòng)態(tài)創(chuàng)建信息 |
|
public |
m_pFrameClass |
CRuntimeClass* |
指向框架類的動(dòng)態(tài)創(chuàng)建信息 |
|
public |
m_pViewClass |
CRuntimeClass* |
指向視類的動(dòng)態(tài)創(chuàng)建信息,由字符串m_nIDResource描述 |
|
public |
m_pOleFrameClass |
CRuntimeClass* |
指向OLD框架類的動(dòng)態(tài)創(chuàng)建信息 |
|
public |
m_pOleViewClass |
CRuntimeClass* |
|
|
public |
m_strDocStrings |
CString |
描述該文檔類型的字符串 |
表5-10 單文檔模板的成員變量
|
訪問(wèn)限制 |
變量名稱 |
類型 |
解釋 |
|
protected |
m_pOnlyDoc |
CDocment* |
指向唯一的文檔對(duì)象 |
表5-11 單文檔模板的成員變量
|
訪問(wèn)限制 |
變量名稱 |
類型 |
解釋 |
|
public |
m_hMenuShared |
HMENU |
該模板的MDI子窗口的菜單 |
|
public |
m_hAccelTable |
HACCEL |
該模板的MDI子窗口的快捷鍵 |
|
protected |
m_docList |
CPtrList |
該模板的文檔列表 |
|
protected |
m_nUntitledCount |
UINT |
用來(lái)生成文件名的數(shù)字,如”untitled0”的0。 |
- WinMain入口函數(shù)
- WinMain流程
現(xiàn)在討論MFC應(yīng)用程序如何啟動(dòng)。
WinMain函數(shù)是MFC提供的應(yīng)用程序入口。進(jìn)入WinMain前,全局應(yīng)用程序?qū)ο笠呀?jīng)生成。WinMain流程如圖5-3所示。圖中,灰色框是對(duì)被調(diào)用的虛擬函數(shù)的注釋,程序員可以或必須覆蓋它以實(shí)現(xiàn)MFC要求的或用戶希望的功能;大括號(hào)所包含的圖示是相應(yīng)函數(shù)流程的細(xì)化,有應(yīng)用程序?qū)ο驛pp的初始化、Run函數(shù)的實(shí)現(xiàn)、PumpMessage的流程,等等。
象的創(chuàng)建.files/image120.gif)
從圖中可以看出:
(1)一些虛擬函數(shù)被調(diào)用的時(shí)機(jī)
對(duì)應(yīng)用程序類(線程類)的InitIntance、ExitInstance、Run、ProcessMessageFilter、OnIdle、PreTranslateMessage來(lái)說(shuō),InitInstance在應(yīng)用程序初始化時(shí)調(diào)用,ExitInstance在程序退出時(shí)調(diào)用,Run在程序初始化之后調(diào)用導(dǎo)致程序進(jìn)入消息循環(huán),ProcessMessageFilter、OnIdle、PreTranslateMessage在消息循環(huán)時(shí)被調(diào)用,分別用來(lái)過(guò)濾消息、進(jìn)行Idle處理、讓窗口預(yù)處理消息。
(2)應(yīng)用程序?qū)ο蟮慕巧?/P>
首先,應(yīng)用程序?qū)ο蟮某蓡T函數(shù)InitInstance被WinMain調(diào)用。對(duì)程序員來(lái)說(shuō),它就是程序的入口點(diǎn)(真正的入口點(diǎn)是WinMain,但MFC向程序員隱藏了WinMain的存在)。由于MFC沒(méi)有提供InitInstance的缺省實(shí)現(xiàn),用戶必須自己實(shí)現(xiàn)它。稍后將討論該函數(shù)的實(shí)現(xiàn)。
其次,通過(guò)應(yīng)用程序?qū)ο蟮腞un函數(shù),程序進(jìn)入消息循環(huán)。實(shí)際上,消息循環(huán)的實(shí)現(xiàn)是通過(guò)CWinThread::Run來(lái)實(shí)現(xiàn)的,圖中所示的是CWinThread::Run的實(shí)現(xiàn),因?yàn)镃WinApp沒(méi)有覆蓋Run的實(shí)現(xiàn),程序員的應(yīng)用程序類一般也不用覆蓋該函數(shù)。
(3)Run所實(shí)現(xiàn)的消息循環(huán)
它調(diào)用PumpMessage來(lái)實(shí)現(xiàn)消息循環(huán),如果沒(méi)消息,則進(jìn)行空閑(Idle)處理。如果是WM_QUIT消息,則調(diào)用ExitInstance后退出消息循環(huán)。
(4)CWinThread::PumpMessage
該函數(shù)在MFC函數(shù)文檔里沒(méi)有描述,但是MFC建議用戶使用。它實(shí)現(xiàn)獲取消息,轉(zhuǎn)換(Translate)消息,發(fā)送消息的消息循環(huán)。在轉(zhuǎn)換消息之前,調(diào)用虛擬函數(shù)PreTranslateMessage對(duì)消息進(jìn)行預(yù)處理,該函數(shù)得到消息目的窗口對(duì)象之后,使用CWnd的WalkPreTranslateTree讓目的窗口及其所有父窗口得到一個(gè)預(yù)處理當(dāng)前消息的機(jī)會(huì)。關(guān)于消息預(yù)處理,見(jiàn)消息映射的有關(guān)章節(jié)。如果是WM_QUIT消息,PumpMessage返回FALSE;否則返回TRUE。
- MFC空閑處理
MFC實(shí)現(xiàn)了一個(gè)Idle處理機(jī)制,就是在沒(méi)有消息可以處理時(shí),進(jìn)行Idle處理。Idle處理的一個(gè)應(yīng)用是更新用戶接口對(duì)象的狀態(tài)。更新用戶接口狀態(tài)的內(nèi)容見(jiàn)消息映射的章節(jié)。
- 空閑處理由函數(shù)OnIdle完成,其原型為BOOL OnIdle(int)。參數(shù)的含義是當(dāng)前空閑處理周期已經(jīng)完成了多少次OnIdle調(diào)用,每個(gè)空閑處理周期的第一次調(diào)用,該參數(shù)設(shè)為0,每調(diào)用一次加1;返回值表示當(dāng)前空閑處理周期是否繼續(xù)調(diào)用OnIdle。
- MFC的缺省實(shí)現(xiàn)里,CWinThread::OnIdle完成了工具欄等的狀態(tài)更新。如果覆蓋OnIdle,需要調(diào)用基類的實(shí)現(xiàn)。
- 在處理完一個(gè)消息或進(jìn)入消息循環(huán)時(shí),如果消息隊(duì)列中沒(méi)有消息要處理,則MFC開(kāi)始一個(gè)新的空閑處理周期;
- 當(dāng)OnIdle返回FASLE,或者消息隊(duì)列中有消息要處理時(shí),當(dāng)前的空閑處理周期結(jié)束。
從圖5-3中Run的流程上可以清楚的看到MFC空閑處理的情況。
本節(jié)描述了應(yīng)用程序從InitInstance開(kāi)始初始化、從Run進(jìn)入消息循環(huán)的過(guò)程,下面將就SDI應(yīng)用程序的例子描述該過(guò)程中創(chuàng)建各個(gè)所需MFC對(duì)象的流程。
- SDI應(yīng)用程序的對(duì)象創(chuàng)建
如前一節(jié)所述,程序從InitInstance開(kāi)始。在SDI應(yīng)用程序的InitInstance里,至少有以下語(yǔ)句:
//第一部分,創(chuàng)建文檔模板對(duì)象并把它添加到應(yīng)用程序的模板鏈表
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CTDoc),
RUNTIME_CLASS(CMainFrame), // main SDI frame window
RUNTIME_CLASS(CTView));
AddDocTemplate(pDocTemplate);
//第二部分,動(dòng)態(tài)創(chuàng)建文檔、視、邊框窗口等MFC對(duì)象和對(duì)應(yīng)的Windows對(duì)象
//Parse command line for standard shell commands, DDE, file open
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
// Dispatch commands specified on the command line
if (!ProcessShellCommand(cmdInfo))
return FALSE;
//第三部分,返回TRUE,WinMain下一步調(diào)用Run開(kāi)始消息循環(huán),
//否則,終止程序
return TRUE;
對(duì)于第二部分,又可以分解成許多步驟。
下面將解釋每一步。
- 文檔模板的創(chuàng)建
第一步是創(chuàng)建文檔模板。
文檔模板的作用是動(dòng)態(tài)創(chuàng)建其他MFC對(duì)象,它保存了要?jiǎng)討B(tài)創(chuàng)建類的動(dòng)態(tài)創(chuàng)建信息和該文檔類型的資源ID。這些信息保存在文檔模板的成員變量里:m_nIDResource(資源ID)、m_pDocClass(文檔類動(dòng)態(tài)創(chuàng)建信息)、m_pFrameClass(邊框窗口類動(dòng)態(tài)創(chuàng)建信息)、m_pViewClass(視類動(dòng)態(tài)創(chuàng)建信息)。
資源ID包括菜單、像標(biāo)、快捷鍵、字符串資源的ID,它們都使用同一個(gè)ID值,如IDR_MAINFRAME。其中,字符串資源描述了文檔類型,由七個(gè)被“\n”分隔的子字符串組成,各個(gè)子串可以通過(guò)CDocTemplate的成員函數(shù)GetDocString(CString& rString, enum DocStringIndex index)來(lái)獲取。DocStringIndex是CDocTemplate類定義的枚舉變量以區(qū)分七個(gè)子串,描述如下(英文是枚舉變量名稱)。
WindowTitle 應(yīng)用程序窗口的標(biāo)題。僅僅對(duì)SDI程序指定。
DocName 用來(lái)構(gòu)造缺省文檔名的字符串。當(dāng)用File菜單的菜單項(xiàng)new創(chuàng)建新文檔時(shí),缺省文檔名由該字符串加一個(gè)數(shù)字構(gòu)成。如果空,使用“unitled”。
FileNewName 文檔類型的名稱,在打開(kāi)File New對(duì)話框時(shí)顯示。
FilterName 匹配過(guò)濾字符串,在File Open對(duì)話框用來(lái)過(guò)濾要顯示的文件。如果不指定,F(xiàn)ile Open對(duì)話框的文件類型(file style)不可訪問(wèn)。
FilterExt 該類型文檔的擴(kuò)展名。如果不指定,則不可訪問(wèn)對(duì)話框的文件類型(File Style)。
RegFileTypeId 文檔類型在Windows 注冊(cè)庫(kù)中的存儲(chǔ)標(biāo)識(shí)。
RegFileTypeName 文檔類型在Windows 注冊(cè)庫(kù)中的類型名稱。
文檔模板被應(yīng)用程序?qū)ο髣?chuàng)建和管理。應(yīng)用程序類CWinApp有一個(gè)CDocManager類型的成員變量m_pDocManager,通過(guò)該變量來(lái)管理應(yīng)用程序的文檔模板列表,把一些相關(guān)的操作委派給CDocManager對(duì)象處理。
CDocManager使用CPtrList類型的m_templateList變量來(lái)存儲(chǔ)文檔模板,并提供了操作文檔模板列表的系列函數(shù)。
從語(yǔ)句pDocTemplate = new CSingleDocTemplate(…)可以看出應(yīng)用程序?qū)ο髣?chuàng)建模板時(shí)傳遞一個(gè)資源ID和三個(gè)類的動(dòng)態(tài)創(chuàng)建信息給它:
IDR_MAINFRAME,資源ID
RUNTIME_CLASS(CTDoc),文檔類動(dòng)態(tài)創(chuàng)建信息
RUNTIME_CLASS(CMainFrame),邊框窗口類動(dòng)態(tài)創(chuàng)建信息
RUNTIME_CLASS(CTView),視類動(dòng)態(tài)創(chuàng)建信息
文檔模板對(duì)象接收這些信息并把它們保存到對(duì)應(yīng)的成員變量里頭。然后AddDocTemplate實(shí)際調(diào)用m_pDocManager->AddDocTemplate,把創(chuàng)建的模板對(duì)象加入到文檔模板管理器的模板列表中,也就是應(yīng)用程序?qū)ο蟮奈臋n模板列表中。
- 文件的創(chuàng)建或者打開(kāi)
第二步是創(chuàng)建或者打開(kāi)文件。
對(duì)于SDI程序,MFC對(duì)象的動(dòng)態(tài)創(chuàng)建過(guò)程是在創(chuàng)建或者打開(kāi)文件中發(fā)生的。但是為什么沒(méi)有看到文件操作相關(guān)的語(yǔ)句呢?
- CCommandLineInfo
首先,需要弄清楚類CcommandLineInfo,它是用來(lái)處理命令行信息的類,CWinApp::PareCommandLine調(diào)用CCommandLineInfo的成員函數(shù)ParseParm分析啟動(dòng)程序時(shí)的參數(shù),把分析結(jié)果保存在CCommandLineInfo對(duì)象的成員變量里。CCommandLineInfo的定義如下:
class CCommandLineInfo : public CObject
{
BOOL m_bShowSplash;
BOOL m_bRunEmbedded;
BOOL m_bRunAutomated;
enum { FileNew, FileOpen, FilePrint, FilePrintTo, FileDDE,
AppUnregister, FileNothing = -1 } m_nShellCommand;
// not valid for FileNew
CString m_strFileName;
// valid only for FilePrintTo
CString m_strPrinterName;
CString m_strDriverName;
CString m_strPortName;
};
由上述定義可以看出,分析結(jié)果分幾類:是否OLE激活;應(yīng)該執(zhí)行什么動(dòng)作(FileNew、FileOpen等);傳遞的參數(shù)(打開(kāi)或打印的文件名,打印設(shè)備、端口等)。
當(dāng)命令行空時(shí),執(zhí)行FileNew命令。原因在于CCommandLineInfo的缺省構(gòu)造函數(shù):
CCommandLineInfo::CCommandLineInfo()
{
m_bShowSplash = TRUE;
m_bRunEmbedded = FALSE;
m_bRunAutomated = FALSE;
m_nShellCommand = FileNew;//指定了SHELL命令操作
}
缺省構(gòu)造把應(yīng)該執(zhí)行的動(dòng)作指定為FileNew。
- 處理命令行命令
其次,分析 CWinApp::ProcessShellCommand(CCommandLineInfo& rCmdInfo)的流程,它處理命令行的命令,流程如圖5-3所示。
圖5-4第三層表示根據(jù)命令類型進(jìn)一步調(diào)用的函數(shù),都是CWinApp或者派生類的成員函數(shù)。對(duì)于FILEDDE類型沒(méi)有進(jìn)一步的調(diào)用。
命令類型是FILENEW時(shí),調(diào)用的函數(shù)就是標(biāo)準(zhǔn)命令I(lǐng)D_FILE_NEW對(duì)應(yīng)的處理函數(shù)OnFileNew;命令類型是FILEOPEN時(shí)調(diào)用的函數(shù)是OpenDocumentFile,標(biāo)準(zhǔn)命令I(lǐng)D_FILE_OPEN的處理函數(shù)OnFileOpen的工作實(shí)際上就是由OpenDocumentFile完成的。函數(shù)FileNew、OpenDocumentFile導(dǎo)致了窗口、文檔的創(chuàng)建。
- OnFileNew
接著,分析 CWinApp::OnFileNew流程,如圖5-5所示。
圖5-5的說(shuō)明:
應(yīng)用程序?qū)ο蟮玫轿臋n模板管理器指針,調(diào)用文檔模板管理器的成員函數(shù)OnFileNew(m_pDocManager->OnFileNew());模板管理器獲取文檔模板對(duì)象指針,調(diào)用文檔模板對(duì)象的OpenDocumentFile 函數(shù)(pTemplate->OpenDocumentFile(NULL))。如果模板管理器發(fā)現(xiàn)有多個(gè)文檔模板,就彈出一個(gè)對(duì)話框讓用戶選擇文檔模板。這里和后面的圖解中類似于CWinApp::、CDocManager::、CDocTemplate::等的函數(shù)類屬限制并不表示直接源碼中有這樣的限制,而是通過(guò)指針或者指針的動(dòng)態(tài)約束可以認(rèn)定調(diào)用了某個(gè)類的成員函數(shù),其正確性僅僅限于本書圖解的MFC的缺省實(shí)現(xiàn)。
如圖5-5所示,程序員可以覆蓋有關(guān)虛擬函數(shù)或命令處理函數(shù):如果程序員在自己的應(yīng)用程序類中覆蓋了OnFileNew,則可以實(shí)現(xiàn)完全不同的處理流程;一般情況下,不會(huì)從文檔模板類派生新類,如果派生的話,可以覆蓋CDocTemplate的虛擬函數(shù)。
- OnFileOpen
分析了 OnFileNew后,現(xiàn)在分析CWinApp::OnFileOpen(),其流程如圖5-6所示。
CWinApp::OnFileOpen和OnFileNew類似,不過(guò),第二步須得到一個(gè)要打開(kāi)的文件的名稱,第三步調(diào)用的是應(yīng)用程序?qū)ο蟮腛penDocumentFile,而不是文檔模板對(duì)象的該函數(shù)。
- 應(yīng)用程序?qū)ο蟮腛penDocumentFile
分析應(yīng)用程序的打開(kāi)文檔函數(shù): CWinApp::OpenDocumentFile(LPCSTR name),其流程如圖5-7所示。
應(yīng)用程序?qū)ο蟀汛蜷_(kāi)文件操作委托給文檔模板管理器,后者又委托給文檔模板對(duì)象來(lái)執(zhí)行。如果是SDI程序,則委托給單文檔對(duì)象;如果是MDI程序,則委托給多文檔對(duì)象──這是由指針?biāo)笇?duì)象的實(shí)際類型決定的,因?yàn)樵摵瘮?shù)是一個(gè)虛擬函數(shù)。
- 文檔模板的OpenDocumentFile
不論是FileNew還是FileOpen,最后的操作都?xì)w結(jié)到由文檔模板來(lái)打開(kāi)文件(文件名空則創(chuàng)建文件)。
CSingleDocTemplate::OpenDocumentFile(lpcstr name,BOOL visible)的流程見(jiàn)圖5-8。有一點(diǎn)需要指出的是:創(chuàng)建了一個(gè)文檔對(duì)象,并不等于打開(kāi)了一個(gè)文檔(件)或者創(chuàng)建了一個(gè)新文檔(件)。
圖5-8顯示的流程大致可以描述如下:
如果已經(jīng)有文檔打開(kāi),則保存當(dāng)前的文檔;否則,文檔對(duì)象還沒(méi)有創(chuàng)建,需要?jiǎng)?chuàng)建一個(gè)新的文檔對(duì)象。因?yàn)檫@時(shí)邊框窗口還沒(méi)有生成,所以還要?jiǎng)?chuàng)建邊框窗口對(duì)象(MFC對(duì)象)和邊框窗口。MFC邊框窗口對(duì)象動(dòng)態(tài)創(chuàng)建,HWND邊框窗口由LoadFrame創(chuàng)建。MFC邊框窗口被創(chuàng)建時(shí),CFrameWnd的缺省構(gòu)造函數(shù)被調(diào)用,它把正創(chuàng)建的對(duì)象(this所指)加入到模塊-線程狀態(tài)的邊框窗口列表m_frameList之首。
邊框窗口創(chuàng)建過(guò)程中由CreateView動(dòng)態(tài)創(chuàng)建MFC視對(duì)象和HWND視窗口。
接著,如果沒(méi)有指定要打開(kāi)的文件名,則創(chuàng)建一個(gè)新的文件;否則,則打開(kāi)文件,并使用序列化機(jī)制讀入文件內(nèi)容。
通過(guò)上述過(guò)程,動(dòng)態(tài)地創(chuàng)建了MFC邊框窗口對(duì)象、視對(duì)象、文檔對(duì)象以及對(duì)應(yīng)的Windows對(duì)象,并填寫了有關(guān)對(duì)象的成員變量,建立起這些MFC對(duì)象的關(guān)系。
- 打開(kāi)文件過(guò)程中所涉及的消息處理函數(shù)和虛擬函數(shù)
圖5-8描述的整個(gè)過(guò)程中系列消息處理函數(shù)和虛擬函數(shù)被調(diào)用。例如:在Windwos邊框窗口和視窗口被創(chuàng)建時(shí)會(huì)產(chǎn)生WM_CREATE等消息,導(dǎo)致OnCreate等消息處理函數(shù)的調(diào)用,CFrameWnd和CView都覆蓋了該函數(shù),所以在邊框窗口和視窗口的創(chuàng)建中,同樣的消息調(diào)用了不同的處理函數(shù)CFrameWnd::OnCreate和CView::OnCreate。
圖5-8涉及的幾個(gè)虛擬函數(shù)的流程分別由圖5-9、圖5-10圖解。圖5-9表示CDocument的OnNewDocument的流程;圖5-10表示CDocument的OpenDocument的流程。這兩個(gè)函數(shù)分別在創(chuàng)建新文檔或者打開(kāi)一個(gè)文檔時(shí)被調(diào)用。從流程可以看出,對(duì)于OpenDocument函數(shù),MFC的缺省實(shí)現(xiàn)主要用來(lái)設(shè)置修改標(biāo)識(shí)、序列化讀入打開(kāi)文檔的內(nèi)容。圖5-10顯示了序列化的操作過(guò)程:
首先,使用文檔對(duì)象打開(kāi)或者創(chuàng)建的文件句柄創(chuàng)建一個(gè)用于讀出數(shù)據(jù)的CArchive對(duì)象loadarchive;然后使用它通過(guò)Serialize進(jìn)行序列化操作,完畢,CArchive對(duì)象被自動(dòng)銷毀,文件句柄被關(guān)閉。
從這些圖中可以看到何時(shí)、何處調(diào)用了什么消息處理函數(shù)和虛擬函數(shù),這些函數(shù)用來(lái)作了什么事情。必要的話,程序員可以在派生類覆蓋它們。
在創(chuàng)建工作完成之后,進(jìn)行初始化,使用文檔對(duì)象的數(shù)據(jù)來(lái)更新視和顯示窗口。
至此,本節(jié)描述了MFC的SDI程序從分析命令行到創(chuàng)建或打開(kāi)文件的處理過(guò)程,文檔對(duì)象已經(jīng)動(dòng)態(tài)創(chuàng)建。總結(jié)如下:
命令行分析→應(yīng)用程序的FileNew→文檔模板的OpenDocumentFile(NULL)→文檔的OnNewDocument
命令行分析→應(yīng)用程序的FileOPen→文檔模板的OpenDocumentFile(filename)→文檔的OpenDocument
邊框窗口對(duì)象、視對(duì)象的動(dòng)態(tài)創(chuàng)建和對(duì)應(yīng) Windows對(duì)象的創(chuàng)建從LoadFrame開(kāi)始,這些將在下一節(jié)論述。
- SDI邊框窗口的創(chuàng)建
第三步是創(chuàng)建SDI邊框窗口。
圖5-8已經(jīng)分析了創(chuàng)建SDI邊框窗口的時(shí)機(jī)和創(chuàng)建方法,下面,從LoadFrame開(kāi)始分析整個(gè)窗口創(chuàng)建過(guò)程。
- CFrameWnd::LoadFrame
CFrameWnd::LoadFrame的流程如圖5-11所示,其原型如下:
BOOL CFrameWnd::LoadFrame(UINT nIDResource,
DWORD dwDefaultStyle,
CWnd* pParentWnd,
CCreateContext* pContext)
第一個(gè)參數(shù)是和該框架相關(guān)的資源ID,包括字符串、快捷鍵、菜單、像標(biāo)等;
第二個(gè)參數(shù)指定框架窗口的“窗口類”和窗口風(fēng)格;此處創(chuàng)建SDI窗口時(shí)和缺省值相同,為WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE;
第三個(gè)參數(shù)指定框架窗口的父窗口,此處和缺省值相同,為NULL;
第四個(gè)參數(shù)指定創(chuàng)建的上下文,如圖5-8所示由CreateNewFrame生成了該變量并傳遞給LoadFrame。其缺省值為NULL。
創(chuàng)建上下文結(jié)構(gòu)的定義:
struct CCreateContext
{
CRuntimeClass* m_pNewViewClass; //View的動(dòng)態(tài)創(chuàng)建信息
CDocument*m_pCurrentDoc;//指向一文檔對(duì)象,將和新創(chuàng)建視關(guān)聯(lián)
//用來(lái)創(chuàng)建MDI子窗口的信息(C MDIChildFrame::LoadFrame使用)
CDocTemplate* m_pNewDocTemplate;
// for sharing view/frame state from the original view/frame
CView* m_pLastView;
CFrameWnd* m_pCurrentFrame;
};
這里,傳遞給LoadFrame的CCreateContext變量是:
(視的動(dòng)態(tài)創(chuàng)建信息,新創(chuàng)建的文檔對(duì)象,當(dāng)前文檔模板,NULL,NULL)。
其中,“新創(chuàng)建的文檔對(duì)象”就是圖 5-8中創(chuàng)建的那個(gè)文檔對(duì)象。從此圖中還可以看到,LoadFrame被CreateNewFrame調(diào)用,CreateNewFrame是文檔模板的成員函數(shù),被文檔模板的成員函數(shù)OpenDocumentFile所調(diào)用,所以,LoadFrame間接地被文檔模板調(diào)用,“當(dāng)前文檔模板”就是調(diào)用它的模板對(duì)象。順便指出,對(duì)SDI程序來(lái)說(shuō)是這樣的,對(duì)MDI程序有所不同?!耙暤膭?dòng)態(tài)創(chuàng)建信息”也是文檔模板傳遞過(guò)來(lái)的。
對(duì)圖5-11的說(shuō)明:
在創(chuàng)建邊框窗口之前,先注冊(cè)“窗口類”。LoadFrame注冊(cè)了兩個(gè)“窗口類”,一個(gè)為邊框窗口,一個(gè)為視窗口。關(guān)于“窗口類”注冊(cè),見(jiàn)2.2.1節(jié)。
注冊(cè)窗口類之后,創(chuàng)建邊框窗口,并加載資源。創(chuàng)建邊框窗口使用了CFrameWnd的Create虛擬函數(shù),最終調(diào)用::CreateEx創(chuàng)建窗口。::CreateEx有11個(gè)參數(shù),其最后一個(gè)參數(shù)就是文檔模板傳遞給LoadFrame的CCreateContext類型的指針,該指針將被傳遞給窗口過(guò)程,進(jìn)一步由Windows傳遞給OnCreate函數(shù)。順便指出,創(chuàng)建完畢的邊框窗口的窗口過(guò)程是統(tǒng)一的MFC窗口過(guò)程。
創(chuàng)建邊框窗口時(shí),發(fā)送消息WM_NCCREATE和WM_CREATE,導(dǎo)致對(duì)應(yīng)的消息處理函數(shù)OnNcCreate和OnCreate被調(diào)用。CWnd提供了OnNcCreate處理非客戶區(qū)創(chuàng)建消息,CFrameWnd沒(méi)有處理該消息,但是提供了OnCreate處理消息WM_CREATE。OnCreate將創(chuàng)建視對(duì)象和視窗口。
- CFrameWnd::OnCreate
按創(chuàng)建工作的進(jìn)度,現(xiàn)在要討論邊框窗口創(chuàng)建消息(WM_CREATE)的處理了,處理函數(shù)是CFrameWnd的OnCreate,其原型如下:
int CFrameWnd::OnCreate(LPCREATESTRUCT lpcs)
其中,參數(shù)指向一個(gè)CreateStruct結(jié)構(gòu)(關(guān)于CreateStruct的描述見(jiàn)4.4.1節(jié)),它包含了窗口創(chuàng)建參數(shù)的副本,也就是說(shuō)CreaeEx窗口創(chuàng)建函數(shù)的11個(gè)參數(shù)被對(duì)應(yīng)地復(fù)制到該結(jié)構(gòu)的11個(gè)域,例如它的第一個(gè)成員就可以轉(zhuǎn)換成CCreateContext類型的指針。
函數(shù)OnCreate處理WM_CREATE消息,它從lpcs指向的結(jié)構(gòu)中分離出lpCreateParams并把它轉(zhuǎn)換成為CCreateContext類型的指針pContext,然后,調(diào)用OnCreateHelp(lpcs,pContext),把創(chuàng)建工作委派給它完成。
CFrameWnd::OnCreateHelp的原型如下,流程見(jiàn)圖5-11。
int CFrameWnd::OnCreateHelp(LPCREATESTRUCT lpcs,
CCreateContext* pContext)
說(shuō)明:由于CFrameWnd覆蓋了消息處理函數(shù)OnCreate來(lái)處理WM_CREATE消息,所以CWnd就失去了處理該消息的機(jī)會(huì),除非CFrameWnd::OnCreate主動(dòng)調(diào)用基類的該消息處理函數(shù)。圖5-11展示了對(duì)CWnd::OnCreate的調(diào)用。
在邊框窗口被創(chuàng)建之后,調(diào)用虛擬函數(shù)OnCreateClient(lpcs,pContext),它的缺省實(shí)現(xiàn)將創(chuàng)建視對(duì)象和視窗口。
最后,在狀態(tài)欄顯示“Ready”字樣,調(diào)用RecalcLayout安排工具欄等的位置。關(guān)于WM_SETMESSAGESTRING消息和RecalcLayout函數(shù),見(jiàn)工具欄有關(guān)13.2.3節(jié)。
到此,SDI的邊框窗口已經(jīng)被創(chuàng)建。下一節(jié)將描述視的創(chuàng)建。
- 視的創(chuàng)建
第四步,創(chuàng)建視。
如前一節(jié)所述,若CFrameWnd::OnCreateClient(lpcs,pContext)判斷pContext包含了視的動(dòng)態(tài)創(chuàng)建信息,則調(diào)用函數(shù)CreateView創(chuàng)建視對(duì)象和視窗口。CreateView的原型如下,其流程如圖5-13所示。
CWnd * CFrameWnd::CreateView(CCreateContext* pContext, UINT nID)
其中:
第一個(gè)參數(shù)是創(chuàng)建上下文;
第二個(gè)參數(shù)是創(chuàng)建視 (子窗口)的ID,缺省是AFX_IDW_PANE_FIRST,這里等同缺省值。
說(shuō)明:
CreateView調(diào)用了CWnd的Create函數(shù)創(chuàng)建HWND視窗口,視的子窗口ID是AFX_IDW_PANE_FIRST,父窗口是創(chuàng)建它的邊框窗口。創(chuàng)建視窗口時(shí)的WM_CREATE、WM_NCCREATE消息分別被CView、CWnd的相關(guān)消息處理函數(shù)處理。處理情況如圖5-13所述,這里不作進(jìn)一步的討論。
到此,文檔對(duì)象、邊框窗口對(duì)象、視窗口對(duì)象已經(jīng)創(chuàng)建,文件已經(jīng)打開(kāi)或者創(chuàng)建,邊框窗口、視窗口已經(jīng)創(chuàng)建?,F(xiàn)在,是顯示和定位窗口、顯示文檔數(shù)據(jù)的時(shí)候了,這些通過(guò)調(diào)用CFrameWnd的虛擬函數(shù)InitialUpdateFrame完成,如圖5-8所示。
- 窗口初始化
這是第五步,初始化邊框窗口、視窗口等。
InitialUpdateFrame的原型如下:
void CFrameWnd::InitialUpdateFrame(CDocument* pDoc, BOOL bMakeVisible)
其中:
第一個(gè)參數(shù)是當(dāng)前的文檔對(duì)象;
第二個(gè)參數(shù)表示邊框窗口是否應(yīng)該可見(jiàn)并激活。
該函數(shù)是在文檔模板的OpenDocumentFile中調(diào)用的,傳遞過(guò)來(lái)的第一個(gè)參數(shù)是剛才創(chuàng)建的文檔,第二個(gè)參數(shù)是TRUE,見(jiàn)圖5-8。
InitialUpdateFrame的處理過(guò)程參見(jiàn)圖5-14,解釋如下:
首先,如果當(dāng)前邊框窗口沒(méi)有活動(dòng)視,則獲取ID為AFX_IDW_PANE_FIRST的視pView。如果該視不存在,則pView=NULL;否則(pView!=NULL),調(diào)用成員函數(shù)SetActiveView(pView,F(xiàn)ALSE)把它設(shè)置為活動(dòng)視,參數(shù)2為FALSE表示并不通知它成為活動(dòng)視(見(jiàn)圖5-14)。
然后,如果InitialUpdateFrame的參數(shù)bMakeVisible為TRUE,則給所有邊框窗口的視發(fā)送WM_INITIALUPDATE消息,通知它們?cè)陲@示之前使用文檔數(shù)據(jù)來(lái)初始化視。這導(dǎo)致視類的虛擬函數(shù)OnInitUpdate被調(diào)用,該函數(shù)又調(diào)用OnUpdate來(lái)完成初始化。其他子窗口(如狀態(tài)欄、工具欄)也將收到WM_INITIALUPDATE消息,導(dǎo)致它們更新?tīng)顟B(tài)。
其三,調(diào)用pView->OnActivateFrame(WA_INACTIVE,this)給活動(dòng)視(若存在的話)一個(gè)機(jī)會(huì)來(lái)保存當(dāng)前焦點(diǎn)。這里,解釋這個(gè)函數(shù):
void CView::OnActivateFrame( UINT nState,CFrameWnd* pFrameWnd );
其中,參數(shù)1取值為WA_INACTIVE/WA_ACTIVE/WA_CLICKACTIVE,具體見(jiàn)消息WM_ACTIVE的解釋;參數(shù)2指向被激活的框架窗口。
視對(duì)象通過(guò)該虛擬函數(shù)在它所屬的邊框窗口被激活或者失去激活時(shí)作一些特別的處理,例如,CFormView用它來(lái)保存或者恢復(fù)輸入焦點(diǎn)控制。
其四,在OnActivateFrame之后,調(diào)用成員函數(shù)ActivateFrame激活框架窗口。這個(gè)過(guò)程將產(chǎn)生一個(gè)消息WM_ACTIVE(處理該消息的過(guò)程在下一節(jié)作解釋),它導(dǎo)致OnActiveTopLevel和OnActive被調(diào)用。接著,如果活動(dòng)視非空,則調(diào)用成員函數(shù)OnActivateView激活它。
至此,參數(shù)bMakeVisible為TRUE時(shí)顯示窗口的處理完畢。
最后,如果參數(shù)pDoc非空,則更新邊框窗口計(jì)數(shù),更新邊框窗口的標(biāo)題。更新邊框窗口計(jì)數(shù)是為了在一個(gè)文檔對(duì)應(yīng)多個(gè)視的情況下,給顯示同一文檔的不同文檔邊框窗口編號(hào),編號(hào)從1開(kāi)始,保存在邊框窗口的成員變量m_nWindow里。例如有兩個(gè)邊框?qū)?yīng)一個(gè)文檔tt1,則它們的標(biāo)題分別為“tt1:1”、“tt1:2”。如果只有一個(gè)文檔只對(duì)應(yīng)一個(gè)邊框窗口,則成員變量m_nWindow等于-1,標(biāo)題不含編號(hào),如“tt1”。
當(dāng)然,對(duì)于SDI應(yīng)用程序,不存在一個(gè)文檔對(duì)應(yīng)多個(gè)視的情況。上述情況是針對(duì)MDI應(yīng)用程序而言的。SDI應(yīng)用程序執(zhí)行該過(guò)程時(shí),相當(dāng)于MDI程序的一個(gè)特例。
圖 5-14涉及的一些函數(shù)由圖5-15、5-15圖解。
圖5-14中的函數(shù)SetActiveView的圖解如圖5-15所示,其原型如下,:
void CFrameWnd::SetActiveView(CView * pViewNew, BOOL bNotify = TRUE)
其中:
參數(shù)1指向被設(shè)置的視對(duì)象,若非視類型的對(duì)象,則為NULL;
參數(shù) 2表示是否通知被設(shè)置的視。
圖5-15中的變量m_pViewActive是CFrameWnd的成員變量,用來(lái)保存邊框窗口的活動(dòng)視。
圖5-15中的流程可以概括為:Deactivate當(dāng)前視(m_pViewActive非空時(shí));設(shè)置當(dāng)前活動(dòng)視;若參數(shù)bNotify為TRUE,通知pViewNew被激活。
圖5-14中的函數(shù)ActivateFrame圖解如圖5-16所示,其原型如下,:
void CFrameWnd::ActivateFrame(UINT nCmdShow)
參數(shù)nCmdShow用來(lái)傳遞給CWnd::ShowWindow,指定顯示窗口的方式。參數(shù)缺省為1,圖5-14調(diào)用時(shí)設(shè)置為-1。
該函數(shù)用來(lái)激活(Activate)和恢復(fù)(Restore)邊框窗口,使得它對(duì)用戶可見(jiàn)可用。在初始化、OLE事件、DDE事件等需要顯示邊框窗口的地方調(diào)用。圖5-16表示的MFC缺省實(shí)現(xiàn)是激活邊框窗口并把它放到頂層。
程序員可以覆蓋該虛擬函數(shù)ActivateFrame來(lái)控制邊框窗口怎樣被激活。
圖5-16中的函數(shù)BringToTop是CFrameWnd內(nèi)部使用的成員函數(shù)(protected)。它調(diào)用::BringWindowToTop把窗口放到Z軸上的頂層。
至此,邊框窗口初始化的過(guò)程已經(jīng)描述清楚,視的初始化見(jiàn)下一節(jié)。
- 視的初始化
第六步,在邊框窗口初始化之后,初始化視。
如圖5-14所示,視、工具條窗口處理消息WM_INITAILUPDATE(MFC內(nèi)部消息),完成初始化。這里只討論視的消息處理函數(shù),其原型如下:
void CView::OnInitialUpdate()
圖5-14對(duì)該函數(shù)的注釋說(shuō)明了該函數(shù)的特殊之處。其缺省實(shí)現(xiàn)是調(diào)用OnUpdate(NULL, 0, NULL)更新視??梢愿采wOnInitialUpdate實(shí)現(xiàn)自己的初始化。
OnUpdate是一個(gè)虛擬函數(shù),其原型如下:
void CView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)
其中:
參數(shù)1指向修改文檔數(shù)據(jù)的視;若更新所有的視,設(shè)為NULL;
參數(shù)2是一個(gè)包含了修改信息的long型變量;
參數(shù)3指向一個(gè)包含修改信息的對(duì)象(從CObject派生的對(duì)象)。
參數(shù)2、參數(shù)3是在文檔更新對(duì)應(yīng)視的時(shí)候傳遞過(guò)來(lái)的。
該函數(shù)用來(lái)更新顯示視窗口,反映文檔的變化,在MFC中,它為函數(shù)CView::OnInitialUpdate和CDocument::UpdateAllViews所調(diào)用。其缺省實(shí)現(xiàn)是使整個(gè)客戶區(qū)無(wú)效。在下一次收到WM_PAINT消息時(shí),重繪無(wú)效區(qū)。
工具條的初始化見(jiàn)討論第13章。
- 激活邊框窗口(處理WM_ACTIVE)
第七步,在窗口初始化完成之后,激活并顯示出來(lái)。
下面討論邊框窗口激活時(shí)的處理(對(duì)WM_ACTIVE的處理)。
- WM_ACTIVE的消息參數(shù)
wParam的低階word指示窗口是被激活還是失去激活:WA_ACTIVE,被鼠標(biāo)點(diǎn)擊以外的方法激活;WA_CLICKACTIVE,由鼠標(biāo)點(diǎn)擊激活;WA_INACTIVE,失去激活;
wParam的高階word指示窗口是否被最小化;非零表示最小化;
lPararm表示將激活的窗口句柄(WA_INACTIVE),或者將失去激活的窗口句柄(WA_CLICKACTIVE、WA_ACTIVE)。
在標(biāo)準(zhǔn)Windows消息處理的章節(jié)中,曾指出處理WM_ACTIVE消息時(shí),先要調(diào)用一個(gè)函數(shù)_AfxHandleActivate,此函數(shù)的原型如下:
static void AFXAPI _AfxHandleActivate(CWnd* pWnd,
WPARAM nState,CWnd* pWndOther)
其中:
參數(shù)1是接收消息的窗口;
參數(shù)2是窗口狀態(tài),為WM_ACTIVE的消息參數(shù)wParam;
參數(shù)3是WM_ACTIVE的消息參數(shù)lParam表示的窗口。
_AfxHandleActivate是MFC內(nèi)部使用的函數(shù),聲明和實(shí)現(xiàn)均在WinCore.CPP文件中。實(shí)現(xiàn)如下:
如果pWnd指向的窗口不是子窗口,而且pWnd和pWndOther窗口的頂級(jí)父窗口(TopLevelParent)不是同一窗口,則發(fā)送MFC定義的消息WM_ACTIVATETOPLEVEL給pWnd的頂級(jí)窗口,消息參數(shù)wParam是nState,消息參數(shù)lParam指向一個(gè)長(zhǎng)度為二的數(shù)組,數(shù)組里存放pWnd和pWndOther所指窗口的句柄。否則,_AfxHandleActivate不作什么。
從這里可以看出:只有頂層的主邊框窗口能處理WM_ACTIVE消息,事實(shí)上,Windows系統(tǒng)只會(huì)給頂層的非子窗口發(fā)送WM_ACTIVE消息。
- WM_ACTIVATETOPLEVEL消息的處理
CWnd及派生類CFrameWnd實(shí)現(xiàn)了對(duì)WM_ACTIVATETOPLEVEL消息的處理,分別解釋如下:
消息處理函數(shù)CWnd::OnActivateTopLevel如果失去激活,則取消工具欄的提示(TOOLTIP)。
消息處理函數(shù)CFrameWnd::OnActivateTopLevel調(diào)用CWnd的OnActivateTopLevel;如果接收WM_ACTIVE消息的窗口是線程主窗口,則使得其活動(dòng)的視窗口變成非活動(dòng)的(OnActive(FALSE, pActiveView,pActiveView)。
從這里可以知道,在頂層窗口接收到WM_ACTIVE消息后,MFC會(huì)進(jìn)行一些固定的處理,然后才調(diào)用WM_ACTIVE消息處理函數(shù)。
- WM_ACTIVE消息的處理
在_AfxHandleActivate和WM_ACTIVATETOPLEVEL消息處理完之后,才是對(duì)WM_ACTIVE的處理。CWnd和CFrameWnd都實(shí)現(xiàn)了消息處理。
CWnd的消息處理函數(shù):
void CWnd::OnActive(UINT nState, CWnd* pWndOther, BOOL bMinimized)
其中:
參數(shù)1取值為WA_INACTIVE/WA_ACTIVE/WA_CLICKACTIVE;
參數(shù)2指向激活或者失去激活的窗口,具體同WM_ACTIVE消息;
參數(shù)3表示是否最小化。
此函數(shù)的實(shí)現(xiàn)是調(diào)用Default(),作缺省處理。
CFrameWnd的消息處理函數(shù):
void CFrameWnd::OnActive(UINT nState,CWnd* pWndOther, BOOL bMinimized)
首先調(diào)用CWnd::OnActivate。
如果活動(dòng)視非空,消息是WA_ACTIVE/WA_CLICKACTIVE,并且不是最小化,則重新激活當(dāng)前視,調(diào)用了以下函數(shù):
pActiveView->OnActiveView(TRUE,pActiveView,pActiveView);
并且,如果活動(dòng)視非空,通知它邊框窗口狀態(tài)的變化(激活/失去激活),調(diào)用以下函數(shù):
pActiveView->OnActivateFrame(nState, this)。
- SDI流程的回顧
從InitialInstance開(kāi)始,首先應(yīng)用程序?qū)ο髣?chuàng)建文檔模板,文檔模板創(chuàng)建文檔對(duì)象、打開(kāi)或創(chuàng)建文件;然后,文檔模板創(chuàng)建邊框窗口對(duì)象和邊框窗口;接著邊框窗口對(duì)象創(chuàng)建視對(duì)象和視窗口。這些創(chuàng)建是以應(yīng)用程序的文檔模板為中心進(jìn)行的。在創(chuàng)建這些MFC對(duì)象的同時(shí),建立了它們之間的關(guān)系。創(chuàng)建這些之后,進(jìn)行初始化,激活主邊框窗口,把邊框窗口、視窗口顯示出來(lái)。
這樣,一個(gè)SDI應(yīng)用程序就完成了啟動(dòng)過(guò)程,等待著用戶的交互或者輸入。
5.3.4節(jié)將在SDI程序啟動(dòng)流程的基礎(chǔ)之上,介紹MDI應(yīng)用程序的啟動(dòng)流程。兩者的相同之處可以這樣類比:創(chuàng)建SDI邊框窗口、視、文檔的過(guò)程和創(chuàng)建MDI文檔邊框窗口、視、文檔的過(guò)程類似。不同之處主要表現(xiàn)在:主邊框窗口的創(chuàng)建不一樣;MDI有文檔邊框窗口的創(chuàng)建,SDI沒(méi)有;SDI只能一個(gè)文檔、一個(gè)視;MDI可能多文檔、多個(gè)視。
- MDI程序的對(duì)象創(chuàng)建
MDI應(yīng)用程序?qū)ο蟮腎nitialInstance函數(shù)一般含有以下代碼:
//第一部分:創(chuàng)建和添加模板
CMultiDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate(
IDR_TTTYPE,
RUNTIME_CLASS(CTtDoc),
RUNTIME_CLASS(CChildFrame),//custom MDI child frame
RUNTIME_CLASS(CTtView));
AddDocTemplate(pDocTemplate);
//第二部分:創(chuàng)建MFC框架窗口對(duì)象和Windows主邊框窗口
// 創(chuàng)建主MDI邊框窗口
CMainFrame* pMainFrame = new CMainFrame;
if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
return FALSE;
m_pMainWnd = pMainFrame;
//第三部分:處理命令行,命令行空則執(zhí)行OnFileNew創(chuàng)建新文檔
//分析命令行
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
// 處理命令行命令
if (!ProcessShellCommand(cmdInfo))
return FALSE;
第四部分:顯示和更新主框架窗口
// 主窗口已被初始化,現(xiàn)在顯示和更新主窗口
pMainFrame->ShowWindow(m_nCmdShow);
pMainFrame->UpdateWindow();
SDI應(yīng)用程序?qū)ο蟮腎nitialInstance和SDI應(yīng)用程序?qū)ο蟮腎nitialInstance比較,有以下的相同和不同之處。相同之處在于:
創(chuàng)建和添加模板;處理命令行。
不同之處在于:
- 創(chuàng)建的模板類型不同。SDI使用單文檔模板,邊框窗口類從CFrameWnd派生;MDI使用多文檔模板,邊框窗口類從CMDIChildWnd派生.
- 主窗口類型不同。SDI的是從CFrameWnd派生的類;MDI的是從CMDIFrameWnd派生的類。
- 主框架窗口的創(chuàng)建方式不同。SDI在創(chuàng)建或者打開(kāi)文檔時(shí)動(dòng)態(tài)創(chuàng)建主窗口對(duì)象,然后加載主窗口(LoadFrame)并初始化;MDI使用第二部分的語(yǔ)句來(lái)創(chuàng)建動(dòng)態(tài)主窗口對(duì)象和加載主窗口,第四部分語(yǔ)句顯示、更新主窗口。
- 命令行處理的用途不一樣。SDI一定要有命令行處理部分的代碼,因?yàn)樗鼘?dǎo)致了主窗口的創(chuàng)建;MDI可以去掉這部分代碼,因?yàn)樗闹鞔翱诘膭?chuàng)建、顯示等由第二、四部分的語(yǔ)句來(lái)處理。
- 有別于SDI的主窗口加載過(guò)程
和SDI應(yīng)用程序一樣,MDI應(yīng)用程序使用LoadFrame加載主邊框窗口,但因?yàn)長(zhǎng)oadFrame的虛擬屬性,所以MDI調(diào)用了CMDIFrameWnd的LoadFrame函數(shù),而不是CFrameWnd的LoadFrame。
LoadFrame的參數(shù)1指定了資源ID,其余幾個(gè)參數(shù)取缺省值。和SDI相比,第四個(gè)創(chuàng)建上下文參數(shù)為NULL,因?yàn)镸DI主窗口不需要文檔、視等的動(dòng)態(tài)創(chuàng)建信息。
圖 5-17圖解了CMdiFrameWnd::LoadFrame的流程:
首先,用同樣的參數(shù)調(diào)用基類CFrameWnd的LoadFrame,其流程如圖5-11所示,但由于參數(shù)4表示的創(chuàng)建上下文為空,所以,CFrameWnd::LoadFrame在加載了菜單和快捷鍵之后,給所有子窗口發(fā)送WM_INITUPDATE消息。
另外,WM_CREATE消息怎樣處理呢?由于CMDIFrameWnd沒(méi)有覆蓋OnCreate,所以還是由基類CFrameWnd::OnCreate處理。但是它調(diào)用虛擬函數(shù)OnCreateClient(見(jiàn)圖5-12)時(shí),由于CMDIFrameWnd覆蓋了該函數(shù),所以動(dòng)態(tài)約束的結(jié)果是CMDIFrameWnd::OnCreateClient被調(diào)用,它和基類的OnCreateClient不同,后者CreateView創(chuàng)建MFC視對(duì)象和視窗口,前者調(diào)用虛擬函數(shù)CreateClient創(chuàng)建MDI客戶窗口。MDI客戶窗口負(fù)責(zé)創(chuàng)建和管理MDI子窗口。
CreateClient是CMDIFrameWnd的虛擬函數(shù),其原型如下:
BOOL CMDIFrameWnd::CreateClient(
LPCREATESTRUCT lpCreateStruct, CMenu* pWindowMenu);
該函數(shù)主要用來(lái)創(chuàng)建MDI客戶區(qū)窗口。它使用Windows系統(tǒng)預(yù)定義的“mdiclient”窗口類來(lái)創(chuàng)建客戶區(qū)窗口,保存該窗口句柄在CMDIFrameWnd的成員變量m_hWndMDIClient中。調(diào)用::CreateWindowEx創(chuàng)建客戶窗口時(shí)傳遞給它的窗口創(chuàng)建數(shù)據(jù)參數(shù)(第11個(gè)參數(shù))是一個(gè)CLIENTCREATESTRUCT結(jié)構(gòu)類型的參數(shù),該結(jié)構(gòu)指定了一個(gè)菜單和一個(gè)子窗口ID:
typedef struct tagCLIENTCREATESTRUCT{
HMENU hWindowMenu;
UINT idFirstChild;
}CLIENTCREATESTRUCT;
hWindowMenu表示主邊框窗口菜單欄上的“Windows彈出菜單項(xiàng)”的句柄。MDICLIENT類客戶窗口在創(chuàng)建MDI子窗口時(shí),把每一個(gè)子窗口的標(biāo)題加在這個(gè)彈出菜單的底部。idFirstChild是第一個(gè)被創(chuàng)建的MDI子窗口的ID號(hào),第二個(gè)MDI子窗口ID號(hào)為idFirstChild+1,依此類推。
這里,hWindowMenu的指定不是必須的,程序員可以在MDI子窗口激活時(shí)進(jìn)行菜單的處理;idFirstChild的值是AFX_IDM_FIRST_MDICHILD。
綜合地講,CMDIFrameWnd::LoadFrame完成創(chuàng)建MDI主邊框窗口和MDI客戶區(qū)窗口的工作。
創(chuàng)建了MDI邊框窗口和客戶區(qū)窗口之后,接著是處理WM_INITUPDATE消息,進(jìn)行初始化。但是按SDI應(yīng)用程序的討論順序,下一節(jié)先討論MDI子窗口的創(chuàng)建。
- MDI子窗口、視、文檔的創(chuàng)建
和SDI應(yīng)用程序類似,MDI應(yīng)用程序通過(guò)文檔模板來(lái)動(dòng)態(tài)創(chuàng)建MDI子窗口、視、文檔對(duì)象。不同之處在于:這里使用了多文檔模板,調(diào)用的是CMDIChildWnd(或派生類)的消息處理函數(shù)和虛擬函數(shù),如果它覆蓋了CFrameWnd的有關(guān)函數(shù)的話。
還是以處理標(biāo)準(zhǔn)命令消息ID_FILE_NEW的OnFileNew為例。
表示OnFileNew的圖5-5、表示OnFileOpen的圖5-6在多文檔應(yīng)用程序中仍然適用,但表示OpenDocumentFile的圖5-8有所不同,其第三步中地單文檔模板應(yīng)當(dāng)換成多文檔模板,關(guān)于這一點(diǎn),參閱圖5-8的說(shuō)明。
(1)多文檔模板的OpenDocumentFile
MDI的OpenDocumentFile的原型如下:
CDocument* CMultiDocTemplate::OpenDocumentFile(
LPCTSTR lpszPathName, BOOL bMakeVisible);
它的原型和單文檔模板的該函數(shù)原型一樣,但處理流程比圖5-8要簡(jiǎn)單些:
第一,不用檢查是否已經(jīng)打開(kāi)了文檔;
第二,不用判斷是否需要?jiǎng)?chuàng)建框架窗口或者文檔對(duì)象,因?yàn)椴徽撔陆ㄟ€是打開(kāi)文檔都需要?jiǎng)?chuàng)建新的文檔框架窗口(MDI子窗口)和文檔對(duì)象。
除了這兩點(diǎn),其他處理步驟基本相同,調(diào)用同樣名字的函數(shù)來(lái)創(chuàng)建文檔對(duì)象和MDI子窗口。雖然是名字相同的函數(shù),但是參數(shù)的值可能有異,又由于C++的虛擬機(jī)制和MFC消息映射機(jī)制,這些函數(shù)可能來(lái)自不同層次類的成員函數(shù),因而導(dǎo)致有不同的處理過(guò)程和結(jié)果,即SDI創(chuàng)建了CFrameWnd類型的對(duì)象和邊框窗口;MDI則創(chuàng)建了CMDIChildWnd類型的對(duì)象和邊框窗口。不同之處解釋如下:
(2)CMDIChildWnd的虛擬函數(shù)LoadFrame
CMDIChildWnd::LoadFrame代替了圖5-8中的CFrameWnd::LoadFrame,兩者流程大致相同,可以參見(jiàn)圖5-11。但是它們用來(lái)創(chuàng)建窗口的函數(shù)不同。前者調(diào)用了函數(shù)CMDIChildWnd::Create(參數(shù)1…參數(shù)6);后者調(diào)用了CFrameWnd::Create(參數(shù)1…參數(shù)7)。
這兩個(gè)窗口創(chuàng)建函數(shù),雖然都是虛擬函數(shù),但是有很多不同之處:
- 前者是CMDIChildWnd定義的虛擬函數(shù),后者是CWnd定義的虛擬函數(shù);
- 前者在參數(shù)中指定了父窗口,即主創(chuàng)建窗口,后者的父窗口參數(shù)為NULL;
- 前者指定了WS_CHILD風(fēng)格,創(chuàng)建的是子窗口,后者創(chuàng)建一個(gè)頂層窗口;
- 前者給客戶窗口m_hWndMDIClient(CMDIFrameWnd的成員變量)發(fā)送WM_MDICREATE消息讓客戶窗口來(lái)創(chuàng)建MDI子窗口(主邊框窗口的子窗口是客戶窗口,客戶窗口的子窗口是MDI子窗口),后者調(diào)用::CreateEx函數(shù)來(lái)創(chuàng)建邊框窗口;
- 前者的窗口創(chuàng)建數(shù)據(jù)是指向MDICREATESTRUCT結(jié)構(gòu)的指針,該結(jié)構(gòu)的最后一個(gè)域存放一個(gè)指向CCreateContext結(jié)構(gòu)的指針,后者是指向CCreateContext結(jié)構(gòu)的指針。
MDICREATESTRUCT結(jié)構(gòu)的定義如下:
typedef struct tagMDICREATESTRUCT { // mdic
LPCTSTR szClass;
LPCTSTR szTitle;
HANDLE hOwner;
int x;
int y;
int cx;
int cy;
DWORD style;
LPARAM lParam;
}MDICREATESTRUCT;
該結(jié)構(gòu)的用處和CREATESTRUCT類似,只是它僅用于MDI子窗口的創(chuàng)建上,用來(lái)保存創(chuàng)建MDI子窗口時(shí)的窗口創(chuàng)建數(shù)據(jù)。域lParam保存一個(gè)指向CCreateContext結(jié)構(gòu)的指針。
- WM_CREATE的處理函數(shù)不同
創(chuàng)建MDI子窗口時(shí)發(fā)送的WM_CREATE消息由CMDIChildWnd的成員函數(shù)OnCreate(LPCREATESTRUCT lpCreateStruct)處理。
OnCreate函數(shù)僅僅從lpCreateStruct指向的數(shù)據(jù)中取出窗口創(chuàng)建數(shù)據(jù),即指向MDICREATESTRUCT結(jié)構(gòu)的指針,并從該結(jié)構(gòu)得到指向CCreateContext結(jié)構(gòu)的指針pContext,然后調(diào)用虛擬函數(shù)OnCreateHelper(lpCreateStruct,pContext)。
此處動(dòng)態(tài)約束的結(jié)果是調(diào)用了CFrameWnd的成員函數(shù)OnCreateHelper。SDI應(yīng)用程序的OnCreate也調(diào)用了CFrameWnd::OnCreateHelper,所以后面的處理(創(chuàng)建視等)可參見(jiàn)SDI的流程了。
待MDI子窗口、視、文檔對(duì)象創(chuàng)建完畢,多文檔模板的OpenDocumentFile也調(diào)用InitialUpdateFrame來(lái)進(jìn)行初始化。
- MDI子窗口的初始化和窗口的激活
(1)MDI子窗口的初始化
完成了 MDI子窗口、視、文檔的創(chuàng)建之后,多文檔模板的OpenDocumenFile調(diào)用邊框窗口的虛擬函數(shù)InitialUpdateFrame進(jìn)行初始化,該函數(shù)流程參見(jiàn)圖5-14。不過(guò),這里this指針指向CMDIChildWnd對(duì)象,由于C++虛擬函數(shù)的動(dòng)態(tài)約束,初始化過(guò)程調(diào)用了CMDIChildWnd的ActivateFrame函數(shù)(不是CFrameWnd的ActivateFrame),來(lái)顯示MDI子窗口,更新菜單等等,見(jiàn)圖5-18。
圖5-18的說(shuō)明:
第一,調(diào)用基類CFrameWnd的ActivateFrame顯示窗口時(shí),由于當(dāng)前窗口是文檔邊框窗口,所以沒(méi)有發(fā)送WM_ACTIVATE消息,而是發(fā)送消息WM_MDIACTIVATE。
第二,由于Windows不處理MDI子窗口的激活,所以必須由MFC或者程序員來(lái)完成。當(dāng)一個(gè)激活的MDI子窗口被隱藏后從可見(jiàn)變成不可見(jiàn),但它仍然是活動(dòng)的,這時(shí)需要把下一文檔邊框窗口激活以便用戶看到的就是激活的窗口。在沒(méi)有其他文檔邊框窗口時(shí),則把該隱藏的文檔邊框窗口標(biāo)記為“偽失去激活”。當(dāng)一個(gè)文檔邊框窗口從不可見(jiàn)變成可見(jiàn)時(shí),檢查變量m_bPseudoInactive,若真則該窗口從Windows角度看仍然是激活的,只需要調(diào)用OnMDIActivate把它改成“MFC激活”。OnMDIActivate把變量m_bPseudoInactive的值改變?yōu)镕ALSE。
至此,MDI子窗口初始化調(diào)用描述完畢。初始化將導(dǎo)致MDI窗口被顯示、激活。下面討論MDI子窗口的激活。
(2)MDI子窗口的激活
通過(guò)給客戶窗口發(fā)送消息WM_MDIACTIVATE來(lái)激活文檔邊框窗口??蛻舸翱诎l(fā)送WM_MDIACTIVATE消息給將被激活或者取消激活的MDI子窗口(文檔邊框窗口),這些子窗口調(diào)用消息處理函數(shù)OnMDIActivate響應(yīng)該消息WM_MDIACTIVATE。關(guān)于MDI消息,見(jiàn)表5-12。
用戶轉(zhuǎn)向一個(gè)子窗口(包括文檔邊框窗口)導(dǎo)致它的頂層(TOP LEVEL)邊框窗口收到WM_ACTIVATE消息而被激活,子窗口是文檔邊框窗口的話將收到WM_MDIACTIVATE消息。
但是,一個(gè)邊框窗口被其他方式激活時(shí),它的文檔邊框窗口不會(huì)收到WM_MDIACTIVATE消息,而是最近一次被激活的文檔邊框窗口收到WM_NCACTIVATE消息。該消息由CWnd::Default缺省處理,用來(lái)重繪文檔邊框窗口的標(biāo)題欄、邊框等等。
MDI子窗口用OnMDIActiveate函數(shù)處理WM_MDIACTIVATE消息。其原型如下:
void CMDIChildWnd::OnMDIActivate( BOOL bActivate,
CWnd* pActivateWnd,CWnd* pDeactivateWnd );
其中:
參數(shù)1表示是激活(TRUE),還是失去激活(FALSE);
參數(shù)2表示將被激活的MDI子窗口;
參數(shù)3表示將被失去激活的MDI子窗口;
簡(jiǎn)單地說(shuō),該函數(shù)把m_bPseudoInactive的值改變?yōu)镕ALSE,調(diào)用成員函數(shù)OnActivateView通知失去激活的子窗口的視它將失去激活,調(diào)用成員函數(shù)OnActivateView通知激活子窗口的視它將被激活。
至于MDI主邊框窗口,它還是響應(yīng)WM_ACTIVATE消息而被激活或相反。CMDIFrameWnd沒(méi)有提供該消息的處理函數(shù),它調(diào)用基類CFrameWnd的處理函數(shù)OnActivate。
現(xiàn)在,MDI應(yīng)用程序的啟動(dòng)過(guò)程描述完畢。
表5-12 MDI消息
消息 |
說(shuō)明 |
WM_MDIACTIVATE |
激活MDI Child窗口 |
WM_MDICASCADE |
CASCADE排列MDI Child窗口 |
WM_MDICREATE |
創(chuàng)建MDI Child窗口 |
WM_MDIDESTROY |
銷毀MDI Child窗口 |
WM_MDIGETACTIVE |
得到活動(dòng)的MDI Child窗口 |
WM_MDIICONARRANGE |
安排最小化了的MDI Child窗口 |
WM_MDIMAXIMIZE |
MDI Child窗口最大化 |
WM_MDINEXT |
激活Z軸順序的下一MDI Child窗口 |
WM_MDIREFRESHMENU |
根據(jù)當(dāng)前MDI Child窗口更新菜單 |
WM_MDIRESTORE |
恢復(fù)MDI Child窗口 |
WM_MDISETMENU |
根據(jù)當(dāng)前MDI Child窗口設(shè)置菜單 |
WM_MDITITLE |
TITLE安排MDI Child窗口 |