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

