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)的值刚好为5666666908,这个值记住,以后有用处。如果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取得的菜单为空!

标签: none

添加新评论