You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MFC中CButton子类OnNcCreate未调用及BS_OWNERDRAW设置问题

问题分析与解决方案

一、为什么CBad的OnNcCreate()不被调用?

核心问题出在你的WndProcHook实现和窗口创建的消息时机上:

WM_NCCREATE是窗口创建过程中第一个发送的消息,它在窗口的非客户区初始化前就会触发。而你的钩子逻辑是:

只有当收到第一个消息时,才new出CBad对象、Attach窗口,并将窗口过程替换为AfxWndProc(MFC的消息分发入口)

但WM_NCCREATE就是这个"第一个消息"——当钩子收到它时,MFC的对象还没关联,窗口过程也还没换成AfxWndProc,所以这个消息直接被钩子处理(没转发给MFC的消息映射),导致OnNcCreate这个MFC消息处理函数根本没机会被调用。

至于CGood类(继承CStatic)用"button"类却能正常工作,这完全是巧合:static和button窗口类的内部创建流程有差异,可能WM_NCCREATE的发送时机刚好晚于钩子完成对象关联的时机,但这种情况不具备通用性,不能依赖。

二、还有哪些位置可以设置BS_OWNERDRAW?

根据你的场景,有几个更可靠的方式:

1. 注册窗口类时直接添加默认样式

在RegisterControlClass中,复制button的窗口类信息后,直接把BS_OWNERDRAW加到窗口类的默认样式里。这样所有用CBad类创建的控件,天生就带这个标志:

BOOL CBad::RegisterControlClass() {
    WNDCLASS wcls;
    static const TCHAR szClass[] = _T( "CBad" );
    if ( ::GetClassInfo( AfxGetInstanceHandle(), szClass, &wcls ) )
        return wcls.lpfnWndProc == ( WNDPROC ) CBad::WndProcHook;
    VERIFY( ::GetClassInfo( NULL, _T( "button" ), &wcls ) );
    pfnWndProc = wcls.lpfnWndProc;
    wcls.lpfnWndProc = CBad::WndProcHook;
    wcls.hInstance = AfxGetInstanceHandle();
    wcls.lpszClassName = szClass;
    // 直接给窗口类添加BS_OWNERDRAW样式
    wcls.style |= BS_OWNERDRAW;
    return RegisterClass( &wcls ) != 0;
}

2. 改进钩子逻辑,让WM_NCCREATE能被MFC处理

调整WndProcHook,在收到WM_NCCREATE时先完成对象关联和窗口过程替换,再把这个消息转发给AfxWndProc,这样OnNcCreate就能正常触发:

LRESULT CALLBACK EXPORT CBad::WndProcHook( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam ) {
    if (msg == WM_NCCREATE) {
        // 先完成MFC对象关联
        CBad* pthis = new CBad();
        pthis->Attach( hWnd );
        pthis->m_pfnSuper = pfnWndProc;
        ::SetWindowLong( hWnd, GWL_WNDPROC, ( DWORD )AfxWndProc );
        // 把当前的WM_NCCREATE消息交给MFC处理
        return ::CallWindowProc( ( FARPROC )AfxWndProc, hWnd, msg, wParam, lParam );
    }
    // 未关联对象前,其他消息交给原button窗口过程处理
    return ::CallWindowProc(pfnWndProc, hWnd, msg, wParam, lParam);
}

3. 使用MFC自带的子类化机制(更推荐)

放弃自己写钩子的方式,改用MFC标准的子类化流程:

  • 在CBad类中添加DECLARE_DYNAMIC(CBad)和DECLARE_MESSAGE_MAP宏
  • 在对话框的OnInitDialog中,用SubclassDlgItem关联控件:
    CBad* pcbut = new CBad();
    pcbut->SubclassDlgItem(IDC_MARKCHECK, this);
    // 这里可以直接设置BS_OWNERDRAW
    pcbut->ModifyStyle(0, BS_OWNERDRAW);
    

这种方式下MFC会自动处理消息映射,OnNcCreate也能正常被调用。

4. 动态创建时直接指定样式

如果是动态创建CBad按钮,在调用CreateEx时直接把BS_OWNERDRAW加到样式参数里:

CBad* pBtn = new CBad();
pBtn->CreateEx(WS_EX_CLIENTEDGE, _T("CBad"), _T("My Button"), 
               WS_CHILD | WS_VISIBLE | BS_OWNERDRAW,
               CRect(10,10,100,30), this, IDC_MARKCHECK);

额外验证

你之前在OnInitDialog中用ModifyStyle能生效,是因为这时候窗口已经创建完成,修改样式后MFC能正确触发DrawItem的逻辑,这也是一种稳定的补救方式。

内容的提问来源于stack exchange,提问作者Swiss Frank

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 07:47:43