應用程式的退出
一個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()
CDocument::OnCloseDocument()
CDocument::SaveModified前面已作瞭解釋。OnCloseDocument是一個虛擬函數,其流程6-2所示。
通過文檔對象所對應的視,得到所有顯示該文檔的邊框視窗的指標:在SDI程式關閉視窗時,擷取的是主邊框視窗;在MDI程式關閉視窗時,擷取的是MDI子視窗。
然後,關閉並銷毀對應的邊框視窗。
如果文檔對象的 m_bAutoDelete為真,則銷毀文檔對象自身。
- 視窗的銷毀過程
- DestroyWindow
從圖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。
MFC預設實現空
- void CFrameWnd::PostNcDestroy()
調用delete this銷毀自身這個MFC對象。
- void CView::PostNcDestroy()
調用delete this銷毀自身這個MFC對象。
- 解構函式
delete this導致解構函式的調用。需要提到的是CFrameWnd和CView的解構函式。
邊框視窗在建立時,把自身加入到模組-線程狀態的邊框視窗列表m_frameList中。現在,從列表中移走該視窗對象。
必要的話,刪除m_phWndDisable數組。
在視建立時,把自身加入到文檔對象的視列表中。現在,從列表中移走該視對象。
應用程式調用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將銷毀該文檔邊框視窗(包括其子視窗視)。