MFC對象的創建
前面幾章介紹了MFC的核心概念和思想,即介紹了MFC對Windows對象的封裝方法和特點;MFC對象的動態創建、序列化;MFC消息映射機制。
現在,考查MFC的應用程序結構體系,即以文檔-視為核心的編程模式。學習本章,應該弄清楚以下問題:
MFC中諸多MFC對象的關系:應用程序對象,文檔對象,邊框窗口對象,文檔邊框窗口對象,視對象,文檔模板對象等。
MFC對象的創建和銷毀:由什麼對象創建或銷毀什麼對象,何時創建,何時銷毀?
MFC提供了那些接口來支持其編程模式?
MFC對象的關系
創建關系
這裡討論應用程序、文檔模板、邊框窗口、視、文檔等的創建關系。圖5-1大略地表示了創建順序,但表5-1更直接地顯示了創建與被創建的關系。
表5-1 MFC對象的創建關系
創建者
被創建的對象
應用程序對象
文檔模板
文檔模板
文檔
文檔模板
邊框窗口
邊框窗口
視
交互作用關系
應用程序對象有一個文檔模板列表,存放一個或多個文檔模板對象;文檔模板對象有一個打開文檔列表,存放一個或多個已經打開的文檔對象;文檔對象有一個視列表,存放顯示該文檔數據的一個或多個視對象;還有一個指針指向創建該文檔的文檔模板對象;視有一個指向其關聯文檔的指針,視是一個子窗口,其父窗口是邊框窗口(或者文檔邊框窗口);文檔邊框窗口有一個指向其當前活動視的指針;文檔邊框窗口是邊框窗口的子窗口。
Windows 管理所有已經打開的窗口,把消息或事件發送給目標窗口。通常,命令消息發送給主邊框窗口。
圖5-2大略地表示了上述關系:
MFC提供了一些函數來維護這些關系。
表5-2列出了從一個對象得到相關對象的方法。
表5-2 從一個對象得到另一個對象的方法
本對象
要得到的對象
使用的成員函數
CDocument對象
視列表
GetFirstViewPosition
GetNextView
文檔模板
GetDocTemplate
CView對象
文檔對象
GetDocument
邊框窗口
GetParentFrame
CMDIChildWnd或
CFrameWnd對象
活動視
GetActiveView
活動視的文檔
GetActiveDocument
CMDIFrameWnd對象
活動文檔邊框窗口
MDIGetActive
表5-3 從一個對象通知另一個對象的方法:
本對象
要通知的對象/動作
使用的成員函數
CView對象
通知文檔更新所有視
CDocument::UpdateAllViews
CDocument對象
更新一個視
CView::OnUpdate
CFrameWnd或
CMDIFrameWnd對象
通知一個視為活動視
CView::OnActivateView
設置一個視為活動視
SetActivateView
可以通過表5-2得到相關對象,再調用表5-3中相應的函數。例如:視在接受了新數據或者數據被修改之後,使用表5-2中的函數GetDocument得到關聯文檔對象,然後調用表5-3中的文檔函數UpdateAllViews更新其他和文檔對象關聯的視。
在表5-2和表5-3中,CView對象指CView或派生類的實例;成員函數列中如果沒有指定類屬,就是第一列對象的類的成員函數。
MFC提供的接口
MFC編程就是把一些應用程序特有的東西填入MFC框架。MFC提供了兩種填入的方法:一種就是使用前一章論述的消息映射,消息映射給應用程序的各種對象處理各種消息的機會;另一種就是使用虛擬函數,MFC在實現許多功能或者處理消息、事件的過程中,調用了虛擬函數來完成一些任務,這樣就給了派生類覆蓋這些虛擬函數實現特定處理的機會。
下面兩節將列出兩類接口,有兩個目的:一是為了讓讀者獲得整體印象,二是後文將涉及到或者討論其中的許多函數時,不顯得突兀。
虛擬函數接口
幾乎每一個MFC類都定義和使用了虛擬成員函數,程序員可以在派生類中覆蓋它們。一般,MFC提供了這些函數的缺省實現,所以覆蓋函數應該調用基類的實現。這裡給出一個MFC常用虛擬函數的總覽表(見表5-4),更詳細的信息或它們的缺省實現動作參見MFC文檔。由於基類的虛擬函數被派生類繼承,所以在派生類中不作重復說明。
覆蓋基類的虛擬函數可以通過ClassWizard進行,不過,並非所有的函數都可以這樣,有的必須手工加入函數聲明和實現。
表5-4 常見MFC類的虛擬函數接口
類
虛擬函數
覆蓋的目的和功能
CCmdTarget
OnCmdMsg
發送、派發命令消息
OnFinalRelease
OLE用途,引用為0時作清理工作
CWinThread
ExitInstance
在線程退出時作清理工作
InitInstance
在線程開始時作初始化
OnIdle
執行thread-specific idle-time處理
PreTranslateMessage
在消息送給Windows函數TranslateMessage and DispatchMessage.之前進行消息過濾
IsIdleMessage
檢查是否是某個特別的消息
ProcessWndProcException
截獲線程消息/命令處理中的例外
ProcessMessageFilter
線程消息過濾
Run
實現線程特定的消息循環
CWinApp
HideApplication
關閉所有的窗口之前隱藏應用程序
CloseAllDocument
退出程序之前關閉所有文檔
轉下頁
續表
SaveModifiedDocument
框架窗口關閉時用來保存文檔
DoMessageBox
實現客戶化的messagebox
DoWaitCursor
關閉或打開等待光標
OnDDeCommand
響應DDE命令
WinHelp
調用WinHelp函數
CWnd
WindowProc
提供一個窗口過程
DefWindowProc
為應用程序不處理的消息提供缺省處理
PostNcDestroy
在窗口銷毀之後被消息處理函數OnNcDestroy調用
OnNotify
處理通知消息WM_NOTIFY
OnChildNotify
父窗口調用它給控制子窗口一個機會來處理通知反射消息
DoDataExchange
Updata調用它來進行對話框數據交換和驗證
CFrameWnd
GetMessageBar
返回一個指向框架窗口的狀態條的指針
OnCreateClient
創建框架的客戶窗口
OnSetPreviewMode
設置程序的主框架窗口進入或退出打印預覽模式
NegotiateBorderSpace
協調邊框窗口的邊框空間的大小(OLE用途)
CMDIFrameWnd
CreateClient
創建CMDIFrameWnd的MDICLIENT窗,被CWnd的消息處理函數OnCreate調用.
轉下頁
續表
GetWindowMenuPopup
返回窗口的彈出式菜單
CDialog
OnInitDialog
對話框窗口的初始化
OnSetFont
設置對話框控制的文本字體
OnOK
模式對話框的OK按鈕按下後進行的處理
OnCancel
模式對話框的CANCEL按鈕按下後進行的處理
CView
IsSelected
測試是否有一個文檔被選擇(OLE支持)
OnActivateView
視窗口激活時調用
OnActivateFrame
當包含視窗口的框架窗口變成活動或非活動窗口時調用
OnBeginPrinting
打印工作開始時調用,用來分配GDI資源
OnDraw
用來屏幕顯示、打印、打印預覽文檔內容
OnEndPrinting
打印工作結束時調用,釋放GDI資源
OnEndPrintPreview
退出打印預覽模式時調用
OnPrepareDC
OnDraw或OnPrint之前調用,用來准備設備描述表
OnPreparePrinting
文檔打印或者打印預覽前調用,可用來初始化打印對話框
OnPrint
用來打印或打印預覽文檔
OnUpdate
用來通知一個視的關聯文檔內容已經變化
CDocTemplate
MatchDocType
確定文檔類型和文檔模板匹配時的可信程度
轉下頁
續表
CreateNewDocument
創建一個新的文檔
CreateNewFrame
創建一個包含文檔和視的框架窗口
InitialUpdateFrame
初始化框架窗口,必要時使它可見
SaveAllModified
保存所有和模板相關的而且修改了的文檔
CloseAllDocuments
關閉所有和模板相關的文檔
OpenDocumentFile
打開指定路徑的文件
SetDefaultTitle
設置文檔窗口缺省顯示的標題
CDocument
CanCloseFrame
在關閉顯示該文檔的邊框窗口之前調用
DeleteContents
用來清除文檔的內容
OnChangedViewList
在與文檔關聯的視圖被移走或新加入時調用
OnCloseDocument
用來關閉文檔
OnNewDocument
用來創建新文檔
OnOpenDocument
用來打開文檔
OnSaveDocument
以來保存文檔
ReportSaveLoadException
處理打開、保存文檔操作失敗時的例外
GetFile
返回一個指向Cfile對象的指針
ReleaseFile
釋放一個文件以便其他應用程序可以使用
SaveModified
用來詢問用戶文檔是否需要保存
PreCloseFrame
在框架窗口關閉之前調用
消息映射方法和標准命令消息
窗口對象可以響應以“WM_”為前綴的標准Windows消息,消息處理函數名稱以“ON”為前綴。不同類型的Windows窗口處理的Windows消息是有所不同的,因此,不同類型的MFC窗口實現的消息處理函數也有所不同。例如,多文檔邊框窗口能處理WM_MDIACTIVATE消息,其他類型窗口就不能。程序員從一定的MFC窗口派生自己的窗口類,對感興趣的消息,覆蓋基類的消息處理函數,實現自己的消息處理函數。
所有的命令目標(CCmdTarger或導出類對象)可以響應命令消息,程序員可以指定應用程序對象、框架窗口對象、視對象或文檔對象等來處理某條命令消息。一般地,盡量由與命令消息關系密切的對象來處理,例如隱藏/顯示工具欄由框架窗口處理,打開文件由應用程序對象處理,數據變化的操作由文檔對象處理。
對話框的控制子窗口可以響應各類通知消息。
對於命令消息,MFC實現了一系列標准命令消息處理函數。標准命令ID在afxres.h中定義。表5-5列出了MFC標准命令的實現,從ID或者函數名可以大致地看出該函數的目的、功用,具體的實現有的後續章節會講解,詳細參見MFC技術文檔。
程序員可以自己來處理這些標准消息,也可以通過不同的類或從不同的類導出自己的類來處理這些消息,不過最好遵循MFC的缺省實現。比如處理ID_FILE_NEW命令,最好由CWinApp的派生類處理。
表5-5 標准命令消息處理函數
ID
函數
實現函數的類
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
轉下頁
續表
ID_FILE_PRINT
OnFilePrint
CView
ID_FILE_PRINT_PREVIEW
OnFilePrintPreview
CView
ID_FILE_MRU_FILE1...FILE16
OnUpdateRecentFileMenu
CWinApp
ID_EDIT_CLEAR
CView沒有實現,
ID_EDIT_CLEAR_ALL
但是,如果有實現
ID_EDIT_COPY
函數,就是派生類
ID_EDIT_CUT
CEditView的
ID_EDIT_FIND
實現函數
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
轉下頁
續表
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對象的創建過程
應用程序使用MFC的接口是把一些自己的特殊處理填入MFC框架,這些處理或者在應用程序啟動和初始化的時候被調用,或者在程序啟動之後和用戶交互的過程中被調用,或者在程序退出和作清理工作的時候被調用。這三個階段中,和用戶交互階段是各個程序自己的事情,自然都不一樣,但是程序的啟動和退出兩個階段是MFC框架所實現的,是MFC框架的一部分,各個程序都遵循同樣的步驟和規則。顯然,清楚MFC框架對這兩個階段的處理是很有必要的,它可以幫助深入理解MFC框架,更好地使用MFC框架,更有效地實現應用程序特定的處理。
MFC程序啟動和初始化過程就是創建MFC對象和Windows對象、建立各種對象之間的關系、把窗口顯示在屏幕上的過程,退出過程就是關閉窗口、銷毀所創建的Windows對象和MFC對象的過程。所以,下面要討論幾種常用MFC對象的結構,它們是構成一個文檔-視模式應用程序的重要部件。
應用程序中典型對象的結構
本節將主要分析應用程序對象、文檔對象、文檔模板等的數據結構。通過考察類的結構,特別是成員變量結構,弄清它的功能、目的以及和其他類的關系;另外,在後續有關分析中必定會提到這些成員變量,這裡先作個說明,到時也不會顯得突兀。
下面幾節以表格的形式來描述各個類的成員變量。表格中,第一列打鉤的表示是MFC類庫文檔有說明的;沒打鉤的在文檔中沒有說明,如果是public,則可以直接訪問,但隨著MFC版本的變化,以後MFC可能不支持這些成員;第二列是訪問屬性;第三列是成員變量名稱;第四列是成員變量的數據類型;第五列是對成員變量的功能、用途的簡要描述。
應用程序類的成員變量
應用程序對象的數據成員表由兩部分組成,第一部分是CWinThread的成員變量,如表5-6所示,CWinApp繼承了CWinThread的數據成員。第二部分是CWinApp自己定義的成員變量,如表5-7所示。
表5-6 CwinThread的成員變量
訪問限制
變量名稱
類型
解釋
√
public
m_bAutoDelete
BOOL
指定線程結束時是否銷毀線程對象本身
√
public
m_hThread
HANDLE
當前線程的句柄
√
public
m_nThreadID
UINT
當前線程的ID
√
public
m_pMainWnd
CWnd*
指向應用程序主窗口的指針
√
public
m_pActiveWnd
CWnd*
當OLE SERVER就地激活時指向客戶程序主窗口的指針
public
m_msgCur
MSG
當前消息(MSG結構)
public
m_pThreadParams
LPVOID
傳遞給線程開始函數的參數
public
m_pfnThreadProc
函數指針1
線程開始函數,AFX_THREADPROC類型
public
m_lpfnOleTermOrFreeLib
函數指針2
OLE用途,void (AFXAPI * fn)(BOOL,BOOL)
public
m_pMessageFilter
指針
OLE消息過濾,指向COleMessageFilter對象
protected
m_ptCursorLast
CPoint
最新鼠標位置
protected
m_nMsgLast
UINT
消息隊列中最新接收到的消息
表5-7 CWinApp的成員變量
訪問限制
變量名稱
類型
解釋
√
public
m_pszAppName
LPCTSTR
應用程序名稱
√
public
m_hInstance
HINSTANCE
標志應用程序當前實例句柄
√
public
m_hPrevInstance
HINSTANCE
32位程序設為空
√
public
m_lpCmdLine
LPTSTR
指向應用程序的命令行字符串
√
public
m_nCmdShow
int
指定窗口開始的顯示方式
√
public
m_bHelpMode
BOOL
標識用戶是否在上下文幫助模式
√
public
m_pszExeName
LPCTSTR
應用程序的模塊名
√
public
m_pszHelpFilePath
LPCTSTR
應用程序的幫助文件名,缺省時同模塊名
√
public
m_pszProfileName
LPCTSTR
應用程序的INI文件名,缺省時同應用程序名
√
public
m_pszRegistryKey
LPCTSTR
Register入口,如果不指定,使用INI文件。
public
m_pDocManager;
CDocManager *
指向一個文檔模板管理器
protected
m_hDevMode
HGLOBAL
打印設備模式
protected
m_hDevNames
HGLOBAL
打印設備名稱
protected
m_dwPromptContext
DWORD
被MESSAGE BOX覆蓋的幫助上下文
protected
m_nWaitCursorCount
int
等待光標計數
protected
m_hcurWaitCursorRestore
HCURSOR
保存的光標,在等待光標之後恢復
protected
m_pRecentFileList
指針
指向CRecentFileList對象,最近打開的文件列表
public
m_atomApp
ATOM
DDE用途
public
m_atomSystemTopic
m_atomApp
DDE用途
public
m_nNumPreviewPages
UINT
缺省被打印的頁面
public
m_nSafetyPoolSize
size_t
理想尺寸
public
m_lpfnDaoTerm
函數指針
DAO初始化設置時使用
CDocument的成員變量
表5-8 文檔對象的屬性。
訪問限制
變量名稱
類型
解釋
protected
m_strTitle
CString
文檔標題
protected
m_strPathName
CString
文檔路徑
protected
m_pDocTemplate
CDocTemplate*
指向文檔模板的指針
protected
m_viewList
CPtrList
關聯的視窗口列表
protected
m_bModified
BOOL
文檔是否有變化、需要存盤
public
m_bAutoDelete
BOOL
關聯視都關閉時是否刪除文檔對象
public
m_bEmbedded
BOOL
文檔是否由OLE創建
文檔模板的屬性
表5-9列出了文檔模板的成員變量,5-10列出了單文檔模板的成員變量,5-11列出了多文檔模板的成員變量。單、多文檔模板繼承了文檔模板的成員變量。
表5-9 文檔模板的數據成員
訪問限制
變量名稱
類型
解釋
public
m_bAutoDelete
BOOL
public
m_pAttachedFactory
CObject *
public
m_hMenuInPlace
HMENU
就地激活時,OLE客戶程序的菜單
public
m_hAccelInPlace
HACCEL
就地激活時,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*
指向文檔類的動態創建信息
public
m_pFrameClass
CRuntimeClass*
指向框架類的動態創建信息
public
m_pViewClass
CRuntimeClass*
指向視類的動態創建信息,由字符串m_nIDResource描述
public
m_pOleFrameClass
CRuntimeClass*
指向OLD框架類的動態創建信息
public
m_pOleViewClass
CRuntimeClass*
public
m_strDocStrings
CString
描述該文檔類型的字符串
表5-10 單文檔模板的成員變量
訪問限制
變量名稱
類型
解釋
protected
m_pOnlyDoc
CDocment*
指向唯一的文檔對象
表5-11 單文檔模板的成員變量
訪問限制
變量名稱
類型
解釋
public
m_hMenuShared
HMENU
該模板的MDI子窗口的菜單
public
m_hAccelTable
HACCEL
該模板的MDI子窗口的快捷鍵
protected
m_docList
CPtrList
該模板的文檔列表
protected
m_nUntitledCount
UINT
用來生成文件名的數字,如”untitled0”的0。
WinMain入口函數
WinMain流程
現在討論MFC應用程序如何啟動。
WinMain函數是MFC提供的應用程序入口。進入WinMain前,全局應用程序對象已經生成。WinMain流程如圖5-3所示。圖中,灰色框是對被調用的虛擬函數的注釋,程序員可以或必須覆蓋它以實現MFC要求的或用戶希望的功能;大括號所包含的圖示是相應函數流程的細化,有應用程序對象App的初始化、Run函數的實現、PumpMessage的流程,等等。
從圖中可以看出:
(1)一些虛擬函數被調用的時機
對應用程序類(線程類)的InitIntance、ExitInstance、Run、ProcessMessageFilter、OnIdle、PreTranslateMessage來說,InitInstance在應用程序初始化時調用,ExitInstance在程序退出時調用,Run在程序初始化之後調用導致程序進入消息循環,ProcessMessageFilter、OnIdle、PreTranslateMessage在消息循環時被調用,分別用來過濾消息、進行Idle處理、讓窗口預處理消息。
(2)應用程序對象的角色
首先,應用程序對象的成員函數InitInstance被WinMain調用。對程序員來說,它就是程序的入口點(真正的入口點是WinMain,但MFC向程序員隱藏了WinMain的存在)。由於MFC沒有提供InitInstance的缺省實現,用戶必須自己實現它。稍後將討論該函數的實現。
其次,通過應用程序對象的Run函數,程序進入消息循環。實際上,消息循環的實現是通過CWinThread::Run來實現的,圖中所示的是CWinThread::Run的實現,因為CWinApp沒有覆蓋Run的實現,程序員的應用程序類一般也不用覆蓋該函數。
(3)Run所實現的消息循環
它調用PumpMessage來實現消息循環,如果沒消息,則進行空閒(Idle)處理。如果是WM_QUIT消息,則調用ExitInstance後退出消息循環。
(4)CWinThread::PumpMessage
該函數在MFC函數文檔裡沒有描述,但是MFC建議用戶使用。它實現獲取消息,轉換(Translate)消息,發送消息的消息循環。在轉換消息之前,調用虛擬函數PreTranslateMessage對消息進行預處理,該函數得到消息目的窗口對象之後,使用CWnd的WalkPreTranslateTree讓目的窗口及其所有父窗口得到一個預處理當前消息的機會。關於消息預處理,見消息映射的有關章節。如果是WM_QUIT消息,PumpMessage返回FALSE;否則返回TRUE。
MFC空閒處理
MFC實現了一個Idle處理機制,就是在沒有消息可以處理時,進行Idle處理。Idle處理的一個應用是更新用戶接口對象的狀態。更新用戶接口狀態的內容見消息映射的章節。
空閒處理由函數OnIdle完成,其原型為BOOL OnIdle(int)。參數的含義是當前空閒處理周期已經完成了多少次OnIdle調用,每個空閒處理周期的第一次調用,該參數設為0,每調用一次加1;返回值表示當前空閒處理周期是否繼續調用OnIdle。
MFC的缺省實現裡,CWinThread::OnIdle完成了工具欄等的狀態更新。如果覆蓋OnIdle,需要調用基類的實現。
在處理完一個消息或進入消息循環時,如果消息隊列中沒有消息要處理,則MFC開始一個新的空閒處理周期;
當OnIdle返回FASLE,或者消息隊列中有消息要處理時,當前的空閒處理周期結束。
從圖5-3中Run的流程上可以清楚的看到MFC空閒處理的情況。
本節描述了應用程序從InitInstance開始初始化、從Run進入消息循環的過程,下面將就SDI應用程序的例子描述該過程中創建各個所需MFC對象的流程。
SDI應用程序的對象創建
如前一節所述,程序從InitInstance開始。在SDI應用程序的InitInstance裡,至少有以下語句:
//第一部分,創建文檔模板對象並把它添加到應用程序的模板鏈表
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CTDoc),
RUNTIME_CLASS(CMainFrame), // main SDI frame window
RUNTIME_CLASS(CTView));
AddDocTemplate(pDocTemplate);
//第二部分,動態創建文檔、視、邊框窗口等MFC對象和對應的Windows對象
//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下一步調用Run開始消息循環,
//否則,終止程序
return TRUE;
對於第二部分,又可以分解成許多步驟。
下面將解釋每一步。
文檔模板的創建
第一步是創建文檔模板。
文檔模板的作用是動態創建其他MFC對象,它保存了要動態創建類的動態創建信息和該文檔類型的資源ID。這些信息保存在文檔模板的成員變量裡:m_nIDResource(資源ID)、m_pDocClass(文檔類動態創建信息)、m_pFrameClass(邊框窗口類動態創建信息)、m_pViewClass(視類動態創建信息)。
資源ID包括菜單、像標、快捷鍵、字符串資源的ID,它們都使用同一個ID值,如IDR_MAINFRAME。其中,字符串資源描述了文檔類型,由七個被“ ”分隔的子字符串組成,各個子串可以通過CDocTemplate的成員函數GetDocString(CString& rString, enum DocStringIndex index)來獲取。DocStringIndex是CDocTemplate類定義的枚舉變量以區分七個子串,描述如下(英文是枚舉變量名稱)。
WindowTitle 應用程序窗口的標題。僅僅對SDI程序指定。
DocName 用來構造缺省文檔名的字符串。當用File菜單的菜單項new創建新文檔時,缺省文檔名由該字符串加一個數字構成。如果空,使用“unitled”。
FileNewName 文檔類型的名稱,在打開File New對話框時顯示。
FilterName 匹配過濾字符串,在File Open對話框用來過濾要顯示的文件。如果不指定,File Open對話框的文件類型(file style)不可訪問。
FilterExt 該類型文檔的擴展名。如果不指定,則不可訪問對話框的文件類型(File Style)。
RegFileTypeId 文檔類型在Windows 注冊庫中的存儲標識。
RegFileTypeName 文檔類型在Windows 注冊庫中的類型名稱。
文檔模板被應用程序對象創建和管理。應用程序類CWinApp有一個CDocManager類型的成員變量m_pDocManager,通過該變量來管理應用程序的文檔模板列表,把一些相關的操作委派給CDocManager對象處理。
CDocManager使用CPtrList類型的m_templateList變量來存儲文檔模板,並提供了操作文檔模板列表的系列函數。
從語句pDocTemplate = new CSingleDocTemplate(…)可以看出應用程序對象創建模板時傳遞一個資源ID和三個類的動態創建信息給它:
IDR_MAINFRAME,資源ID
RUNTIME_CLASS(CTDoc),文檔類動態創建信息
RUNTIME_CLASS(CMainFrame),邊框窗口類動態創建信息
RUNTIME_CLASS(CTView),視類動態創建信息
文檔模板對象接收這些信息並把它們保存到對應的成員變量裡頭。然後AddDocTemplate實際調用m_pDocManager->AddDocTemplate,把創建的模板對象加入到文檔模板管理器的模板列表中,也就是應用程序對象的文檔模板列表中。
文件的創建或者打開
第二步是創建或者打開文件。
對於SDI程序,MFC對象的動態創建過程是在創建或者打開文件中發生的。但是為什麼沒有看到文件操作相關的語句呢?
CCommandLineInfo
首先,需要弄清楚類CcommandLineInfo,它是用來處理命令行信息的類,CWinApp::PareCommandLine調用CCommandLineInfo的成員函數ParseParm分析啟動程序時的參數,把分析結果保存在CCommandLineInfo對象的成員變量裡。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;
};
由上述定義可以看出,分析結果分幾類:是否OLE激活;應該執行什麼動作(FileNew、FileOpen等);傳遞的參數(打開或打印的文件名,打印設備、端口等)。
當命令行空時,執行FileNew命令。原因在於CCommandLineInfo的缺省構造函數:
CCommandLineInfo::CCommandLineInfo()
{
m_bShowSplash = TRUE;
m_bRunEmbedded = FALSE;
m_bRunAutomated = FALSE;
m_nShellCommand = FileNew;//指定了SHELL命令操作
}
缺省構造把應該執行的動作指定為FileNew。
處理命令行命令
其次,分析 CWinApp::ProcessShellCommand(CCommandLineInfo& rCmdInfo)的流程,它處理命令行的命令,流程如圖5-3所示。
圖5-4第三層表示根據命令類型進一步調用的函數,都是CWinApp或者派生類的成員函數。對於FILEDDE類型沒有進一步的調用。
命令類型是FILENEW時,調用的函數就是標准命令ID_FILE_NEW對應的處理函數OnFileNew;命令類型是FILEOPEN時調用的函數是OpenDocumentFile,標准命令ID_FILE_OPEN的處理函數OnFileOpen的工作實際上就是由OpenDocumentFile完成的。函數FileNew、OpenDocumentFile導致了窗口、文檔的創建。
OnFileNew
接著,分析 CWinApp::OnFileNew流程,如圖5-5所示。
圖5-5的說明:
應用程序對象得到文檔模板管理器指針,調用文檔模板管理器的成員函數OnFileNew(m_pDocManager->OnFileNew());模板管理器獲取文檔模板對象指針,調用文檔模板對象的OpenDocumentFile 函數(pTemplate->OpenDocumentFile(NULL))。如果模板管理器發現有多個文檔模板,就彈出一個對話框讓用戶選擇文檔模板。這裡和後面的圖解中類似於CWinApp::、CDocManager::、CDocTemplate::等的函數類屬限制並不表示直接源碼中有這樣的限制,而是通過指針或者指針的動態約束可以認定調用了某個類的成員函數,其正確性僅僅限於本書圖解的MFC的缺省實現。
如圖5-5所示,程序員可以覆蓋有關虛擬函數或命令處理函數:如果程序員在自己的應用程序類中覆蓋了OnFileNew,則可以實現完全不同的處理流程;一般情況下,不會從文檔模板類派生新類,如果派生的話,可以覆蓋CDocTemplate的虛擬函數。
OnFileOpen
分析了 OnFileNew後,現在分析CWinApp::OnFileOpen(),其流程如圖5-6所示。
CWinApp::OnFileOpen和OnFileNew類似,不過,第二步須得到一個要打開的文件的名稱,第三步調用的是應用程序對象的OpenDocumentFile,而不是文檔模板對象的該函數。
應用程序對象的OpenDocumentFile
分析應用程序的打開文檔函數: CWinApp::OpenDocumentFile(LPCSTR name),其流程如圖5-7所示。
應用程序對象把打開文件操作委托給文檔模板管理器,後者又委托給文檔模板對象來執行。如果是SDI程序,則委托給單文檔對象;如果是MDI程序,則委托給多文檔對象──這是由指針所指對象的實際類型決定的,因為該函數是一個虛擬函數。
文檔模板的OpenDocumentFile
不論是FileNew還是FileOpen,最後的操作都歸結到由文檔模板來打開文件(文件名空則創建文件)。
CSingleDocTemplate::OpenDocumentFile(lpcstr name,BOOL visible)的流程見圖5-8。有一點需要指出的是:創建了一個文檔對象,並不等於打開了一個文檔(件)或者創建了一個新文檔(件)。
圖5-8顯示的流程大致可以描述如下:
如果已經有文檔打開,則保存當前的文檔;否則,文檔對象還沒有創建,需要創建一個新的文檔對象。因為這時邊框窗口還沒有生成,所以還要創建邊框窗口對象(MFC對象)和邊框窗口。MFC邊框窗口對象動態創建,HWND邊框窗口由LoadFrame創建。MFC邊框窗口被創建時,CFrameWnd的缺省構造函數被調用,它把正創建的對象(this所指)加入到模塊-線程狀態的邊框窗口列表m_frameList之首。
邊框窗口創建過程中由CreateView動態創建MFC視對象和HWND視窗口。
接著,如果沒有指定要打開的文件名,則創建一個新的文件;否則,則打開文件,並使用序列化機制讀入文件內容。
通過上述過程,動態地創建了MFC邊框窗口對象、視對象、文檔對象以及對應的Windows對象,並填寫了有關對象的成員變量,建立起這些MFC對象的關系。
打開文件過程中所涉及的消息處理函數和虛擬函數
圖5-8描述的整個過程中系列消息處理函數和虛擬函數被調用。例如:在Windwos邊框窗口和視窗口被創建時會產生WM_CREATE等消息,導致OnCreate等消息處理函數的調用,CFrameWnd和CView都覆蓋了該函數,所以在邊框窗口和視窗口的創建中,同樣的消息調用了不同的處理函數CFrameWnd::OnCreate和CView::OnCreate。
圖5-8涉及的幾個虛擬函數的流程分別由圖5-9、圖5-10圖解。圖5-9表示CDocument的OnNewDocument的流程;圖5-10表示CDocument的OpenDocument的流程。這兩個函數分別在創建新文檔或者打開一個文檔時被調用。從流程可以看出,對於OpenDocument函數,MFC的缺省實現主要用來設置修改標識、序列化讀入打開文檔的內容。圖5-10顯示了序列化的操作過程:
首先,使用文檔對象打開或者創建的文件句柄創建一個用於讀出數據的CArchive對象loadarchive;然後使用它通過Serialize進行序列化操作,完畢,CArchive對象被自動銷毀,文件句柄被關閉。