自绘菜单的实现 作者:querw (北方工业大学 2000级计算机4班)
下载源代码 在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很简单: 把MenuEx.h和MenuEx.cpp加入到你的工程中; 声明一个CMenuEx对象.例如m_Menu; 调用m_Menu.LoadMenu(IDR_MENU1),读入菜单; 若需要使用菜单位图则调用m_Menu.LoodToolBar(); 效果如下: 主菜单 弹出式菜单 最后,对《一种漂亮的自绘菜单》的作者郑恒给予我的帮助表示衷心感谢!
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);}}}
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很简单:
效果如下: 主菜单 弹出式菜单 最后,对《一种漂亮的自绘菜单》的作者郑恒给予我的帮助表示衷心感谢!