CDockablePane上添加的按钮无效,不可用

今天突然发现了一个问题, 就是在这种DockablePane中添加CButton对象时, 竟然这些按钮都是不可用的, 为此跟了许多的代码来搞定这个问题. 当然了, 我的解决方法很土很恶心, 完全是治标不治本的, 应付一下需求而已…

一. 先说原因
1. 首先说下类的结构:
继承关系是CDockablePane –> CBasePane –> CWnd
2. 再说说原因
(1). 在CBasePane中有一个函数OnIdleUpdateCmdUI, 其原型为:
afx_msg LRESULT OnIdleUpdateCmdUI(WPARAM wParam, LPARAM);
该函数用来在idle的时刻更新一些UI上的东西, 这里说的”空闲”是指没有需要处理的消息.
(2). 该函数实际上是在内部调用了OnUpdateCmdUI()函数, 该函数是定义在CBasePane类中的虚函数, 原型为:
virtual void OnUpdateCmdUI(CFrameWnd* pTarget, BOOL bDisableIfNoHndler);
该函数的参数第二个bool类型参数表示当前控件如果没有消息处理函数是否需要被禁用bDisableIfNoHndler, 在上面所说的这个过程中, 该值为true, 也就是说: 如果一个button没有响应update_command_ui消息的handler, 那么该button会被禁用.
(3). 该函数OnUpdateCmdUI()是在CDockablePane中被重写了的, 其内部调用了CWnd类的CWnd::UpdateDialogControls(), 从名字上也可以看出, 该函数的作用是更新对话框中的控件
(4). 该函数不是虚函数, 其函数定义在CWnd类中, 其函数原型为:
void CWnd::UpdateDialogControls(CCmdTarget* pTarget, BOOL bDisableIfNoHndler)
在该函数内部, 截取其中几段代码为:
// 1. 这段代码用来检查该控件自身是否含有响应update_command_ui的响应函数, 而用的消息是WM_COMMAND+WM_REFLECT_BASE
// check for reflect handlers in the child window
CWnd* pWnd = CWnd::FromHandlePermanent(hWndChild);
if (pWnd != NULL)
{
// call it directly to disable any routing
if (pWnd->CWnd::OnCmdMsg(0, MAKELONG(0xffff,
WM_COMMAND+WM_REFLECT_BASE), &state, NULL))
continue;
}
在MFC中, 如果需要为控件设置该处理函数, 有三个宏可以设置该WM_COMMAND+WM_REFLECT_BASE消息的处理函数, 分别是: ON_CONTROL_REFLECT, ON_CONTROL_REFLECT_EX和ON_UPDATE_COMMAND_UI_REFLECT
// 2. 如果该控件的父窗口也可以处理该update_command_ui消息, 那么也可以
// check for handlers in the parent window
if (CWnd::OnCmdMsg((UINT)state.m_nID, CN_UPDATE_COMMAND_UI, &state, NULL))
continue;
但是如果万一没有人可以处理该消息, 那么, 就是
// determine whether to disable when no handler exists
BOOL bDisableTemp = bDisableIfNoHndler;
if (bDisableTemp)
{
if ((wndTemp.SendMessage(WM_GETDLGCODE) & DLGC_BUTTON) == 0)
{
// non-button controls don’t get automagically disabled
bDisableTemp = FALSE;
}
else
{
// only certain button controls get automagically disabled
UINT nStyle = (UINT)(wndTemp.GetStyle() & 0x0F);
if (nStyle == (UINT)BS_AUTOCHECKBOX ||
nStyle == (UINT)BS_AUTO3STATE ||
nStyle == (UINT)BS_GROUPBOX ||
nStyle == (UINT)BS_AUTORADIOBUTTON)
{
bDisableTemp = FALSE;
}
}
}
也就是说, 如果是按钮, 那么就要被禁用, 其它好像也还有几种, 也是需要被禁用的.
//3. 在这步以后, 要调用CCmdUI的DoUpdate()函数, 该函数原型为:
BOOL DoUpdate(CCmdTarget* pTarget, BOOL bDisableIfNoHndler);
这个函数里面搞不清楚是怎么回事, 但其中调用了CCmdUI::Enable()函数, 在该函数中使得按钮被禁用了

二. 接下来说解决办法
1. 最粗鲁的方法就是直接改变原来的机制
由于在CBasePane中含有OnUpdateCmdUI(CFrameWnd* pTarget, BOOL bDisableIfNoHndler)函数, 其第二个参数意思就是: 如果该控件没有处理函数, 那么是否需要禁用. 默认值为true, 所以只需要重载该函数就差不多了. 但是我们肯定要重载的是CDockablePane中的该函数, 因为在CBasePane中该函数什么也不做, 重载方法很简单, 从CDockablePane中抄写一下就行了:
void CDockablePane_1::OnUpdateCmdUI(class CFrameWnd *pTarget, int bDisableIfNoHndler)
{
//UpdateDialogControls(pTarget, bDisableIfNoHndler);
UpdateDialogControls(pTarget, FALSE); //就是这儿了…

CWnd* pFocus = GetFocus();
BOOL bActiveOld = m_bActive;

m_bActive = (pFocus->GetSafeHwnd() != NULL && (IsChild(pFocus) || pFocus->GetSafeHwnd() == GetSafeHwnd()));

if (m_bActive != bActiveOld)
{
SendMessage(WM_NCPAINT);
}
}
但是这改变了所有控件的系统默认处理机制, 故不推荐啊!!!
2. 让按钮自己来处理on_update_cmd消息
由于按钮是没有对该消息做出处理才导致被禁用的(见原因分析), 所以我们可以自定义一个CMsgButton, 让类中含有该消息的处理部分就行了.如下:
// 头文件
class CMsgButton : public CButton
{
// 其它代码

void OnUpdateUI(CCmdUI *cmdUI);

};
// cpp文件
BEGIN_MESSAGE_MAP(CMsgButton, CButton)
ON_UPDATE_COMMAND_UI_REFLECT(OnUpdateUI)
END_MESSAGE_MAP()
void CMsgButton::OnUpdateUI(CCmdUI *cmdUI) {} // 有该函数就行了
3. 让父窗口处理
按钮的on_update_cmd之类的消息, 也可以由其父窗口来响应(见原因分析), 以下的代码均在CDockablePane派生类中出现:
ON_UPDATE_COMMAND_UI(1988, OnUpdateCommandUI) // 其中的1988是按钮的id, 需要让哪个按钮可用, 这儿就应该出现这个id
// 至少这个函数, 还是有就行, 不一定要有内容
void COutputWnd::OnUpdateCommandUI(CCmdUI *cmdUI) {}
4. 其它方法…
// todo

你可能感兴趣的:(CDockablePane上添加的按钮无效,不可用)