文章标题:原 作 者:querw
原 出 处:www.vczx.com
发 布 者:querw
发布类型:原创
发布日期:2004-08-02
程序运行效果截图:
自绘菜单实现
作者:querw(北方工业大学 2000级计算机4班)
邮箱:querw@sina.com
在VCKBASE上读到<<一种漂亮的自绘菜单>> (http://www.vckbase.com/document/viewdoc/?id=537)
作者:郑恒 (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 Menu; //临时菜单(使用CMenu的LoadMenu()函数读入菜单,并以之为蓝本构建新的菜单)
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很简单:
1.把MenuEx.h和MenuEx.cpp加入到你的工程中
2.声明一个CMenuEx对象.例如m_Menu;
3.调用m_Menu.LoadMenu(IDR_MENU1);读入菜单
4.若需要使用菜单位图则调用m_Menu.LoodToolBar();
效果如下:
MenuEx.jpg,MenuExPopup.jpg
最后,对<<一种漂亮的自绘菜单>> 的作者郑恒给予我的帮助表示衷心感谢!