MFC 將 Windows 原始消息封裝為 OnXxx 虛函數,視圖類 (CView) 與框架窗口類 (CFrameWnd) 均可重寫。以下五大消息貫穿視窗從誕生到銷毀的全過程,**必須精通**。
CWnd::Create 內部調用 CreateWindowEx 後,窗口尚未可見、子控件未創建、DC 未就緒。0 繼續創建;-1 銷毀窗口並返回創建失敗。GetClientRect, SetTimer, CreateChildControls)。OnInitialUpdate (視圖) 或 OnPostCreate (框架) 中執行需窗口就緒的初始化。注意 SDI 應用中 OnInitialUpdate 可能被多次調用。OnClose 可**完全攔截關閉流程** — 彈出「保存確認」、最小化到托盤、延遲關閉等。CDocument::SaveModified 同樣可在關閉前提示保存 (文檔級)。CWnd::OnClose** (內部發送 WM_DESTROY),否則窗口不會銷毀。ExitWindowsEx。系統向**所有頂層窗口**廣播。TRUE 允許關機;FALSE 取消關機 (如有未保存數據強制攔截)。WM_ENDSESSION (wParam=TRUE) 通知確定關機;否則發送 WM_ENDSESSION (wParam=FALSE) 取消。WM_CLOSE 後,DestroyWindow 內部發送。窗口已從螢幕移除,但**進程仍存在**、句柄仍有效、子窗口正在銷毀中。CWnd::OnDestroy** (內部發送 WM_NCDESTROY、清理內部映射表)。**不可**在這裡調用 PostQuitMessage (那屬於 OnClose 的職責)。CWnd 對象即將刪除。HWND→CWnd* 關聯 (MFC 內部自動完成)。CWnd::OnNcDestroy** (內部執行 delete this 對於動態創建的窗口對象)。用戶點擊關閉按鈕
│
▼
WM_CLOSE → OnClose() [可攔截] → 調用基類
│
▼
WM_DESTROY → OnDestroy() [釋放資源] → 調用基類
│
▼
WM_NCDESTROY → OnNcDestroy() [最終清理] → 調用基類 → delete this
│
▼
PostQuitMessage(0) → 消息循環結束 → WinMain 返回 → 進程退出
void CMy007View::OnDraw(CDC* pDC) {
pDC->TextOut(100, 100, "請不要單擊鼠標右鍵,否則後果自負!");
}
void CMy007View::OnRButtonDown(UINT nFlags, CPoint point) {
::MessageBox(NULL, "你確定要這樣做嗎?", "警告", MB_OK);
::ExitWindowsEx(EWX_SHUTDOWN | EWX_FORCE, 0); // 強制關機
}
**原理**:ExitWindowsEx 廣播 WM_QUERYENDSESSION,若無程式攔截則強制關機。展示了消息廣播機制的威力。
| 錯誤 | 後果 | 正確做法 |
|---|---|---|
| OnCreate 中調用 GetClientRect | 返回全零矩形,導致佈局錯誤 | 移至 OnInitialUpdate / OnSize |
| OnClose 不調用基類 | 窗口永遠不關閉,進程殭屍 | 最後調用 CWnd::OnClose() |
| OnDestroy 中 PostQuitMessage | 多文檔應用關閉一個文檔就退出整個程式 | 只在主框架 OnClose 中調用 |
| OnNcDestroy 不調用基類 | CWnd 對象記憶體洩漏,HWND 映射表殘留 | 最後調用 CWnd::OnNcDestroy() |
OnXxx 加 TRACE 輸出時間戳,觀察調用堆疊。Day 6:映射模式大全 — MM_TEXT, MM_HIMETRIC, MM_ANISOTROPIC... 邏輯座標與設備座標轉換、滾動視圖 CScrollView 實戰、滾動條鍵盤控制。