最近在使用MFC自绘菜单时遇到一个问题:当菜单项都为Popup Menu 时,发现MFC不会调用自绘MeasureItem,
以至于我的菜单宽度不对,无法显示菜单项文字。
跟踪发现: 在wincore.cpp中的OnMeasureItem函数中调用的_AfxFindPopupMenuFromID有点问题。OnMeasureItem是自绘菜单弹出时向菜单父窗口发送的WM_MEASUREITEM消息的响应函数,而_AfxFindPopupMenuFromID是个递归查找子菜单的函数,如果AfxFindPopupMenuFromID找到子菜单MFC就会调用菜单的MeasureItem。(wincore.cpp在vc安装目录下atlmfc/src/mfc目录中)
_AfxFindPopupMenuFromID的代码如下:
AFX_STATIC CMenu
*
AFXAPI _AfxFindPopupMenuFromID(CMenu
*
pMenu, UINT nID)
{
ENSURE_VALID(pMenu);
// walk through all items, looking for ID match
UINT nItems = pMenu -> GetMenuItemCount();
for ( int iItem = 0 ; iItem < ( int )nItems; iItem ++ )
{
CMenu * pPopup = pMenu -> GetSubMenu(iItem);
if (pPopup != NULL)
{
if (pPopup -> m_hMenu == (HMENU)(UINT_PTR)nID) {
pMenu = CMenu::FromHandlePermanent(pMenu -> m_hMenu);
return pMenu;
}
// recurse to child popup
pPopup = _AfxFindPopupMenuFromID(pPopup, nID);
// check popups on this popup
if (pPopup != NULL)
return pPopup;
}
else if (pMenu -> GetMenuItemID(iItem) == nID)
{
// it is a normal item inside our popup
pMenu = CMenu::FromHandlePermanent(pMenu -> m_hMenu);
return pMenu;
}
}
// not found
return NULL;
}
{
ENSURE_VALID(pMenu);
// walk through all items, looking for ID match
UINT nItems = pMenu -> GetMenuItemCount();
for ( int iItem = 0 ; iItem < ( int )nItems; iItem ++ )
{
CMenu * pPopup = pMenu -> GetSubMenu(iItem);
if (pPopup != NULL)
{
if (pPopup -> m_hMenu == (HMENU)(UINT_PTR)nID) {
pMenu = CMenu::FromHandlePermanent(pMenu -> m_hMenu);
return pMenu;
}
// recurse to child popup
pPopup = _AfxFindPopupMenuFromID(pPopup, nID);
// check popups on this popup
if (pPopup != NULL)
return pPopup;
}
else if (pMenu -> GetMenuItemID(iItem) == nID)
{
// it is a normal item inside our popup
pMenu = CMenu::FromHandlePermanent(pMenu -> m_hMenu);
return pMenu;
}
}
// not found
return NULL;
}
可以看出这条函数只检查了pMenu的子菜单或菜单项而没有检查pMenu自身,所以当自绘菜单的菜单项只有子菜单就会导致查找不到菜单而不调用自绘菜单的MeasureItem函数。
修改方法: 可以在菜单的父窗口响应WM_MEASUREITEM消息,具体修改如下
CMenu
*
MyFindPopupMenuFromID(CMenu
*
pMenu, UINT nID)
{
ENSURE_VALID(pMenu);
// walk through all items, looking for ID match
UINT nItems = pMenu -> GetMenuItemCount();
for ( int iItem = 0 ; iItem < ( int )nItems; iItem ++ )
{
CMenu * pPopup = pMenu -> GetSubMenu(iItem);
if (pPopup != NULL)
{
if (pPopup -> m_hMenu == (HMENU)(UINT_PTR)nID) {
pMenu = CMenu::FromHandlePermanent(pMenu -> m_hMenu);
return pMenu;
}
// recurse to child popup
pPopup = MyFindPopupMenuFromID(pPopup, nID);
// check popups on this popup
if (pPopup != NULL)
return pPopup;
}
else if (pMenu -> GetMenuItemID(iItem) == nID)
{
// it is a normal item inside our popup
pMenu = CMenu::FromHandlePermanent(pMenu -> m_hMenu);
return pMenu;
}
if (pMenu -> GetSafeHmenu() == nID)
{
pMenu = CMenu::FromHandlePermanent(pMenu -> m_hMenu);
return pMenu;
}
}
// not found
return NULL;
}
void CMyWnd::OnMeasureItem( int /* nIDCtl */ , LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
if (lpMeasureItemStruct -> CtlType == ODT_MENU)
{
ASSERT(lpMeasureItemStruct -> CtlID == 0 );
CMenu * pMenu = NULL;
_AFX_THREAD_STATE * pThreadState = _afxThreadState.GetData();
if (pThreadState -> m_hTrackingWindow == m_hWnd)
{
// start from popup
pMenu = CMenu::FromHandle(pThreadState -> m_hTrackingMenu);
}
else
{
// start from menubar
pMenu = GetMenu();
}
ENSURE(pMenu);
pMenu = MyFindPopupMenuFromID(pMenu, lpMeasureItemStruct -> itemID);
if (pMenu != NULL)
{
pMenu -> MeasureItem(lpMeasureItemStruct);
}
else
{
TRACE(traceAppMsg, 0 , " Warning: unknown WM_MEASUREITEM for menu item 0x%04X. " ,
lpMeasureItemStruct -> itemID);
}
}
else
{
CWnd * pChild = GetDescendantWindow(lpMeasureItemStruct -> CtlID, TRUE);
if (pChild != NULL && pChild -> SendChildNotifyLastMsg())
return ; // eaten by child
}
// not handled - do default
Default();
}
{
ENSURE_VALID(pMenu);
// walk through all items, looking for ID match
UINT nItems = pMenu -> GetMenuItemCount();
for ( int iItem = 0 ; iItem < ( int )nItems; iItem ++ )
{
CMenu * pPopup = pMenu -> GetSubMenu(iItem);
if (pPopup != NULL)
{
if (pPopup -> m_hMenu == (HMENU)(UINT_PTR)nID) {
pMenu = CMenu::FromHandlePermanent(pMenu -> m_hMenu);
return pMenu;
}
// recurse to child popup
pPopup = MyFindPopupMenuFromID(pPopup, nID);
// check popups on this popup
if (pPopup != NULL)
return pPopup;
}
else if (pMenu -> GetMenuItemID(iItem) == nID)
{
// it is a normal item inside our popup
pMenu = CMenu::FromHandlePermanent(pMenu -> m_hMenu);
return pMenu;
}
if (pMenu -> GetSafeHmenu() == nID)
{
pMenu = CMenu::FromHandlePermanent(pMenu -> m_hMenu);
return pMenu;
}
}
// not found
return NULL;
}
void CMyWnd::OnMeasureItem( int /* nIDCtl */ , LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
if (lpMeasureItemStruct -> CtlType == ODT_MENU)
{
ASSERT(lpMeasureItemStruct -> CtlID == 0 );
CMenu * pMenu = NULL;
_AFX_THREAD_STATE * pThreadState = _afxThreadState.GetData();
if (pThreadState -> m_hTrackingWindow == m_hWnd)
{
// start from popup
pMenu = CMenu::FromHandle(pThreadState -> m_hTrackingMenu);
}
else
{
// start from menubar
pMenu = GetMenu();
}
ENSURE(pMenu);
pMenu = MyFindPopupMenuFromID(pMenu, lpMeasureItemStruct -> itemID);
if (pMenu != NULL)
{
pMenu -> MeasureItem(lpMeasureItemStruct);
}
else
{
TRACE(traceAppMsg, 0 , " Warning: unknown WM_MEASUREITEM for menu item 0x%04X. " ,
lpMeasureItemStruct -> itemID);
}
}
else
{
CWnd * pChild = GetDescendantWindow(lpMeasureItemStruct -> CtlID, TRUE);
if (pChild != NULL && pChild -> SendChildNotifyLastMsg())
return ; // eaten by child
}
// not handled - do default
Default();
}
可以看出其实只在查找函数中多加了一个判断。至于为什么用HMENU和id比较,是因为如果是子菜单的话,id就是子菜单的句柄。