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

Windows 視窗類別註冊機制深度解析(下):MFC 內部實作與自訂類別技巧

     編輯:VC++

前言

承上集,我們已了解 MFC 定義五個預設視窗類別字串。本文將深入追蹤 AfxDeferRegisterClass 如何延遲註冊,並解析 CWnd::CreateEx → PreCreateWindow 如何自動綁定正確類別,最後示範如何在 MFC 專案中註冊專屬視窗類別。

一、AfxDeferRegisterClass 延遲註冊機制

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 與 PreCreateWindow 類別選擇鏈

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",完美對應視窗角色。

三、在 MFC 中註冊自訂視窗類別

若需自訂類別風格(如特殊背景筆刷、無邊框、專屬訊息處理),在衍生自 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 只修改風格/筆刷/類別名。
  • 模組狀態: DLL 專案中註冊類別需在 DllMain 或 CWinApp::InitInstance 確保 AfxGetModuleState() 正確。
  • 類別名稱衝突: 建議加上公司/專案前綴(如 "MyCorp_MyCustomWnd"),避免多模組載入時衝突。

結語

MFC 透過 AfxDeferRegisterClass 延遲註冊與 PreCreateWindow 虛擬函數鏈結,優雅地封裝了 SDK 視窗類別機制。理解此流程不僅能解決疑難雜症,更能在需要時靈活註冊專屬類別,打造高度客製化的視窗行為。

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