分类: C/C++
2007-09-25 13:05:06
程序运行效果截图: |
自绘菜单实现 作者:querw(北方工业大学 2000级计算机4班) 邮箱:querw@sina.com 在VCKBASE上读到<<一种漂亮的自绘菜单>> (http://www.vckbase.com/document/viewdoc/?id=537) 作者:郑恒 (lbird).应用到我的工程里后发现:文章中提到的效果能很好的实现,但是有一点不方便:需要映射 WM_DRAWITEM和WM_MEASUREITEM消息才能实现自画功能.这对于一个基于对话框的工程,或者 仅仅需要弹出式菜单的工程来说很不方便.网上有一种很有名的自绘菜单:BCMenu (~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 最后,对<<一种漂亮的自绘菜单>> 的作者郑恒给予我的帮助表示衷心感谢! |