首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

MFC 應用程式優雅退出全流程:從 WM_CLOSE 到文檔關閉的完整機制

     編輯:關於C語言

退出觸發點:使用者如何說「再見」

Windows 應用程式進入訊息迴圈後,直到收到 WM_QUIT 才真正結束。常見觸發路徑:

  • 點擊主視窗標題列「關閉」鈕 (×) → WM_SYSCOMMAND, SC_CLOSE → WM_CLOSE。
  • 系統選單「關閉」 → 同上。
  • 「檔案」選單「結束」 → 命令 ID_APP_EXIT → CWinApp::OnAppExit() → SendMessage(WM_CLOSE)。
  • PostQuitMessage(0) 直接貼上 WM_QUIT(繞過 WM_CLOSE,不推薦)。

CWinApp::OnAppExit:標準退出入口

void CWinApp::OnAppExit()
{
    ASSERT(m_pMainWnd != NULL);
    m_pMainWnd->SendMessage(WM_CLOSE); // 同步發送,確保關閉流程完成
}

設計為 SendMessage 而非 PostMessage,確保主視窗關閉邏輯(詢問儲存、釋放資源)在 OnAppExit 返回前完成。

CFrameWnd::OnClose:框架視窗關閉的核心邏輯

適用於 SDI 主框架、MDI 主框架 (CMDIFrameWnd)、MDI 子框架 (CMDIChildWnd)。流程圖:

詳細步驟

  1. 可關閉檢查:m_lpfnCloseProc 非空時呼叫(列印預覽等特殊模式)。
  2. 判斷視窗角色:
    • 主視窗 (m_pMainWnd) → 進入「關閉應用程式」流程。
    • 非主視窗 (MDI 子視窗) → 進入「關閉文檔/子視窗」流程。

主視窗關閉:關閉所有文檔 → 銷毀所有視窗 → 退出

// 簡化版 CFrameWnd::OnClose 主視窗分支
void CFrameWnd::OnClose()
{
    if (this == AfxGetApp()->m_pMainWnd) {
        // 1. 關閉所有開啟文檔 (會觸發各文檔的 SaveModified、DeleteContents)
        CDocManager* pDocMgr = AfxGetApp()->m_pDocManager;
        if (pDocMgr) pDocMgr->CloseAllDocuments(FALSE); // FALSE = 不提示儲存?實際會提示

        // 2. 銷毀主框架視窗 (觸發 WM_DESTROY → PostQuitMessage)
        DestroyWindow(); 
    }
    else { /* MDI 子視窗分支見下 */ }
}

CloseAllDocuments 會逐一呼叫 CDocument::OnCloseDocument → SaveModified (詢問儲存) → DeleteContents → 移除視圖/框架。

MDI 子視窗關閉:視文檔共享狀態決定

// MDI 子視窗分支
void CMDIChildWnd::OnClose()
{
    CDocument* pDoc = GetActiveDocument();
    if (!pDoc) { DestroyWindow(); return; }

    // 計算有多少視圖/框架正在顯示此文檔
    int nViewCount = 0;
    POSITION pos = pDoc->GetFirstViewPosition();
    while (pos) {
        CView* pView = pDoc->GetNextView(pos);
        if (pView->GetParentFrame() != this) nViewCount++;
    }

    if (nViewCount == 0) {
        // 此文檔僅被本視窗顯示 → 關閉文檔 (含儲存提示) + 銷毀視窗
        pDoc->OnCloseDocument();
    } else {
        // 文檔還被其他視窗顯示 → 僅銷毀本視窗,文檔保留
        DestroyWindow();
    }
}

WM_DESTROY → WM_QUIT:訊息迴圈的終點

// CWnd::OnDestroy 處理 WM_DESTROY
void CWnd::OnDestroy()
{
    // 清理視窗相關資源
    if (m_hWnd == AfxGetApp()->m_pMainWnd->m_hWnd) {
        // 主視窗被銷毀 → 發送 WM_QUIT 結束訊息迴圈
        PostQuitMessage(0);
    }
    // 釋放 MFC 內部資源 (HandleMap 移除等)
    Detach();
}

CWinApp::Run() 訊息迴圈:

int CWinApp::Run()
{
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0)) {
        if (!PreTranslateMessage(&msg)) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    return msg.wParam; // WM_QUIT 的 wParam (通常 0)
}

自訂退出邏輯:覆寫點與最佳實踐

需求覆寫位置注意事項
攔截關閉、詢問確認CWnd::OnClose() 或 OnQueryEndSession()呼叫 DestroyWindow() 繼續流程,或直接 return 取消關閉。
文檔儲存前檢查CDocument::SaveModified()回傳 TRUE 允許關閉,FALSE 取消。預設彈出「是/否/取消」對話框。
文檔銷毀時清理資料CDocument::DeleteContents()釋放文檔持有的記憶體、關閉資料庫連線等。
應用程式級清理CWinApp::ExitInstance()訊息迴圈結束後呼叫,釋放全域資源、寫入登錄設定。
強制立即退出 (不提示)AfxGetApp()->m_pMainWnd->DestroyWindow()繞過 OnClose,慎用。或 PostQuitMessage 直接結束迴圈。

常見陷阱與解決

  • 模態對話框阻塞退出:若 DoModal() 正在執行,主視窗 DestroyWindow 不會關閉對話框。需在 OnClose 中 EndDialog 所有模態對話框,或使用 PostMessage(WM_CLOSE) 讓對話框自行處理。
  • 背景執行緒未結束:ExitInstance 前務必 WaitForSingleObject 等待工作執行緒結束,否則進程殘留。
  • MDI 子視窗關閉不觸發文檔關閉:檢查 CDocument::m_bAutoDelete (預設 TRUE) 及視圖計數邏輯。
  • 系統關機 (WM_QUERYENDSESSION/WM_ENDSESSION):覆寫 CWnd::OnQueryEndSession 返回 TRUE 允許關機,FALSE 阻止。限時 5 秒必須返回。

總結

MFC 退出流程是一個層層遞進的協作過程:使用者動作 → WM_CLOSE → CFrameWnd::OnClose → (主視窗)關閉所有文檔 → 銷毀主視窗 → WM_DESTROY → PostQuitMessage → 訊息迴圈結束 → ExitInstance。掌握每個階段的覆寫點、預設行為、執行順序,能讓您實現「優雅退出、資料不遺失、資源不洩漏」的專業級應用程式。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved