承接上篇對模式對話框的剖析,本文聚焦無模式對話框的 MFC 實現、CFormView 作為無模式對話框視圖的設計精髓,以及 PreTranslateMessage 訊息預處理機制在兩者中的關鍵差異。
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 銷毀視窗。
class CDataExchange {
public:
BOOL m_bSaveAndValidate; // TRUE: 控制項 → 變數;FALSE: 變數 → 控制項
CWnd* m_pDlgWnd; // 目標對話框
HWND PrepareCtrl(int nIDC); // 取得控制項 HWND
HWND PrepareEditCtrl(int nIDC); // 取得編輯框 HWND
void Fail(); // 拋出 CUserException
};
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_Text(pDX, IDC_EDIT, strVal):編輯框 ↔ CString/數值DDX_Check(pDX, IDC_CHECK, boolVal):核取方塊 ↔ BOOLDDX_Control(pDX, IDC_LIST, m_listCtrl):子類化控制項 ↔ CWnd 衍生變數DDV_MinMaxInt(pDX, val, min, max):範圍驗證關鍵實作:DDX_Control 呼叫 SubclassWindow 將 Windows 控制項「子類化」為 MFC 物件,實現封裝操作。
CFormView 繼承 CScrollView → CView,核心功能:
SetScrollSizes、SetScaleToFitSize)OnHScroll/OnVScroll、OnSize)OnPrepareDC、OnScroll 供衍生類自訂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);
};
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、捲軸屬性,並接管焦點管理。
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 置頂 |
| 建立 API | DialogBoxIndirect 系 | CreateDialogIndirect 系 |
| PreTranslateMessage | 直接 IsDialogMessage | 框架優先 → IsDialogMessage |
| 焦點管理 | 系統自動 | CFormView 手動持久化 |
| 資料交換 | OnInitDialog/OnOK 自動 | 需手動呼叫 UpdateData |
MFC 以 CFormView 為樞紐,將對話框模板的易用性與 Document/View 架構的強大功能完美融合,PreTranslateMessage 的層級化設計更體現了框架對「使用者體驗優先」的堅持。