天天看點

MFC深入淺出-應用程式的退出

應用程式的退出

一個Windows應用程式啟動之後,一般是進入消息循環,等待或者處理使用者的輸入,直到使用者關閉應用程式視窗,退出應用程式為止。

例如,使用者按主視窗的關閉按鈕,或者選擇執行系統菜單“關閉”,或者從“檔案”菜單選擇執行“退出”,都會導緻主視窗被關閉。

當使用者從“檔案”菜單選擇執行“退出”時,将發送MFC标準指令消息ID_APP_EXIT。MFC實作了函數CWinApp::OnAppExit()來完成對該指令消息的預設處理。

void CWinApp::OnAppExit()

{

// same as double-clicking on main window close box

ASSERT(m_pMainWnd != NULL);

m_pMainWnd->SendMessage(WM_CLOSE);

}

可以看出,其實作是向主視窗發送WM_CLOSE消息。主視窗處理完WM_CLOSE消息之後,關閉視窗,發送WM_QUIT消息,退出消息循環(見圖5-3),進而退出整個應用程式。

邊框視窗對WM_CLOSE的處理

MFC提供了函數CFrameWnd::OnClose來處理各類邊框視窗的關閉:不僅包括SDI的邊框視窗(從CFrameWnd派生),而且包括MDI的主邊框視窗(從CMDIFrameWnd派生)或者文檔邊框視窗(從CMDIChildWnd派生)的關閉。

該函數的原型如下,流程如圖6-1所示:

void CFrameWnd::OnClose()

從圖6-1中可以看出,它首先判斷是否可以關閉視窗(m_lpfnCloseProc是函數指針類型的成員變量,用于列印預覽等情況下),然後,根據具體情況進行處理:

如果是主視窗被關閉,則關閉程式的所有文檔,銷毀所有視窗,退出程式;

如果不是主視窗被關閉,則是文檔邊框視窗被關閉,又分兩種情況:若該視窗所顯示的文檔被且僅被該視窗顯示,則關閉文檔和文檔視窗并銷毀視窗;若該視窗顯示的文檔還被其他文檔邊框視窗所顯示,則僅僅關閉和銷毀文檔視窗。

 

下面是處理WM_CLOSE消息中涉及的一些函數。

BOOL CDocument::SaveModified()

該虛拟函數的預設實作:首先調用IsModifed判斷文檔是否被修改,沒有修改就傳回,否則繼續。

當詢問使用者是否儲存被修改的文檔時,若使用者表示“cancel”,傳回FALSE;若使用者表示“no”,則傳回TRUE;若使用者表示“yes”,則存盤失敗傳回FALSE,存盤成功傳回TRUE。存盤處理首先要得到被儲存檔案的名稱,然後調用虛拟函數OnSaveDocument完成存盤工作,并使用SetModifidFlag(FALSE)設定文檔為沒有修改。

BOOL CDocument::OnSaveDocument(LPCTSTR lpszPathName)

該函數是虛拟函數,用來儲存檔案。其實作的功能和OpOpenDocument相反,但處理流程類似,描述如下:

根據lpszPathName打開檔案pFile;

使用pFile構造一個用于寫入資料的CArchive對象,此對象用來儲存資料到檔案;

設定滑鼠為時間瓶形狀;

使用Serialize函數完成序列化寫;

完畢,恢複滑鼠的形狀。

CWinApp::SaveAllModified()

CWinApp::CloseAllDocuments(BOOL bEndSession)

這兩個函數都周遊模闆管理器清單,并分别對清單中的模闆管理器對象逐個調用CDocManager的同名成員函數:

CDocManager::SaveAllModified()

CDocManager::CloseAllDocuments(BOOL bEndSession)

這兩個函數都周遊其文檔模闆清單,并分别對清單中的模闆對象逐個調用CDocTemplate的同名成員函數:

CDocTemplate::SaveAllModified()

CDocTemplate::CloseAllDocuments(BOOL bEndSession)

這兩個函數都周遊其文檔清單,并分别對清單中的文檔對象逐個調用CDocuemnt的成員函數:

CDocument::SaveModified()

CDocument::OnCloseDocument()

CDocument::SaveModified前面已作了解釋。OnCloseDocument是一個虛拟函數,其流程如圖6-2所示。

通過文檔對象所對應的視,得到所有顯示該文檔的邊框視窗的指針:在SDI程式關閉視窗時,擷取的是主邊框視窗;在MDI程式關閉視窗時,擷取的是MDI子視窗。

然後,關閉并銷毀對應的邊框視窗。

如果文檔對象的m_bAutoDelete為真,則銷毀文檔對象自身。

視窗的銷毀過程

從圖6-1、圖6-2可以看出,銷毀視窗是通過調用DestroyWindow來完成的。DestroyWindow是CWnd類的一個虛拟函數。CWnd實作了該函數,而CMDIChildWnd覆寫了該函數。

(1)CWnd::DestroyWindow()

主要就是調用::DestroyWindow銷毀m_hWnd(必須非空),同時銷毀其菜單、定時器,以及完成其他清理工作。

::DestroyWindow使将被銷毀的視窗失去激活、失去輸入焦點,并發送WM_DESTROY、WM_NCDESTROY消息到該視窗及其各級子視窗。如果被銷毀的視窗是子視窗且沒有設定WM_NOPARENTNOTFIY風格,則給其父視窗發送WM_PARENTNOFITY消息。

