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

MFC 對話框機制深度解析(二):無模式對話框、CFormView 與訊息預處理機制

     編輯:關於VC++

前言

承接上篇對模式對話框的剖析,本文聚焦無模式對話框的 MFC 實現、CFormView 作為無模式對話框視圖的設計精髓,以及 PreTranslateMessage 訊息預處理機制在兩者中的關鍵差異。

一、模式對話框收尾:RunModalLoop 與 EndDialog

RunModalLoop 實現獨立訊息循環,直到 m_nFlags & WF_CONTINUEMODAL 清零:

int CWnd::RunModalLoop(DWORD dwFlags)
{
    m_nFlags |= (WF_MODALLOOP | WF_CONTINUEMODAL);
    MSG* pMsg = &AfxGetThread()->m_msgCur;

    for (;;) {
        // Idle 階段:顯示視窗、發送 WM_ENTERIDLE、WM_KICKIDLE
        while (bIdle && !::PeekMessage(pMsg, NULL, 0, 0, PM_NOREMOVE)) { ... }

        // Dispatch 階段
        do {
            if (!AfxGetThread()->PumpMessage()) { AfxPostQuitMessage(0); return -1; }
            if (!ContinueModal()) goto ExitModal;
            if (IsIdleMessage(pMsg)) { bIdle = TRUE; lIdleCount = 0; }
        } while (::PeekMessage(pMsg, NULL, 0, 0, PM_NOREMOVE));
    }
ExitModal:
    m_nFlags &= ~(WF_MODALLOOP | WF_CONTINUEMODAL);
    return m_nModalResult;
}

EndDialog → EndModalLoop → PostMessage(WM_NULL) 觸發循環退出,最後 DestroyWindow 銷毀視窗。

二、對話框資料交換 (DDX/DDV) 機制

核心類別:CDataExchange

class CDataExchange {
public:
    BOOL m_bSaveAndValidate; // TRUE: 控制項 → 變數;FALSE: 變數 → 控制項
    CWnd* m_pDlgWnd;         // 目標對話框
    HWND PrepareCtrl(int nIDC);        // 取得控制項 HWND
    HWND PrepareEditCtrl(int nIDC);    // 取得編輯框 HWND
    void Fail();                       // 拋出 CUserException
};

UpdateData 雙向驅動

BOOL CWnd::UpdateData(BOOL bSaveAndValidate)
{
    CDataExchange dx(this, bSaveAndValidate);
    // 鎖定通知訊息防止遞迴
    _AFX_THREAD_STATE* pState = AfxGetThreadState();
    HWND hOld = pState->m_hLockoutNotifyWindow;
    pState->m_hLockoutNotifyWindow = m_hWnd;

    TRY { DoDataExchange(&dx); bOK = TRUE; }
    CATCH(CUserException, e) { /* 驗證失敗 */ }
    END_CATCH

    pState->m_hLockoutNotifyWindow = hOld;
    return bOK;
}

常用 DDX/DDV 函式

  • DDX_Text(pDX, IDC_EDIT, strVal):編輯框 ↔ CString/數值
  • DDX_Check(pDX, IDC_CHECK, boolVal):核取方塊 ↔ BOOL
  • DDX_Control(pDX, IDC_LIST, m_listCtrl):子類化控制項 ↔ CWnd 衍生變數
  • DDV_MinMaxInt(pDX, val, min, max):範圍驗證

關鍵實作:DDX_Control 呼叫 SubclassWindow 將 Windows 控制項「子類化」為 MFC 物件,實現封裝操作。

三、無模式對話框與 CFormView

CScrollView 基礎

CFormView 繼承 CScrollView → CView,核心功能:

  • 映射模式與座標轉換(SetScrollSizes、SetScaleToFitSize)
  • 自動捲軸管理(OnHScroll/OnVScroll、OnSize)
  • 虛擬函式 OnPrepareDC、OnScroll 供衍生類自訂

CFormView 關鍵覆寫

class CFormView : public CScrollView {
    LPCTSTR m_lpszTemplateName;   // 對話框模板資源名
    CCreateContext* m_pCreateContext;
    HWND m_hWndFocus;             // 記錄焦點控制項

    // 覆寫建立流程:使用 CreateDialogIndirect
    virtual BOOL Create(LPCTSTR, LPCTSTR, DWORD, const RECT&, CWnd*, UINT, CCreateContext*);

