一個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。
CWnd::PostNcDestroy()
MFC缺省實現空
void CFrameWnd::PostNcDestroy()
調用delete this銷毀自身這個MFC對象。
void CView::PostNcDestroy()
調用delete this銷毀自身這個MFC對象。
析構函數
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將銷毀該文檔邊框窗口(包括其子窗口視)。