程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> 關於VC++ >> MFC教程(5)-- MFC對象的創建(2)

MFC教程(5)-- MFC對象的創建(2)

編輯:關於VC++

從這些圖中可以看到何時、何處調用了什麼消息處理函數和虛擬函數,這些函數用來作了什麼事情。必要的話,程序員可以在派生類覆蓋它們。

在創建工作完成之後,進行初始化,使用文檔對象的數據來更新視和顯示窗口。

至此,本節描述了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程序有所不同。“視的動態創建信息”也是文檔模板傳遞過來的。

對圖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所述,這裡不作進一步的討論。

到此,文檔對象、邊框窗口對象、視窗口對象已經創建,文件已經打開或者創建,邊框窗口、視窗口已經創建。現在,是顯示和定位窗口、顯示文檔數據的時候了,這些通過調用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)更新視。可以覆蓋OnInitialUpdate實現自己的初始化。

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窗口
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved