自绘菜单的实现

本文详细探讨了如何使用imagelist和menu等组件,结合工具和技术,实现自绘菜单的功能。通过创建和设置null字符串,调整框架布局,实现菜单项的个性化显示。此方法适用于希望在应用程序中增添独特交互体验的开发者。
文章标题:原 作 者: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
最后,对<<一种漂亮的自绘菜单>> 的作者郑恒给予我的帮助表示衷心感谢!
从VC++项目中的菜单资源建立结构相同的自弹出式菜单,原理和步骤如下: (1)CMenu::LoadMenu读入菜单资源; (2)CImageList::Create读入工具栏位图; (3)CMenu::CreatePopupMenu和CMenu::AppendMenu拷贝菜单资源,建立弹出式菜单。其中CMenu::AppendMenu第1个参数设置成MF_OWNERDRAW(自), 第四个参数设置成一个附加结构的指针,包括菜单项文字和位图索引等信息。通过这个结构,在自制时,可以获取对应的菜单项文字和位图位置索引,其中位图保存在第(2)步中的CImageList变量中; (4)在对右鼠标键的响应函数里,使用CMenu::TrackPopupMenu启动显示弹出式菜单; (5)在弹出式菜单的拥有者窗口(CxxxView)里,处理WM_MEASUREITEM消息和WM_DRAWITEM消息,分别调用CMenuEx::MeasureItem和CMenuEx::DrawItem, 分别用来定义菜单项的尺寸,对菜单项进行自; (6)在自函数CMenuEx::DrawItem里,通过每个菜单项的附加结构lpDIS->itemData,获得其文字和位图索引,然后分别使用CDC::DrawText和CImageList::Draw,画出该菜单项的文字和位图,从而实现制。 程序在VC6下编译通过。 没有处理的地方:如果菜单项状态是checked或者radio,程序没做处理。另外,弹出式菜单的激活/禁止时,不会自动触发其拥有者窗口的ON_UPDATE_COMMAND_UI宏。不过,可以处理owner窗口的WM_INITMEMUPOPUP消息(在弹出式菜单的每个子菜单弹出时,都会发出此消息),为每个子菜单项单独生成一个CCmdUI对象,调用其CCmdUI::DoUpdate函数,来手动触发ON_UPDATE_COMMAND_UI宏中对应的消息处理函数,使得菜单项能够根据应用环境进行激活和禁止。详见博客: http://oliver.zheng.blog.163.com/blog/static/14241159520143210595266/
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值