(2)CMDIChildWnd::DestroyWindow()

因為MDI子視窗不能使用::DestroyWindows來銷毀,是以CMdiChildWnd覆寫了該函數,CMDIChildWnd主要是調用成員函數MDIDestroy給客戶視窗(父視窗)發送消息WM_MDIDESTROY,讓客戶視窗來銷毀自己。

處理WM_DESTROY消息

消息處理函數OnDestroy處理WM_DESTROY消息,CFrameWnd、CMDIChildWnd、CWnd、CView及其派生類(如CEditView等等)、CControlBar等都提供了對該消息的處理函數。這裡,主要解釋邊框、文檔邊框、視類的消息處理函數OnDestroy。

CWnd::OnDestroy()

調用預設處理函數Default()。

CFrameWnd::OnDestroy()

首先,銷毀工具欄的視窗;然後,設定菜單為預設菜單;接着,如果要銷毀的是主邊框視窗,則通知HELP程式本應用程式将退出,沒有其他程式使用WINHELP則關閉WINHELP;最後調用CWnd::OnDestroy。

CMDIFrameWnd::OnDestroy()

首先,調整客戶視窗的邊界類型;然後,調用基類CframeWnd的OnDestroy。這時,MDI子視窗的工具欄視窗清單為空,故沒有工具欄視窗可以銷毀。

CView::OnDestroy()

首先,判斷自身是否是邊框視窗的活動視,如果是則調用邊框視窗的SetActivateView使自己失去激活;然後,調用基類CWnd的OnDestroy。

處理WM_NCDESTROY消息

視窗的非客戶區被銷毀時,視窗接收WM_NCDESTROY消息,由OnNcDestroy處理WM_NCDESTROY消息。在MFC中,OnNcDestroy是Windows視窗被銷毀時調用的最後一個成員函數。

CWnd、CView的某些派生類提供了對該消息的處理函數,這裡隻讨論CWnd的實作。

CWnd::OnNcDestroy()

首先判斷目前線程的主視窗是否是該視窗,如果是且子產品非DLL,則發送WM_QUIT消息,使得程式結束;

然後,判斷目前線程的活動視窗是否是該視窗,如果是則設定活動視窗為NULL;

接着,清理Tooltip視窗,調用Default由Windows預設處理WM_NCDESTROY消息,UNSUBCLASS,把視窗句柄和MFC視窗對象分離(Detach);

最後,調用虛拟函數PostNcDestoy。

PostNcDestoy

CWnd、CFrameWnd、CView、CControlBar等都覆寫了該函數。文檔邊框視窗和邊框視窗都使用CFrameWnd::PostNcDestroy。

CWnd::PostNcDestroy()

MFC預設實作空

void CFrameWnd::PostNcDestroy()

調用delete this銷毀自身這個MFC對象。

void CView::PostNcDestroy()

析構函數

delete this導緻析構函數的調用。需要提到的是CFrameWnd和CView的析構函數。

CFrameWnd::~CFrameWnd()

邊框視窗在建立時,把自身加入到子產品-線程狀态的邊框視窗清單m_frameList中。現在,從清單中移走該視窗對象。

必要的話,删除m_phWndDisable數組。

CView::~CView()

在視建立時,把自身加入到文檔對象的視清單中。現在,從清單中移走該視對象。

應用程式調用CloseAllDocument關閉文檔時。參數為FALSE,它實際上并沒有把視從清單中清除,而最後的清除是由析構函數來完成的。

至此,邊框視窗關閉的過程讨論完畢。下面,結合具體情況──SDI視窗的關閉、MDI主視窗的關閉、MDI子視窗的關閉──描述對WM_CLOSE消息的處理。

SDI視窗、MDI主、子視窗的關閉

參考圖6-1分析SDI視窗、MDI主、子視窗的關閉流程。

SDI視窗的關閉

在這種情況下,主視窗将被關閉。首先,關閉應用程式的文檔對象。文檔對象的虛拟函數OnCloseDocument調用時銷毀了主視窗(Windows視窗和MFC視窗對象),同時也導緻視、工具條視窗的銷毀。主視窗銷毀後,應用程式的主視窗對象為空,故發送WM_QUIT消息結束程式。

MDI主視窗的關閉

首先,關閉應用程式的所有文檔對象。文檔對象的OnCloseDocument函數關閉文檔時,将銷毀文檔對象對應的文檔邊框視窗和它的視視窗。這樣,所有的MDI子視窗(包括其子視窗視)被銷毀,但應用程式的主視窗還在。接着,調用DestroyWindow成員函數銷毀主視窗自身,DestroyWindow發現被銷毀的是應用程式的主視窗,于是發送WM_QUIT消息結束程式。

MDI子視窗(文檔邊框視窗)的關閉

在這種情況下,被關閉的不是主視窗。判斷與該文檔邊框視窗對應的文檔對象是否還被其他一個或者多個文檔邊框視窗使用,如果是,則僅僅銷毀該文檔邊框視窗(包括其子視窗視);否則,關閉文檔,文檔對象的OnCloseDocument将銷毀該文檔邊框視窗(包括其子視窗視)。

繼續閱讀