第五章 MFC對(duì)象的創(chuàng)建
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ū)ο螅臋n對(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ū)ο?div style="height:15px;">
文檔模板
文檔模板
文檔
文檔模板
邊框窗口
邊框窗口
視
交互作用關(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ū)ο?、文檔對(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的流程,等等。
從圖中可以看出:
(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ū)ο蟮慕巧?div style="height:15px;">
首先,應(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)建??偨Y(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)更新視。可以覆蓋OnInitialUpdate實(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窗口