一、提出問題
在VCKBASE上讀到《自繪菜單的實現》[作者:querw]。應用的我自己的正在進行的工程後發現效果不錯,可是有存在許多問題。整個類的設計方面存在很多缺陷(先天,後天的),存在的主要問題如下:
菜單編輯器中的模菜單樣

使用BCMENU並且映射了這兩個消息後的執行情況
使用BCMENU沒有映射兩個消息的執行情況

原作者分析的自繪的是因為把主菜單(top-level menu)的子菜單都加載成彈出菜單(popupmenu),是不正確的。真正的原因是因為MFC框架會自動調用CMenu的兩個虛擬函數MeasureItem()和OnDrawItem()。 因此,當CMenuEx派生於CMenu,並且重寫這兩個虛擬函數以後。
1、MFC框架調用的GetMenu()->MeasureItem()就相當於調用了CMenuEx::MeasureItem(),從而實現自繪菜單控件尺寸的測量。2、MFC框架調用GetMenu()->DrawItem()就相當於調用了CMenuEx::DrawItem()來實現自繪菜單控件的自繪操作(不懂??,這正是C++的虛擬的妙用,指向派生類對象的基類指針可以調用派生類的虛擬函數,多麼偉大的發明,誰想出來的???)。與子菜單是否為彈出菜單(popupmenu)沒有什麼關系。以下是摘自WINCORE.CPP的一段程序,也就是WM_MEASUREITEM消息的默認流向的地方,相信大家會從中看出一些端倪。
void CWnd::OnMeasureItem(int /*nIDCtl*/, LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
if (lpMeasureItemStruct->CtlType == ODT_MENU)
{
......
// 如果沒有主菜單
if (pThreadState->m_hTrackingWindow == m_hWnd)
{
......
}
else
{
// 如果有主菜單
pMenu = GetMenu(); // 找到窗體的主菜單,注意,pMenu的是CMenu* 類型
}
// 在當前菜單中尋找ID匹配的菜單項
pMenu = _AfxFindPopupMenuFromID(pMenu, lpMeasureItemStruct->itemID);
if (pMenu != NULL)
// 如果找到,就調用MeasureItem()
// 這就是所謂的基類指針指向派生類對象,可以調用派生類虛擬函數的情況了
pMenu->MeasureItem(lpMeasureItemStruct);
else
TRACE1("Warning: unknown WM_MEASUREITEM for menu item 0x%04X.n",
lpMeasureItemStruct->itemID);
}
else
{
......
}
......
} 摘錄自原CMenuEx.cpp第546-560行
if(uID == 0) //分隔符
{
::AppendMenu(hNewMenu,MF_SEPARATOR,0,NULL);
......
// 注意,就是下面那個-1,把分割條的ID從0改到-1,
// 從而是MFC框架誤以為找到了ID為-1的菜單項,並且測量了它的尺寸
// 而實際上ID為-1的菜單項是不可能被void CWnd::OnMeasureItem()找到的
::ModifyMenu(hNewMenu,i,MF_BYPOSITION | MF_OWNERDRAW,-1,(LPCTSTR)pMenuItem);
} 菜單編輯器中沒有分割條菜單的菜單
原CMenuEx執行的模樣
本文示例代碼或素材下載