承上集,我們已了解 MFC 定義五個預設視窗類別字串。本文將深入追蹤 AfxDeferRegisterClass 如何延遲註冊,並解析 CWnd::CreateEx → PreCreateWindow 如何自動綁定正確類別,最後示範如何在 MFC 專案中註冊專屬視窗類別。
MFC 使用位元旗標 afxRegisteredClasses 記錄已註冊類別,避免重複註冊:
#define AfxDeferRegisterClass(fClass) \
((afxRegisteredClasses & fClass) ? TRUE : AfxEndDeferRegisterClass(fClass))
#define afxRegisteredClasses AfxGetModuleState()->m_fRegisteredClasses
BOOL AFXAPI AfxEndDeferRegisterClass(short fClass)
{
WNDCLASS wndCls = {0};
wndCls.lpfnWndProc = DefWindowProc; // 預設訊息處理
if (fClass & AFX_WND_REG) {
wndCls.lpszClassName = _afxWnd;
AfxRegisterClass(&wndCls);
}
else if (fClass & AFX_WNDOLECONTROL_REG) {
wndCls.lpszClassName = _afxWndOleControl;
AfxRegisterClass(&wndCls);
}
else if (fClass & AFX_WNDCONTROLBAR_REG) {
wndCls.lpszClassName = _afxWndControlBar;
AfxRegisterClass(&wndCls);
}
else if (fClass & AFX_WNDMDIFRAME_REG) {
RegisterWithIcon(&wndCls, _afxWndMDIFrame, AFX_IDI_MDIFRAME);
}
else if (fClass & AFX_WNDFRAMEORVIEW_REG) {
RegisterWithIcon(&wndCls, _afxWndFrameOrView, AFX_IDI_STD_FRAME);
}
else if (fClass & AFX_WNDCOMMCTLS_REG) {
InitCommonControls();
}
return TRUE;
}
關鍵點:RegisterWithIcon 內部仍呼叫 AfxRegisterClass,最終落實到 SDK 的 RegisterClass。這證明 MFC 只是封裝而非重寫底層機制。
CWnd::CreateEx 是所有視窗建立的核心入口:
BOOL CWnd::CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName, ...)
{
CREATESTRUCT cs;
cs.dwExStyle = dwExStyle;
cs.lpszClass = lpszClassName;
...
PreCreateWindow(cs); // 關鍵:讓衍生類別修改 cs.lpszClass
AfxHookWindowCreate(this);
HWND hWnd = ::CreateWindowEx(cs.dwExStyle, cs.lpszClass, ...);
...
}
PreCreateWindow 是虛擬函數,各衍生類別覆寫以指定專屬類別:
BOOL CWnd::PreCreateWindow(CREATESTRUCT& cs)
{
if (cs.lpszClass == NULL) {
AfxDeferRegisterClass(AFX_WND_REG);
cs.lpszClass = _afxWnd;
}
return TRUE;
}
BOOL CFrameWnd::PreCreateWindow(CREATESTRUCT& cs)
{
if (cs.lpszClass == NULL) {
AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG);
cs.lpszClass = _afxWndFrameOrView;
}
return CWnd::PreCreateWindow(cs);
}
BOOL CMDIFrameWnd::PreCreateWindow(CREATESTRUCT& cs)
{
if (cs.lpszClass == NULL) {
AfxDeferRegisterClass(AFX_WNDMDIFRAME_REG);
cs.lpszClass = _afxWndMDIFrame;
}
return CFrameWnd::PreCreateWindow(cs);
}
此鏈結機制確保:CFrameWnd 用 "FrameOrView"、CMDIFrameWnd 用 "MDIFrame"、CView 同用 "FrameOrView",完美對應視窗角色。
若需自訂類別風格(如特殊背景筆刷、無邊框、專屬訊息處理),在衍生自 CWnd 的類別中覆寫 PreCreateWindow:
class CMyCustomWnd : public CWnd
{
DECLARE_DYNCREATE(CMyCustomWnd)
public:
BOOL PreCreateWindow(CREATESTRUCT& cs) override
{
// 1. 註冊專屬類別(僅第一次執行)
static bool s_bRegistered = false;
if (!s_bRegistered) {
WNDCLASS wc = {0};
wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
wc.lpfnWndProc = ::DefWindowProc; // 或自訂 WndProc
wc.hInstance = AfxGetInstanceHandle();
wc.hbrBackground = CreateSolidBrush(RGB(240,240,240));
wc.lpszClassName = _T("MyCustomWndClass");
AfxRegisterClass(&wc);
s_bRegistered = true;
}
// 2. 指定使用該類別
cs.lpszClass = _T("MyCustomWndClass");
cs.style |= WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
return CWnd::PreCreateWindow(cs);
}
};
建立視窗時:
CMyCustomWnd* pWnd = new CMyCustomWnd;
pWnd->CreateEx(0, _T("MyCustomWndClass"), _T("自訂視窗"),
WS_OVERLAPPEDWINDOW, CRect(100,100,500,400),
pParentWnd, 0);
GetClassInfo 取得現有類別(如 "BUTTON"),修改 lpfnWndProc 與 lpszClassName 後重新註冊,即可繼承原控制項行為並擴充功能。WNDCLASS 指定自訂 lpfnWndProc,MFC 的訊息映射將失效;建議保持 DefWindowProc 並靠 PreCreateWindow 只修改風格/筆刷/類別名。DllMain 或 CWinApp::InitInstance 確保 AfxGetModuleState() 正確。MFC 透過 AfxDeferRegisterClass 延遲註冊與 PreCreateWindow 虛擬函數鏈結,優雅地封裝了 SDK 視窗類別機制。理解此流程不僅能解決疑難雜症,更能在需要時靈活註冊專屬類別,打造高度客製化的視窗行為。