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。其中,字符串資源描述了文檔類型,由七個被“\n”分隔的子字符串組成,各個子串可以通過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對象被自動銷毀,文件句柄被關閉。
從這些圖中可以看到何時、何處調用了什么消息處理函數和虛擬函數,這些函數用來作了什么事情。必要的話,程序員可以在派生類覆蓋它們。
在創建工作完成之后,進行初始化,使用文檔對象的數據來更新視和顯示窗口。
至此,本節描述了MFC的SDI程序從分析命令行到創建或打開文件的處理過程,文檔對象已經動態創建。總結如下:
命令行分析→應用程序的FileNew→文檔模板的OpenDocumentFile(NULL)→文檔的OnNewDocument
命令行分析→應用程序的FileOPen→文檔模板的OpenDocumentFile(filename)→文檔的OpenDocument
邊框窗口對象、視對象的動態創建和對應 Windows對象的創建從LoadFrame開始,這些將在下一節論述。
- SDI邊框窗口的創建
第三步是創建SDI邊框窗口。
圖5-8已經分析了創建SDI邊框窗口的時機和創建方法,下面,從LoadFrame開始分析整個窗口創建過程。
- CFrameWnd::LoadFrame
CFrameWnd::LoadFrame的流程如圖5-11所示,其原型如下:
BOOL CFrameWnd::LoadFrame(UINT nIDResource,
DWORD dwDefaultStyle,
CWnd* pParentWnd,
CCreateContext* pContext)
第一個參數是和該框架相關的資源ID,包括字符串、快捷鍵、菜單、像標等;
第二個參數指定框架窗口的“窗口類”和窗口風格;此處創建SDI窗口時和缺省值相同,為WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE;
第三個參數指定框架窗口的父窗口,此處和缺省值相同,為NULL;
第四個參數指定創建的上下文,如圖5-8所示由CreateNewFrame生成了該變量并傳遞給LoadFrame。其缺省值為NULL。
創建上下文結構的定義:
struct CCreateContext
{
CRuntimeClass* m_pNewViewClass; //View的動態創建信息
CDocument*m_pCurrentDoc;//指向一文檔對象,將和新創建視關聯
//用來創建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變量是:
(視的動態創建信息,新創建的文檔對象,當前文檔模板,NULL,NULL)。
其中,“新創建的文檔對象”就是圖 5-8中創建的那個文檔對象。從此圖中還可以看到,LoadFrame被CreateNewFrame調用,CreateNewFrame是文檔模板的成員函數,被文檔模板的成員函數OpenDocumentFile所調用,所以,LoadFrame間接地被文檔模板調用,“當前文檔模板”就是調用它的模板對象。順便指出,對SDI程序來說是這樣的,對MDI程序有所不同?!耙暤膭討B創建信息”也是文檔模板傳遞過來的。
對圖5-11的說明:
在創建邊框窗口之前,先注冊“窗口類”。LoadFrame注冊了兩個“窗口類”,一個為邊框窗口,一個為視窗口。關于“窗口類”注冊,見2.2.1節。
注冊窗口類之后,創建邊框窗口,并加載資源。創建邊框窗口使用了CFrameWnd的Create虛擬函數,最終調用::CreateEx創建窗口。::CreateEx有11個參數,其最后一個參數就是文檔模板傳遞給LoadFrame的CCreateContext類型的指針,該指針將被傳遞給窗口過程,進一步由Windows傳遞給OnCreate函數。順便指出,創建完畢的邊框窗口的窗口過程是統一的MFC窗口過程。
創建邊框窗口時,發送消息WM_NCCREATE和WM_CREATE,導致對應的消息處理函數OnNcCreate和OnCreate被調用。CWnd提供了OnNcCreate處理非客戶區創建消息,CFrameWnd沒有處理該消息,但是提供了OnCreate處理消息WM_CREATE。OnCreate將創建視對象和視窗口。
- CFrameWnd::OnCreate
按創建工作的進度,現在要討論邊框窗口創建消息(WM_CREATE)的處理了,處理函數是CFrameWnd的OnCreate,其原型如下:
int CFrameWnd::OnCreate(LPCREATESTRUCT lpcs)
其中,參數指向一個CreateStruct結構(關于CreateStruct的描述見4.4.1節),它包含了窗口創建參數的副本,也就是說CreaeEx窗口創建函數的11個參數被對應地復制到該結構的11個域,例如它的第一個成員就可以轉換成CCreateContext類型的指針。
函數OnCreate處理WM_CREATE消息,它從lpcs指向的結構中分離出lpCreateParams并把它轉換成為CCreateContext類型的指針pContext,然后,調用OnCreateHelp(lpcs,pContext),把創建工作委派給它完成。
CFrameWnd::OnCreateHelp的原型如下,流程見圖5-11。
int CFrameWnd::OnCreateHelp(LPCREATESTRUCT lpcs,
CCreateContext* pContext)
說明:由于CFrameWnd覆蓋了消息處理函數OnCreate來處理WM_CREATE消息,所以CWnd就失去了處理該消息的機會,除非CFrameWnd::OnCreate主動調用基類的該消息處理函數。圖5-11展示了對CWnd::OnCreate的調用。
在邊框窗口被創建之后,調用虛擬函數OnCreateClient(lpcs,pContext),它的缺省實現將創建視對象和視窗口。
最后,在狀態欄顯示“Ready”字樣,調用RecalcLayout安排工具欄等的位置。關于WM_SETMESSAGESTRING消息和RecalcLayout函數,見工具欄有關13.2.3節。
到此,SDI的邊框窗口已經被創建。下一節將描述視的創建。
- 視的創建
第四步,創建視。
如前一節所述,若CFrameWnd::OnCreateClient(lpcs,pContext)判斷pContext包含了視的動態創建信息,則調用函數CreateView創建視對象和視窗口。CreateView的原型如下,其流程如圖5-13所示。
CWnd * CFrameWnd::CreateView(CCreateContext* pContext, UINT nID)
其中:
第一個參數是創建上下文;
第二個參數是創建視 (子窗口)的ID,缺省是AFX_IDW_PANE_FIRST,這里等同缺省值。
說明:
CreateView調用了CWnd的Create函數創建HWND視窗口,視的子窗口ID是AFX_IDW_PANE_FIRST,父窗口是創建它的邊框窗口。創建視窗口時的WM_CREATE、WM_NCCREATE消息分別被CView、CWnd的相關消息處理函數處理。處理情況如圖5-13所述,這里不作進一步的討論。
到此,文檔對象、邊框窗口對象、視窗口對象已經創建,文件已經打開或者創建,邊框窗口、視窗口已經創建?,F在,是顯示和定位窗口、顯示文檔數據的時候了,這些通過調用CFrameWnd的虛擬函數InitialUpdateFrame完成,如圖5-8所示。
- 窗口初始化
這是第五步,初始化邊框窗口、視窗口等。
InitialUpdateFrame的原型如下:
void CFrameWnd::InitialUpdateFrame(CDocument* pDoc, BOOL bMakeVisible)
其中:
第一個參數是當前的文檔對象;
第二個參數表示邊框窗口是否應該可見并激活。
該函數是在文檔模板的OpenDocumentFile中調用的,傳遞過來的第一個參數是剛才創建的文檔,第二個參數是TRUE,見圖5-8。
InitialUpdateFrame的處理過程參見圖5-14,解釋如下:
首先,如果當前邊框窗口沒有活動視,則獲取ID為AFX_IDW_PANE_FIRST的視pView。如果該視不存在,則pView=NULL;否則(pView!=NULL),調用成員函數SetActiveView(pView,FALSE)把它設置為活動視,參數2為FALSE表示并不通知它成為活動視(見圖5-14)。
然后,如果InitialUpdateFrame的參數bMakeVisible為TRUE,則給所有邊框窗口的視發送WM_INITIALUPDATE消息,通知它們在顯示之前使用文檔數據來初始化視。這導致視類的虛擬函數OnInitUpdate被調用,該函數又調用OnUpdate來完成初始化。其他子窗口(如狀態欄、工具欄)也將收到WM_INITIALUPDATE消息,導致它們更新狀態。
其三,調用pView->OnActivateFrame(WA_INACTIVE,this)給活動視(若存在的話)一個機會來保存當前焦點。這里,解釋這個函數:
void CView::OnActivateFrame( UINT nState,CFrameWnd* pFrameWnd );
其中,參數1取值為WA_INACTIVE/WA_ACTIVE/WA_CLICKACTIVE,具體見消息WM_ACTIVE的解釋;參數2指向被激活的框架窗口。
視對象通過該虛擬函數在它所屬的邊框窗口被激活或者失去激活時作一些特別的處理,例如,CFormView用它來保存或者恢復輸入焦點控制。
其四,在OnActivateFrame之后,調用成員函數ActivateFrame激活框架窗口。這個過程將產生一個消息WM_ACTIVE(處理該消息的過程在下一節作解釋),它導致OnActiveTopLevel和OnActive被調用。接著,如果活動視非空,則調用成員函數OnActivateView激活它。
至此,參數bMakeVisible為TRUE時顯示窗口的處理完畢。
最后,如果參數pDoc非空,則更新邊框窗口計數,更新邊框窗口的標題。更新邊框窗口計數是為了在一個文檔對應多個視的情況下,給顯示同一文檔的不同文檔邊框窗口編號,編號從1開始,保存在邊框窗口的成員變量m_nWindow里。例如有兩個邊框對應一個文檔tt1,則它們的標題分別為“tt1:1”、“tt1:2”。如果只有一個文檔只對應一個邊框窗口,則成員變量m_nWindow等于-1,標題不含編號,如“tt1”。
當然,對于SDI應用程序,不存在一個文檔對應多個視的情況。上述情況是針對MDI應用程序而言的。SDI應用程序執行該過程時,相當于MDI程序的一個特例。
圖 5-14涉及的一些函數由圖5-15、5-15圖解。
圖5-14中的函數SetActiveView的圖解如圖5-15所示,其原型如下,:
void CFrameWnd::SetActiveView(CView * pViewNew, BOOL bNotify = TRUE)
其中:
參數1指向被設置的視對象,若非視類型的對象,則為NULL;
參數 2表示是否通知被設置的視。
圖5-15中的變量m_pViewActive是CFrameWnd的成員變量,用來保存邊框窗口的活動視。
圖5-15中的流程可以概括為:Deactivate當前視(m_pViewActive非空時);設置當前活動視;若參數bNotify為TRUE,通知pViewNew被激活。
圖5-14中的函數ActivateFrame圖解如圖5-16所示,其原型如下,:
void CFrameWnd::ActivateFrame(UINT nCmdShow)
參數nCmdShow用來傳遞給CWnd::ShowWindow,指定顯示窗口的方式。參數缺省為1,圖5-14調用時設置為-1。
該函數用來激活(Activate)和恢復(Restore)邊框窗口,使得它對用戶可見可用。在初始化、OLE事件、DDE事件等需要顯示邊框窗口的地方調用。圖5-16表示的MFC缺省實現是激活邊框窗口并把它放到頂層。
程序員可以覆蓋該虛擬函數ActivateFrame來控制邊框窗口怎樣被激活。
圖5-16中的函數BringToTop是CFrameWnd內部使用的成員函數(protected)。它調用::BringWindowToTop把窗口放到Z軸上的頂層。
至此,邊框窗口初始化的過程已經描述清楚,視的初始化見下一節。
- 視的初始化
第六步,在邊框窗口初始化之后,初始化視。
如圖5-14所示,視、工具條窗口處理消息WM_INITAILUPDATE(MFC內部消息),完成初始化。這里只討論視的消息處理函數,其原型如下:
void CView::OnInitialUpdate()
圖5-14對該函數的注釋說明了該函數的特殊之處。其缺省實現是調用OnUpdate(NULL, 0, NULL)更新視??梢愿采wOnInitialUpdate實現自己的初始化。
OnUpdate是一個虛擬函數,其原型如下:
void CView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)
其中:
參數1指向修改文檔數據的視;若更新所有的視,設為NULL;
參數2是一個包含了修改信息的long型變量;
參數3指向一個包含修改信息的對象(從CObject派生的對象)。
參數2、參數3是在文檔更新對應視的時候傳遞過來的。
該函數用來更新顯示視窗口,反映文檔的變化,在MFC中,它為函數CView::OnInitialUpdate和CDocument::UpdateAllViews所調用。其缺省實現是使整個客戶區無效。在下一次收到WM_PAINT消息時,重繪無效區。
工具條的初始化見討論第13章。
- 激活邊框窗口(處理WM_ACTIVE)
第七步,在窗口初始化完成之后,激活并顯示出來。
下面討論邊框窗口激活時的處理(對WM_ACTIVE的處理)。
- WM_ACTIVE的消息參數
wParam的低階word指示窗口是被激活還是失去激活:WA_ACTIVE,被鼠標點擊以外的方法激活;WA_CLICKACTIVE,由鼠標點擊激活;WA_INACTIVE,失去激活;
wParam的高階word指示窗口是否被最小化;非零表示最小化;
lPararm表示將激活的窗口句柄(WA_INACTIVE),或者將失去激活的窗口句柄(WA_CLICKACTIVE、WA_ACTIVE)。
在標準Windows消息處理的章節中,曾指出處理WM_ACTIVE消息時,先要調用一個函數_AfxHandleActivate,此函數的原型如下:
static void AFXAPI _AfxHandleActivate(CWnd* pWnd,
WPARAM nState,CWnd* pWndOther)
其中:
參數1是接收消息的窗口;
參數2是窗口狀態,為WM_ACTIVE的消息參數wParam;
參數3是WM_ACTIVE的消息參數lParam表示的窗口。
_AfxHandleActivate是MFC內部使用的函數,聲明和實現均在WinCore.CPP文件中。實現如下:
如果pWnd指向的窗口不是子窗口,而且pWnd和pWndOther窗口的頂級父窗口(TopLevelParent)不是同一窗口,則發送MFC定義的消息WM_ACTIVATETOPLEVEL給pWnd的頂級窗口,消息參數wParam是nState,消息參數lParam指向一個長度為二的數組,數組里存放pWnd和pWndOther所指窗口的句柄。否則,_AfxHandleActivate不作什么。
從這里可以看出:只有頂層的主邊框窗口能處理WM_ACTIVE消息,事實上,Windows系統只會給頂層的非子窗口發送WM_ACTIVE消息。
- WM_ACTIVATETOPLEVEL消息的處理
CWnd及派生類CFrameWnd實現了對WM_ACTIVATETOPLEVEL消息的處理,分別解釋如下:
消息處理函數CWnd::OnActivateTopLevel如果失去激活,則取消工具欄的提示(TOOLTIP)。
消息處理函數CFrameWnd::OnActivateTopLevel調用CWnd的OnActivateTopLevel;如果接收WM_ACTIVE消息的窗口是線程主窗口,則使得其活動的視窗口變成非活動的(OnActive(FALSE, pActiveView,pActiveView)。
從這里可以知道,在頂層窗口接收到WM_ACTIVE消息后,MFC會進行一些固定的處理,然后才調用WM_ACTIVE消息處理函數。
- WM_ACTIVE消息的處理
在_AfxHandleActivate和WM_ACTIVATETOPLEVEL消息處理完之后,才是對WM_ACTIVE的處理。CWnd和CFrameWnd都實現了消息處理。
CWnd的消息處理函數:
void CWnd::OnActive(UINT nState, CWnd* pWndOther, BOOL bMinimized)
其中:
參數1取值為WA_INACTIVE/WA_ACTIVE/WA_CLICKACTIVE;
參數2指向激活或者失去激活的窗口,具體同WM_ACTIVE消息;
參數3表示是否最小化。
此函數的實現是調用Default(),作缺省處理。
CFrameWnd的消息處理函數:
void CFrameWnd::OnActive(UINT nState,CWnd* pWndOther, BOOL bMinimized)
首先調用CWnd::OnActivate。
如果活動視非空,消息是WA_ACTIVE/WA_CLICKACTIVE,并且不是最小化,則重新激活當前視,調用了以下函數:
pActiveView->OnActiveView(TRUE,pActiveView,pActiveView);
并且,如果活動視非空,通知它邊框窗口狀態的變化(激活/失去激活),調用以下函數:
pActiveView->OnActivateFrame(nState, this)。
- SDI流程的回顧
從InitialInstance開始,首先應用程序對象創建文檔模板,文檔模板創建文檔對象、打開或創建文件;然后,文檔模板創建邊框窗口對象和邊框窗口;接著邊框窗口對象創建視對象和視窗口。這些創建是以應用程序的文檔模板為中心進行的。在創建這些MFC對象的同時,建立了它們之間的關系。創建這些之后,進行初始化,激活主邊框窗口,把邊框窗口、視窗口顯示出來。
這樣,一個SDI應用程序就完成了啟動過程,等待著用戶的交互或者輸入。
5.3.4節將在SDI程序啟動流程的基礎之上,介紹MDI應用程序的啟動流程。兩者的相同之處可以這樣類比:創建SDI邊框窗口、視、文檔的過程和創建MDI文檔邊框窗口、視、文檔的過程類似。不同之處主要表現在:主邊框窗口的創建不一樣;MDI有文檔邊框窗口的創建,SDI沒有;SDI只能一個文檔、一個視;MDI可能多文檔、多個視。
- MDI程序的對象創建
MDI應用程序對象的InitialInstance函數一般含有以下代碼:
//第一部分:創建和添加模板
CMultiDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate(
IDR_TTTYPE,
RUNTIME_CLASS(CTtDoc),
RUNTIME_CLASS(CChildFrame),//custom MDI child frame
RUNTIME_CLASS(CTtView));
AddDocTemplate(pDocTemplate);
//第二部分:創建MFC框架窗口對象和Windows主邊框窗口
// 創建主MDI邊框窗口
CMainFrame* pMainFrame = new CMainFrame;
if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
return FALSE;
m_pMainWnd = pMainFrame;
//第三部分:處理命令行,命令行空則執行OnFileNew創建新文檔
//分析命令行
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
// 處理命令行命令
if (!ProcessShellCommand(cmdInfo))
return FALSE;
第四部分:顯示和更新主框架窗口
// 主窗口已被初始化,現在顯示和更新主窗口
pMainFrame->ShowWindow(m_nCmdShow);
pMainFrame->UpdateWindow();
SDI應用程序對象的InitialInstance和SDI應用程序對象的InitialInstance比較,有以下的相同和不同之處。相同之處在于:
創建和添加模板;處理命令行。
不同之處在于:
- 創建的模板類型不同。SDI使用單文檔模板,邊框窗口類從CFrameWnd派生;MDI使用多文檔模板,邊框窗口類從CMDIChildWnd派生.
- 主窗口類型不同。SDI的是從CFrameWnd派生的類;MDI的是從CMDIFrameWnd派生的類。
- 主框架窗口的創建方式不同。SDI在創建或者打開文檔時動態創建主窗口對象,然后加載主窗口(LoadFrame)并初始化;MDI使用第二部分的語句來創建動態主窗口對象和加載主窗口,第四部分語句顯示、更新主窗口。
- 命令行處理的用途不一樣。SDI一定要有命令行處理部分的代碼,因為它導致了主窗口的創建;MDI可以去掉這部分代碼,因為它的主窗口的創建、顯示等由第二、四部分的語句來處理。
- 有別于SDI的主窗口加載過程
和SDI應用程序一樣,MDI應用程序使用LoadFrame加載主邊框窗口,但因為LoadFrame的虛擬屬性,所以MDI調用了CMDIFrameWnd的LoadFrame函數,而不是CFrameWnd的LoadFrame。
LoadFrame的參數1指定了資源ID,其余幾個參數取缺省值。和SDI相比,第四個創建上下文參數為NULL,因為MDI主窗口不需要文檔、視等的動態創建信息。
圖 5-17圖解了CMdiFrameWnd::LoadFrame的流程:
首先,用同樣的參數調用基類CFrameWnd的LoadFrame,其流程如圖5-11所示,但由于參數4表示的創建上下文為空,所以,CFrameWnd::LoadFrame在加載了菜單和快捷鍵之后,給所有子窗口發送WM_INITUPDATE消息。
另外,WM_CREATE消息怎樣處理呢?由于CMDIFrameWnd沒有覆蓋OnCreate,所以還是由基類CFrameWnd::OnCreate處理。但是它調用虛擬函數OnCreateClient(見圖5-12)時,由于CMDIFrameWnd覆蓋了該函數,所以動態約束的結果是CMDIFrameWnd::OnCreateClient被調用,它和基類的OnCreateClient不同,后者CreateView創建MFC視對象和視窗口,前者調用虛擬函數CreateClient創建MDI客戶窗口。MDI客戶窗口負責創建和管理MDI子窗口。
CreateClient是CMDIFrameWnd的虛擬函數,其原型如下:
BOOL CMDIFrameWnd::CreateClient(
LPCREATESTRUCT lpCreateStruct, CMenu* pWindowMenu);
該函數主要用來創建MDI客戶區窗口。它使用Windows系統預定義的“mdiclient”窗口類來創建客戶區窗口,保存該窗口句柄在CMDIFrameWnd的成員變量m_hWndMDIClient中。調用::CreateWindowEx創建客戶窗口時傳遞給它的窗口創建數據參數(第11個參數)是一個CLIENTCREATESTRUCT結構類型的參數,該結構指定了一個菜單和一個子窗口ID:
typedef struct tagCLIENTCREATESTRUCT{
HMENU hWindowMenu;
UINT idFirstChild;
}CLIENTCREATESTRUCT;
hWindowMenu表示主邊框窗口菜單欄上的“Windows彈出菜單項”的句柄。MDICLIENT類客戶窗口在創建MDI子窗口時,把每一個子窗口的標題加在這個彈出菜單的底部。idFirstChild是第一個被創建的MDI子窗口的ID號,第二個MDI子窗口ID號為idFirstChild+1,依此類推。
這里,hWindowMenu的指定不是必須的,程序員可以在MDI子窗口激活時進行菜單的處理;idFirstChild的值是AFX_IDM_FIRST_MDICHILD。
綜合地講,CMDIFrameWnd::LoadFrame完成創建MDI主邊框窗口和MDI客戶區窗口的工作。
創建了MDI邊框窗口和客戶區窗口之后,接著是處理WM_INITUPDATE消息,進行初始化。但是按SDI應用程序的討論順序,下一節先討論MDI子窗口的創建。
- MDI子窗口、視、文檔的創建
和SDI應用程序類似,MDI應用程序通過文檔模板來動態創建MDI子窗口、視、文檔對象。不同之處在于:這里使用了多文檔模板,調用的是CMDIChildWnd(或派生類)的消息處理函數和虛擬函數,如果它覆蓋了CFrameWnd的有關函數的話。
還是以處理標準命令消息ID_FILE_NEW的OnFileNew為例。
表示OnFileNew的圖5-5、表示OnFileOpen的圖5-6在多文檔應用程序中仍然適用,但表示OpenDocumentFile的圖5-8有所不同,其第三步中地單文檔模板應當換成多文檔模板,關于這一點,參閱圖5-8的說明。
(1)多文檔模板的OpenDocumentFile
MDI的OpenDocumentFile的原型如下:
CDocument* CMultiDocTemplate::OpenDocumentFile(
LPCTSTR lpszPathName, BOOL bMakeVisible);
它的原型和單文檔模板的該函數原型一樣,但處理流程比圖5-8要簡單些:
第一,不用檢查是否已經打開了文檔;
第二,不用判斷是否需要創建框架窗口或者文檔對象,因為不論新建還是打開文檔都需要創建新的文檔框架窗口(MDI子窗口)和文檔對象。
除了這兩點,其他處理步驟基本相同,調用同樣名字的函數來創建文檔對象和MDI子窗口。雖然是名字相同的函數,但是參數的值可能有異,又由于C++的虛擬機制和MFC消息映射機制,這些函數可能來自不同層次類的成員函數,因而導致有不同的處理過程和結果,即SDI創建了CFrameWnd類型的對象和邊框窗口;MDI則創建了CMDIChildWnd類型的對象和邊框窗口。不同之處解釋如下:
(2)CMDIChildWnd的虛擬函數LoadFrame
CMDIChildWnd::LoadFrame代替了圖5-8中的CFrameWnd::LoadFrame,兩者流程大致相同,可以參見圖5-11。但是它們用來創建窗口的函數不同。前者調用了函數CMDIChildWnd::Create(參數1…參數6);后者調用了CFrameWnd::Create(參數1…參數7)。
這兩個窗口創建函數,雖然都是虛擬函數,但是有很多不同之處:
- 前者是CMDIChildWnd定義的虛擬函數,后者是CWnd定義的虛擬函數;
- 前者在參數中指定了父窗口,即主創建窗口,后者的父窗口參數為NULL;
- 前者指定了WS_CHILD風格,創建的是子窗口,后者創建一個頂層窗口;
- 前者給客戶窗口m_hWndMDIClient(CMDIFrameWnd的成員變量)發送WM_MDICREATE消息讓客戶窗口來創建MDI子窗口(主邊框窗口的子窗口是客戶窗口,客戶窗口的子窗口是MDI子窗口),后者調用::CreateEx函數來創建邊框窗口;
- 前者的窗口創建數據是指向MDICREATESTRUCT結構的指針,該結構的最后一個域存放一個指向CCreateContext結構的指針,后者是指向CCreateContext結構的指針。
MDICREATESTRUCT結構的定義如下:
typedef struct tagMDICREATESTRUCT { // mdic
LPCTSTR szClass;
LPCTSTR szTitle;
HANDLE hOwner;
int x;
int y;
int cx;
int cy;
DWORD style;
LPARAM lParam;
}MDICREATESTRUCT;
該結構的用處和CREATESTRUCT類似,只是它僅用于MDI子窗口的創建上,用來保存創建MDI子窗口時的窗口創建數據。域lParam保存一個指向CCreateContext結構的指針。
- WM_CREATE的處理函數不同
創建MDI子窗口時發送的WM_CREATE消息由CMDIChildWnd的成員函數OnCreate(LPCREATESTRUCT lpCreateStruct)處理。
OnCreate函數僅僅從lpCreateStruct指向的數據中取出窗口創建數據,即指向MDICREATESTRUCT結構的指針,并從該結構得到指向CCreateContext結構的指針pContext,然后調用虛擬函數OnCreateHelper(lpCreateStruct,pContext)。
此處動態約束的結果是調用了CFrameWnd的成員函數OnCreateHelper。SDI應用程序的OnCreate也調用了CFrameWnd::OnCreateHelper,所以后面的處理(創建視等)可參見SDI的流程了。
待MDI子窗口、視、文檔對象創建完畢,多文檔模板的OpenDocumentFile也調用InitialUpdateFrame來進行初始化。
- MDI子窗口的初始化和窗口的激活
(1)MDI子窗口的初始化
完成了 MDI子窗口、視、文檔的創建之后,多文檔模板的OpenDocumenFile調用邊框窗口的虛擬函數InitialUpdateFrame進行初始化,該函數流程參見圖5-14。不過,這里this指針指向CMDIChildWnd對象,由于C++虛擬函數的動態約束,初始化過程調用了CMDIChildWnd的ActivateFrame函數(不是CFrameWnd的ActivateFrame),來顯示MDI子窗口,更新菜單等等,見圖5-18。
圖5-18的說明:
第一,調用基類CFrameWnd的ActivateFrame顯示窗口時,由于當前窗口是文檔邊框窗口,所以沒有發送WM_ACTIVATE消息,而是發送消息WM_MDIACTIVATE。
第二,由于Windows不處理MDI子窗口的激活,所以必須由MFC或者程序員來完成。當一個激活的MDI子窗口被隱藏后從可見變成不可見,但它仍然是活動的,這時需要把下一文檔邊框窗口激活以便用戶看到的就是激活的窗口。在沒有其他文檔邊框窗口時,則把該隱藏的文檔邊框窗口標記為“偽失去激活”。當一個文檔邊框窗口從不可見變成可見時,檢查變量m_bPseudoInactive,若真則該窗口從Windows角度看仍然是激活的,只需要調用OnMDIActivate把它改成“MFC激活”。OnMDIActivate把變量m_bPseudoInactive的值改變為FALSE。
至此,MDI子窗口初始化調用描述完畢。初始化將導致MDI窗口被顯示、激活。下面討論MDI子窗口的激活。
(2)MDI子窗口的激活
通過給客戶窗口發送消息WM_MDIACTIVATE來激活文檔邊框窗口。客戶窗口發送WM_MDIACTIVATE消息給將被激活或者取消激活的MDI子窗口(文檔邊框窗口),這些子窗口調用消息處理函數OnMDIActivate響應該消息WM_MDIACTIVATE。關于MDI消息,見表5-12。
用戶轉向一個子窗口(包括文檔邊框窗口)導致它的頂層(TOP LEVEL)邊框窗口收到WM_ACTIVATE消息而被激活,子窗口是文檔邊框窗口的話將收到WM_MDIACTIVATE消息。
但是,一個邊框窗口被其他方式激活時,它的文檔邊框窗口不會收到WM_MDIACTIVATE消息,而是最近一次被激活的文檔邊框窗口收到WM_NCACTIVATE消息。該消息由CWnd::Default缺省處理,用來重繪文檔邊框窗口的標題欄、邊框等等。
MDI子窗口用OnMDIActiveate函數處理WM_MDIACTIVATE消息。其原型如下:
void CMDIChildWnd::OnMDIActivate( BOOL bActivate,
CWnd* pActivateWnd,CWnd* pDeactivateWnd );
其中:
參數1表示是激活(TRUE),還是失去激活(FALSE);
參數2表示將被激活的MDI子窗口;
參數3表示將被失去激活的MDI子窗口;
簡單地說,該函數把m_bPseudoInactive的值改變為FALSE,調用成員函數OnActivateView通知失去激活的子窗口的視它將失去激活,調用成員函數OnActivateView通知激活子窗口的視它將被激活。
至于MDI主邊框窗口,它還是響應WM_ACTIVATE消息而被激活或相反。CMDIFrameWnd沒有提供該消息的處理函數,它調用基類CFrameWnd的處理函數OnActivate。
現在,MDI應用程序的啟動過程描述完畢。
表5-12 MDI消息
消息 |
說明 |
WM_MDIACTIVATE |
激活MDI Child窗口 |
WM_MDICASCADE |
CASCADE排列MDI Child窗口 |
WM_MDICREATE |
創建MDI Child窗口 |
WM_MDIDESTROY |
銷毀MDI Child窗口 |
WM_MDIGETACTIVE |
得到活動的MDI Child窗口 |
WM_MDIICONARRANGE |
安排最小化了的MDI Child窗口 |
WM_MDIMAXIMIZE |
MDI Child窗口最大化 |
WM_MDINEXT |
激活Z軸順序的下一MDI Child窗口 |
WM_MDIREFRESHMENU |
根據當前MDI Child窗口更新菜單 |
WM_MDIRESTORE |
恢復MDI Child窗口 |
WM_MDISETMENU |
根據當前MDI Child窗口設置菜單 |
WM_MDITITLE |
TITLE安排MDI Child窗口 |