    // 訊息預處理:優先讓框架視窗處理快捷鍵
    virtual BOOL PreTranslateMessage(MSG* pMsg);

    // 焦點管理
    virtual void OnActivateView(BOOL, CView*, CView*);
    virtual void OnActivateFrame(UINT, CFrameWnd*);
    afx_msg int OnCreate(LPCREATESTRUCT);
    afx_msg void OnSetFocus(CWnd* pOldWnd);
};

Create 核心流程

BOOL CFormView::Create(...)
{
    // 1. 驗證模板存在且 WS_CHILD
    _AfxCheckDialogTemplate(m_lpszTemplateName, TRUE);

    // 2. PreCreateWindow 讓使用者調整風格
    CREATESTRUCT cs; PreCreateWindow(cs);

    // 3. CreateDlgIndirect → 實際呼叫 ::CreateDialogIndirect 建立無模式對話框
    if (!CreateDlg(m_lpszTemplateName, pParentWnd)) return FALSE;

    // 4. 修正視窗風格(WS_BORDER/WS_CAPTION/WS_EX_CLIENTEDGE)
    ModifyStyle(WS_BORDER|WS_CAPTION, cs.style & (WS_BORDER|WS_CAPTION));
    ModifyStyleEx(WS_EX_CLIENTEDGE, cs.dwExStyle & WS_EX_CLIENTEDGE);

    // 5. 設定控制項 ID 為 AFX_IDW_PANE_FIRST
    SetDlgCtrlID(nID);

    // 6. 初始化捲軸尺寸 = 模板大小
    CRect rectTemplate; GetWindowRect(rectTemplate);
    SetScrollSizes(MM_TEXT, rectTemplate.Size());

    // 7. 執行 DLGINIT 資源初始化控制項
    ExecuteDlgInit(m_lpszTemplateName);

    // 8. 強制調整至要求矩形、顯示
    SetWindowPos(NULL, rect.left, rect.top, rect.Width(), rect.Height(),
                 SWP_NOZORDER|SWP_NOACTIVATE);
    if (dwRequestedStyle & WS_VISIBLE) ShowWindow(SW_NORMAL);
    return TRUE;
}

關鍵點:無模式對話框建立後,MFC 將其「提升」為視圖視窗,調整風格、ID、捲軸屬性,並接管焦點管理。

PreTranslateMessage:框架優先原則

BOOL CFormView::PreTranslateMessage(MSG* pMsg)
{
    // 1. Tooltip 處理(基類 CView)
    if (CView::PreTranslateMessage(pMsg)) return TRUE;

    // 2. SHIFT+F1 說明模式下不處理 Dialog 訊息
    CFrameWnd* pFrame = GetTopLevelFrame();
    if (pFrame && pFrame->m_bHelpMode) return FALSE;

    // 3. 關鍵差異:優先讓所有父框架視窗預處理(處理快捷鍵)
    pFrame = GetParentFrame();
    while (pFrame) {
        if (pFrame->PreTranslateMessage(pMsg)) return TRUE;
        pFrame = pFrame->GetParentFrame();
    }

    // 4. 最後才處理 Dialog 訊息(Tab/方向鍵導航)
    return PreTranslateInput(pMsg); // 內部呼叫 ::IsDialogMessage
}

對比 CDialog::PreTranslateMessage(直接呼叫 PreTranslateInput),CFormView 確保功能表快捷鍵、工具列按鈕等框架層級快捷鍵優先生效。

焦點持久化

  • OnActivateView(FALSE) / OnActivateFrame:儲存 m_hWndFocus = ::GetFocus()。
  • OnSetFocus:若 m_hWndFocus 有效,::SetFocus(m_hWndFocus) 還原焦點。

四、總結

特性模式對話框 (CDialog)無模式對話框 / CFormView
訊息循環RunModalLoop 獨立循環共用主訊息循環
父視窗狀態Disable 直到結束保持啟用,Z-order 置頂
建立 APIDialogBoxIndirect 系CreateDialogIndirect 系
PreTranslateMessage直接 IsDialogMessage框架優先 → IsDialogMessage
焦點管理系統自動CFormView 手動持久化
資料交換OnInitDialog/OnOK 自動需手動呼叫 UpdateData

MFC 以 CFormView 為樞紐,將對話框模板的易用性與 Document/View 架構的強大功能完美融合,PreTranslateMessage 的層級化設計更體現了框架對「使用者體驗優先」的堅持。

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