承接上篇「MFC 框架視窗註冊與類別設計」,本文深入剖析 CWnd::CreateEx、CFrameWnd::Create、虛函數 PreCreateWindow 如何協同完成視窗建立、樣式修改,以及 ShowWindow/UpdateWindow 顯示更新機制。
CWinApp::InitInstance
→ CFrameWnd::Create (或衍生類覆寫)
→ CWnd::CreateEx
→ PreCreateWindow (虛函數,可修改 CREATESTRUCT)
→ AfxHookWindowCreate (MFC 內部掛鉤)
→ ::CreateWindowEx (Win32 API 真正建立 HWND)
→ OnCreate (WM_CREATE 處理)
→ m_pMainWnd->ShowWindow(SW_SHOW)
→ m_pMainWnd->UpdateWindow()
BOOL CWnd::CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName,
LPCTSTR lpszWindowName, DWORD dwStyle,
int x, int y, int nWidth, int nHeight,
HWND hWndParent, HMENU nIDorHMenu, LPVOID lpParam)
{
CREATESTRUCT cs;
cs.dwExStyle = dwExStyle;
cs.lpszClass = lpszClassName;
cs.lpszName = lpszWindowName;
cs.style = dwStyle;
cs.x = x; cs.y = y; cs.cx = nWidth; cs.cy = nHeight;
cs.hwndParent = hWndParent; cs.hMenu = nIDorHMenu;
cs.hInstance = AfxGetInstanceHandle(); cs.lpCreateParams = lpParam;
// 關鍵:呼叫虛函數 PreCreateWindow,允許衍生類修改 cs
if (!PreCreateWindow(cs)) {
PostNcDestroy(); return FALSE;
}
AfxHookWindowCreate(this); // MFC 內部:將 HWND 與 CWnd* 關聯
HWND hWnd = ::CreateWindowEx(cs.dwExStyle, cs.lpszClass, cs.lpszName,
cs.style, cs.x, cs.y, cs.cx, cs.cy,
cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams);
// ... 後續附加處理
return (hWnd != NULL);
}
BOOL CFrameWnd::Create(LPCTSTR lpszClassName, LPCTSTR lpszWindowName,
DWORD dwStyle, const RECT& rect, CWnd* pParentWnd,
LPCTSTR lpszMenuName, DWORD dwExStyle, CCreateContext* pContext)
{
// ... 處理選單、Context 等 ...
if (!CreateEx(dwExStyle, lpszClassName, lpszWindowName, dwStyle,
rect.left, rect.top, rect.Width(), rect.Height(),
pParentWnd ? pParentWnd->GetSafeHwnd() : NULL, hMenu,
(LPVOID)pContext))
{
TRACE0("Warning: failed to create CFrameWnd.\n");
if (hMenu) DestroyMenu(hMenu);
return FALSE;
}
// ... 後續初始化 ...
return TRUE;
}
CWnd 為 virtual BOOL PreCreateWindow(CREATESTRUCT& cs)。CFrameWnd::PreCreateWindow 預設註冊 _afxWndFrameOrView 類別、加入 WS_EX_CLIENTEDGE 等。BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
if (!CFrameWnd::PreCreateWindow(cs)) return FALSE;
// 移除最大化、最小化、系統選單 (保留關閉)
cs.style &= ~(WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_SYSMENU);
// 或僅移除最大化
// cs.style &= ~WS_MAXIMIZEBOX;
return TRUE;
}
| CREATESTRUCT 欄位 | CreateWindowEx 參數 | 說明 |
|---|---|---|
| dwExStyle | dwExStyle | 擴充視窗樣式 (WS_EX_*) |
| lpszClass | lpClassName | 視窗類別名稱 |
| lpszName | lpWindowName | 視窗標題 |
| style | dwStyle | 視窗樣式 (WS_*) |
| x, y | X, Y | 左上角座標 |
| cx, cy | nWidth, nHeight | 寬、高 |
| hwndParent | hWndParent | 父視窗 |
| hMenu | hMenu | 選單/子視窗 ID |
| hInstance | hInstance | 模組實例 |
| lpCreateParams | lpParam | WM_CREATE 的 lpCreateParams |
順序相反:結構體欄位順序與 API 參數順序相反,但語義完全對應。
// CWinApp::InitInstance 典型結尾
m_pMainWnd->ShowWindow(SW_SHOW); // 發送 WM_SHOWWINDOW,設定可見狀態
m_pMainWnd->UpdateWindow(); // 發送 WM_PAINT,強制立即繪製客戶區
ShowWindow:控制視窗顯示狀態 (SW_SHOW, SW_HIDE, SW_MINIMIZE...)。UpdateWindow:若更新區域非空,繞過訊息佇列直接發送 WM_PAINT,確保首次繪製即時呈現。CMainFrame::PreCreateWindow 設中斷點,觀察 cs 修改前後差異。CWnd::CreateEx 與 CFrameWnd::Create 同設中斷點,驗證呼叫順序。MFC 視窗建立核心在於 CreateEx → PreCreateWindow → CreateWindowEx 三部曲。PreCreateWindow 為開發者保留了修改視窗類別、樣式、位置的最後機會,善用它可避免建立後再 ModifyStyle 的額外開銷與閃爍。