深入分析eXtreme Toolkit Toolbar的Command消息

    eXtreme Toolkit(以下简称XTP)的功能强大毋庸置疑,但是虽然号称与MFC完美兼容,很多地方仍然区别很大,当我将原来的MFC程序改用eXtreme Toolkit时,经常出现若干非常奇怪的Bug,非常讨厌!

    Toolbar大概是每个应用程序都必不可少的元素。XTP的Toolbar功能比mfc实现要强大的多,实现也比其复杂得多。分析其Command消息的前世今生,追踪CXTPControlButton代码:
void  CXTPControlButton::OnClick(BOOL bKeyboard, CPoint pt)
{
    
if (!GetEnabled()) return;

    
if (IsCustomizeMode())
    
{
        m_pParent
->SetPopuped(-1);
        m_pParent
->SetSelected(-1);
        CustomizeStartDrag(pt);
        
return;
    }


    
if (!bKeyboard)
    
{
        
if (m_pParent->GetType() != xtpBarTypePopup)
            ClickToolBarButton();
    }

    
else
    
{
        OnExecute();
    }

}
    这里关键在于ClickToolBarButton()和OnExecute()两个函数。继续查看这两个函数,OnExecute()似乎重点在于处理ToolBarButton支持的一些额外功能,如菜单等。真正产生Command消息的是ClickToolBarButton()函数。继续分析这个函数,这个函数是其基类CXTPControl的成员:
void  CXTPControl::ClickToolBarButton(CRect rcActiveRect)
{    
    
    
if (bExecuteOnTimer)
    
{
        m_pParent
->SetTimer(XTP_TID_CLICKTICK, m_nExecuteOnPressInterval, NULL);
        NotifyExecute(
this, pOwner);
    }


    
while (::GetCapture() == hWndCapture)
    
{
        
        
if (msg.message == WM_LBUTTONUP)
        
{
            bClick 
= m_bSelected && ((!pt.x && !pt.y) || rcActiveRect.PtInRect(pt));
            
break;
        }


        
if (m_pParent == NULL)
            
break;

        
if (msg.message == WM_TIMER && msg.wParam == XTP_TID_CLICKTICK)
        
{
            
if (m_bSelected)
            
{
                NotifyExecute(
this, pOwner);
            }

        }

        
    }

    
}
    这里没有细致的分析代码,感觉大意是在WM_LBUTTONDOWN后设置timer,然后在WM_LBUTTONUP后产生Click消息,执行NotifyExecute(this, pOwner)。这个函数是CXTPControl的成员,继续追踪这个函数:
AFX_INLINE  void  NotifyExecute(CXTPControl *  pControl, CWnd *  pOwner)
{
    NMXTPCONTROL tagNMCONTROL;
    
if (pControl->NotifySite(pOwner, CBN_XTP_EXECUTE, &tagNMCONTROL) == 0)
    
{
        pOwner
->SendMessage(WM_COMMAND, pControl->GetID());
    }

}
    至此,心里有个猜想了,敢情先发通知消息,如果未被处理,才发送Command消息。就在当前文件中寻找NotifySite函数:
LRESULT CXTPControl::NotifySite(CWnd *  pSite, UINT code, NMXTPCONTROL *  pNM)
{
    
if (pSite == 0)
    
{
        
if (!m_pParent)
            
return 0;

        pSite 
= m_pParent->GetOwnerSite();
    }


    pNM
->hdr.code = code ;
    pNM
->hdr.idFrom = GetID();
    pNM
->hdr.hwndFrom = 0;
    pNM
->pControl = this;

    LRESULT lResult 
= pSite->SendMessage(WM_XTP_COMMAND, GetID(), (LPARAM)pNM);

    
if (lResult || !m_pParent)
        
return lResult;

    AFX_NOTIFY notify;
    notify.pResult 
= &lResult;
    notify.pNMHDR 
= (NMHDR*)pNM;

    
if (pSite->OnCmdMsg(GetID(), MAKELONG(code, WM_NOTIFY), &notify, NULL))
    
{
        
return lResult;
    }


    
return 0;
}
    这里先发一个用户消息WM_XTP_COMMAND,如果不被处理,调用父窗口OnCmdMsg函数。WM_XTP_COMMAND消息的用途没有找到,只在定义文件的注释里看到文字“ActiveX commands”,莫非用于某种形式的ActiveX交互?不过没关系,调用OnCmdMsg才是重点。注意函数体中的code是传来的参数,值为CBN_XTP_EXECUTE,表达式MAKELONG(code, WM_NOTIFY)的值刚好为5111908,这个值记住,以后有用处。如果OnCmdMsg没有处理这个消息,则调用链回到函数NotifyExecute处,发送标准的Command消息。
    到现在,ToolBar的Command消息处理过程已经清晰了。一般情况下,这种实现与MFC框架实现兼容。但在某些特殊情况下,会出现非常莫名其妙的错误。我在一个程序中使用了CHtmlEditView这个类,并使用了处理标准的html编辑命令的宏,如下例:
DHTMLEDITING_CMD_ENTRY_TYPE(ID_BUTTON_BOLD, IDM_BOLD, AFX_UI_ELEMTYPE_CHECBOX)
    奇异的情况发生了,编辑器竟然“不响应”这个加粗命令了!但是同时,编辑器响应左对齐等命令。更奇怪的是,当处理超链接命令时,竟然弹出两个对话框!仔细分析调试并实验发现,并非没有响应命令,而是响应得多了点,每个命令响应了两次。查看CHtmlEditView::OnCmdMsg()函数:
BOOL CHtmlEditView::OnCmdMsg(UINT nID,  int  nCode,  void *  pExtra, AFX_CMDHANDLERINFO *  pHandlerInfo)
{
    
// if it's not something we're intersted in, let it go to the base
    if (nCode < (int)CN_UPDATE_COMMAND_UI)
        
return CHtmlView::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);

    
// check for command availability
    BOOL bHasExecFunc = FALSE;
    UINT uiElemType 
= AFX_UI_ELEMTYPE_NORMAL;
    UINT dhtmlCmdID 
= GetDHtmlCommandMapping(nID, bHasExecFunc, uiElemType);
    
if (dhtmlCmdID == AFX_INVALID_DHTML_CMD_ID)
    
{
        
// No mapping for this command. Use normal routing
        return CHtmlView::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
    }


    
long nStatus = QueryStatus(dhtmlCmdID);

    
if (nCode == CN_UPDATE_COMMAND_UI)
    
{
        
// just checking status
        CCmdUI *pUI = static_cast<CCmdUI*>(pExtra);
        
if (pUI)
        
{
            
if(!(nStatus & OLECMDF_LATCHED || nStatus & OLECMDF_ENABLED))
            
{
                pUI
->Enable(FALSE);
                
if (uiElemType & AFX_UI_ELEMTYPE_CHECBOX)
                
{
                    
if (nStatus & OLECMDF_LATCHED)
                        pUI
->SetCheck(TRUE);
                    
else
                        pUI
->SetCheck(FALSE);
                }

                
else if (uiElemType & AFX_UI_ELEMTYPE_RADIO)
                
{
                    
if (nStatus & OLECMDF_LATCHED)
                        pUI
->SetRadio(TRUE);
                    
else
                        pUI
->SetRadio(FALSE);
                }


            }

            
else
            
{
                pUI
->Enable(TRUE); // enable
                
// check to see if we need to do any other state
                
// stuff
                if (uiElemType & AFX_UI_ELEMTYPE_CHECBOX)
                
{
                    
if (nStatus & OLECMDF_LATCHED)
                        pUI
->SetCheck(TRUE);
                    
else
                        pUI
->SetCheck(FALSE);
                }

                
else if (uiElemType & AFX_UI_ELEMTYPE_RADIO)
                
{
                    
if (nStatus & OLECMDF_LATCHED)
                        pUI
->SetRadio(TRUE);
                    
else
                        pUI
->SetRadio(FALSE);
                }

            }

            
return TRUE;
        }

        
return FALSE;
    }


    
// querystatus for this DHTML command to make sure it is enabled
    if(!(nStatus & OLECMDF_LATCHED || nStatus & OLECMDF_ENABLED))
    
{

        
// trying to execute a disabled command
        TRACE(traceHtml, 0"Not executing disabled dhtml editing command %d", dhtmlCmdID);
        
return TRUE;
    }


    
if (bHasExecFunc)
    
{
        
return ExecHandler(nID);        
    }


    
return S_OK == ExecCommand(dhtmlCmdID, OLECMDEXECOPT_DODEFAULT, NULL, NULL) ? TRUE : FALSE;
}
    结合前面对Command消息的了解并分析函数(这里只是一句话,但是我找出问题足足花了两天时间,包括对Command消息的跟踪),发现了这个函数的一个Bug:它没有判断nCode的值。显然,仅应该在nCode=CN_COMMAND的时候才执行html编辑命令。由于这个Bug,函数会在ToolBar发现通知消息的时候执行一次编辑动作,在确实发送Command消息的时候再执行一次。解决的方法很简单,重载OnCmdMsg函数,剔除掉通知消息即可。
    顺便说一下,CXTPMDIFrameWnd竟然不带Menu的,用GetMenu取得的菜单为空!
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
☆ 资料说明:☆ 专业级MFC界面控件套装,包含命令工具栏、浮动面板和属性网格,支持最近的Visual Studio 2010和Windows7风格; 压缩包内包括安装程序和注册机; ☆ 软件简介:☆ Codejock Software 创建于 1998 年,专业开发 MS Visual Studio 开发环境下的组件,便于 VC 开发人员轻松开发出各种界面,如类似 VC++6.0 的开发界面、 Outlook 界面等,并支持 XP 风格。由于功能的完善, Extreme Toolkit 被许多大公司采用。 2003 年 Codejock 公司加快开发,开发出一系列界面组件,包括 Extreme Toolkit 4.1 , Extreme Toolkit Pro , Extreme Suite 等组件包,支持开发环境也从 VC++5/ 6 升 级到 VC++.NET 和 VB ,而且支持 Windows XP 、 OfficeXP/2003 、 Outlook2003 、 Visual Studio 2005 等风格主题。目前是 VC/VB 和 .NET 开发环境下最好的界面开发组件之一。 Codejock 软件公司的 Xtreme Toolkit Pro 提供了 Xtreme Toolkit 标准版的所有特性,以及 Xtreme Suite 中的所有高级特性,包括 Xtreme 控制栏(Xtreme Command Bars)—— 把需要创建的具有改进对接算法的所有组件提供给 Windows 的图形用户界面(GUI)工程师,这些组件用来创建具有 Microsoft Office XP 风格的工具栏和菜单、 Xtreme 浮动面板(Xtreme Docking Pane)—— 把所有组件提供给 Windows 的图形用户界面(GUI)工程师,这些组件用来创建具有改进 Visual Studio.NET 风格的浮动面板、 Xtreme 属性网格(Xtreme Property Grid)—— 把完善的 Visual Studio.NET 风格的属性网格提供给 Windows 的图形用户界面(GUI)工程师。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值