在VCKBASE上讀到《一種漂亮的自繪菜單》 [作者:鄭恆 (lbird)]。應用到我的工程裡後發現:文章中提到的效果能很好的實現。但是有一點不方便:需要映射 WM_DRAWITEM 和 WM_MEASUREITEM 消息才能實現自畫功能。這對於一個基於對話框的工程或者僅僅需要彈出式菜單的工程來說很不方便。網上有一種很有名的自繪菜單 :BCMenu (http://www.rocscience.com/~corkum/BCMenu.html) (在附帶工程中也有 BCMenu),在使用它的時候並不需要映射上述的兩個消息就能實現自繪效果。這個問題讓我覺得很困惑,MSDN也說明:MeasureItem() 和 DrawItem() 兩個虛函數是由框架調用的 。並不用手工映射。可是若不映射上述的兩個消息則顯示不正常。(我查看了好多資料,直到現在還是不明白原因。呵呵:))既然 BCMenu 可以不用映射 WM_DRAWITEM 和 WM_MEASUREITEM 就能實現自畫功能,那麼它肯定經過了特殊處理。果然,BCMenu::LoadMenu()對整個菜單作了處理 。我注意到,如果菜單是彈出式的,那麼不需要映射 WM_DRAWITEM 和 WM_MEASUREITEM 就能實現自畫功能。於是我在CMenuEx::LoadMenu()中重新構建了整個菜單, 把所有的子菜單創建為彈出式的菜單使用API函數::CreatePopupMenu(),代碼如下:
BOOL CMenuEx::LoadMenu(UINT uMenu)
{
//重新讀入菜單,創建為popup菜單,才能自畫(由框架調用MesureItem() 和 DrawItem()
HMENU hMenu = ::CreateMenu();
this->Attach(hMenu);
//臨時菜單(使用CMenu的LoadMenu()函數讀入菜單,並以之為藍本構建新的菜單)
CMenu Menu;
UINT uID;
Menu.LoadMenu(uMenu);
for(int i = 0; i < (int)Menu.GetMenuItemCount(); i++)
{
uID = Menu.GetMenuItemID(i);
if(uID == 0) //分隔符
{
::AppendMenu(hMenu,MF_SEPARATOR,0,NULL);
}
else if((int)uID == -1) //彈出菜單(即子菜單)
{
CMenu *pSubMenu = Menu.GetSubMenu(i);
//創建子菜單
HMENU hSubMenu = ::CreatePopupMenu();
CString strPopup;
Menu.GetMenuString(i,strPopup,MF_BYPOSITION);
::InsertMenu(hMenu,
i,
MF_BYPOSITION | MF_POPUP | MF_STRING,
(UINT)hSubMenu,
strPopup);
//對子菜單遞歸調用ChangeMenuStyle(),把子菜單改為MF_OWNERDRAW風格
ChangeMenuStyle(pSubMenu,hSubMenu);
}
else //正常的菜單項
{
CString strText;
Menu.GetMenuString(uID,strText,MF_BYCOMMAND);
AppendMenu(MF_STRING,uID,strText);
}
}
Menu.DestroyMenu(); //銷毀臨時菜單
return TRUE;
}
void CMenuEx::ChangeMenuStyle(CMenu *pMenu,HMENU hNewMenu)
{
//關聯為CMenuEx(關聯為CMenuEx後才能自動重畫
//原因不明(CMenu封裝的結果?)
CMenuEx *pNewMenu;
pNewMenu = new CMenuEx;
pNewMenu->Attach(hNewMenu);
m_SubMenuArr.Add(pNewMenu);
UINT uID;
int nItemCount = pMenu->GetMenuItemCount();
for(int i = 0; i < nItemCount; i++)
{
uID = pMenu->GetMenuItemID(i);
if(uID == 0) //分隔符
{
::AppendMenu(hNewMenu,MF_SEPARATOR,0,NULL);
//pNewMenu->AppendMenu(MF_SEPARATOR,0,NULL);
CString strText;
MENUITEM *pMenuItem = new MENUITEM;
pMenuItem->uID = 0;
pMenuItem->uIndex = -1;
pMenuItem->uPositionImageLeft = -1;
pMenuItem->pImageList = &m_ImageList;
m_MenuItemArr.Add(pMenuItem);
::ModifyMenu(hNewMenu,
i,
MF_BYPOSITION | MF_OWNERDRAW,
-1,
(LPCTSTR)pMenuItem);
}
else if(uID == -1) //彈出菜單(即子菜單)
{
CMenu *pSubMenu = pMenu->GetSubMenu(i);
HMENU hPopMenu = ::CreatePopupMenu();
CString strPopup;
pMenu->GetMenuString(i,strPopup,MF_BYPOSITION);
::InsertMenu(hNewMenu,
i,
MF_BYPOSITION | MF_POPUP,
(UINT)hPopMenu,
strPopup);
MENUITEM *pMenuItem = new MENUITEM;
pMenuItem->uID = -1;
pMenuItem->strText = strPopup;
pMenuItem->uIndex = -1;
pMenuItem->uPositionImageLeft = -1;
pMenuItem->pImageList = &m_ImageList;
m_MenuItemArr.Add(pMenuItem);
::ModifyMenu(hNewMenu,
i,
MF_BYPOSITION | MF_OWNERDRAW,
-1,
(LPCTSTR)pMenuItem);
ChangeMenuStyle(pSubMenu,hPopMenu);
}
else //正常的菜單項
{
CString strText;
pMenu->GetMenuString(uID,strText,MF_BYCOMMAND);
MENUITEM *pMenuItem = new MENUITEM;
pMenuItem->uID = pMenu->GetMenuItemID(i);
pMenu->GetMenuString(pMenuItem->uID,
pMenuItem->strText,
MF_BYCOMMAND);
pMenuItem->uIndex = -1;
pMenuItem->uPositionImageLeft = -1;
pMenuItem->pImageList = &m_ImageList;
m_MenuItemArr.Add(pMenuItem);
UINT uState = pMenu->GetMenuState(i,MF_BYPOSITION);
::AppendMenu(hNewMenu,
MF_OWNERDRAW | MF_BYCOMMAND | uState,
uID,
(LPCTSTR)pMenuItem);
}
}
}
這樣,利用標注的CMenu::LoadMenu()函數讀入菜單,並根據這個菜單重新構建一個新的菜單,在新菜單中把所有的子菜單創建為彈出式菜單並關聯一個CMenuEx類。根據需要,我提供了一個
CMenuEx::LoadToolBar(UINT uToolBar, UINT uFace)
接口,請注意它的兩個參數:uToolBar 是工具條的資源,uFace 是一個替代位圖的資源ID。因為VC6.0中做一個真彩工具欄並不是一件容易的事,所以我做了一個小動作:用IDE的資源編輯器隨便編輯一個工具條,只要ID和菜單ID相對應即可,然後可以用外部編輯器編輯好真正要使用的位圖(順序和工具條資源的順序一樣),並把該位圖作為uFace參數傳入,菜單就可以有真彩圖標了。
CMenuEx還提供了如下三個接口:
BOOL ModifyMenuEx()
BOOL AppendMenuEx()
BOOL RemoveMenuEx()
功能一目了然,只是增加了對自繪風格的處理,應用的時候只要像調用普通的CMenu::AppendMenu()等函數一樣就自動擁有自繪風格了。我寫這篇文章的目的在於提出菜單派生類調用 MeasureItem() 和 DrawItem()的問題。至於實現漂亮的菜單界面主要工作當然還是在 DrawItem() 函數中做,有特殊需要的可以自行定義 MENUITEM 結構,重新寫 DrawItem() 函數。我沒有提供設置菜單附加位圖的具體代碼,相信這個不是問題。你可以很容易的通過重寫 DrawItem()實現。有必要提醒的是:有關一個菜單項的信息最好能完全從一個MENUITEM結構中取得,使
virtual void MeasureItem(LPMEASUREITEMSTRUCT lpMIS);
virtual void DrawItem(LPDRAWITEMSTRUCT lpDIS);
兩個函數完全不依賴於CMenuEx類的數據成員。
要在工程中使用CMenuEx很簡單:
效果如下:
主菜單
彈出式菜單
最後,對《一種漂亮的自繪菜單》的作者鄭恆給予我的幫助表示衷心感謝!