首 页 ┆ 源码下载 ┆ IT学院 ┆ 字体下载 ┆ 模板下载 ┆ 源码发布 ┆ 广告合作 ┆ 网站地图 ┆ 虚拟主机 ┆ 中文域名
► 设为首页
► 加入收藏
► 联系我们
源码下载 >> ASP源码 | PHP源码 | ASP.net源码 | JSP源码 | CGI源码 | VC/C++源码 | VB源码 | Delphi源码 | Flash源码
文章学院 >> 网络编程 | 网页设计 | 图形图象 | 数据库 | 服务器 | 网络媒体 | 网络安全 | 操作系统 | 办公软件 | 软件开发 | 黑客知识
字体下载 >> 精制字体 | 非英字体 | 艺术字体 | 著名字体 | 哥特式 | 简单字体 | 手写体 | 节假日 | 图案字体 | 精度像素 | 中文字体
模板下载 >> 企业门户 | 数码网络 | 休闲娱乐 | 影视音乐 | 旅游名胜 | 文化艺术 | 电子商务 | 个性展示 | 登陆导航 | Flash模板
►►您当前的位置:源码园 → IT学院 → 软件开发 → VC编程 → 文章内容

完美实现真彩自绘菜单

作者:阿福(geforce_zf)  来源:网上收集  发布时间:2007-3-27 10:14:18

完美实现真彩自绘菜单

作者:阿福(geforce_zf)

下载源代码

一、提出问题

  在VCKBASE上读到《自绘菜单的实现》[作者:querw]。应用的我自己的正在进行的工程后发现效果不错,可是有存在许多问题。整个类的设计方面存在很多缺陷(先天,后天的),存在的主要问题如下:

  1. 当应用在多文档界面(MDI)中的时候,无法对系统自动添加菜单和文档模板菜单进行自绘(比如无法对文件->最近文件(MRU)菜单项中的文件列表就是系统自动添加)。原因是类内部没有对CMainFrame::OnInitPopupMenu()消息进行处理的函数, 因此不具备修改系统自动添加菜单项的功能。(BCMENU有这功能,而且工作的不错)
  2. 作者提到的 BCMENU 不用映射 WM_DRAWITEM 和 WM_MEASUREITEM 两个消息就能实现自画功能,实际上是错误的。不映射这两个重要的消息,即使能自绘,也是有问题的,不信看图。
    菜单编辑器中的模菜单样

    使用BCMENU并且映射了这两个消息后的执行情况



    使用BCMENU没有映射两个消息的执行情况



      原作者分析的自绘的是因为把主菜单(top-level menu)的子菜单都加载成弹出菜单(popupmenu),是不正确的。真正的原因是因为MFC框架会自动调用CMenu的两个虚拟函数MeasureItem()和OnDrawItem()。 因此,当CMenuEx派生于CMenu,并且重写这两个虚拟函数以后。

    1、MFC框架调用的GetMenu()->MeasureItem()就相当于调用了CMenuEx::MeasureItem(),从而实现自绘菜单控件尺寸的测量。
    2、MFC框架调用GetMenu()->DrawItem()就相当于调用了CMenuEx::DrawItem()来实现自绘菜单控件的自绘操作(不懂??,这正是
    C++的虚拟的妙用,指向派生类对象的基类指针可以调用派生类的虚拟函数,多么伟大的发明,谁想出来的???)。与子菜单是否为弹出菜单(popupmenu)没有什么关系。以下是摘自WINCORE.CPP的一段程序,也就是WM_MEASUREITEM消息的默认流向的地方,相信大家会从中看出一些端倪。
    void CWnd::OnMeasureItem(int /*nIDCtl*/, LPMEASUREITEMSTRUCT lpMeasureItemStruct){if (lpMeasureItemStruct->CtlType == ODT_MENU){......// 如果没有主菜单if (pThreadState->m_hTrackingWindow == m_hWnd){......}else{// 如果有主菜单pMenu = GetMenu();  // 找到窗体的主菜单,注意,pMenu的是CMenu* 类型}// 在当前菜单中寻找ID匹配的菜单项pMenu = _AfxFindPopupMenuFromID(pMenu, lpMeasureItemStruct->itemID);if (pMenu != NULL)// 如果找到,就调用MeasureItem()// 这就是所谓的基类指针指向派生类对象,可以调用派生类虚拟函数的情况了pMenu->MeasureItem(lpMeasureItemStruct);  elseTRACE1("Warning: unknown WM_MEASUREITEM for menu item 0x%04X.\n",lpMeasureItemStruct->itemID);}else{......}......}        
  3. 当菜单项中含有子菜单(submenu),而不含有分割条的时候,子菜单项的高度不可调。原因为原CMenuEx程序中将分割条的原COMMAND ID(0)改为菜单项的COMMADN ID(-1), 以欺骗MFC框架调用CMenuEx::MeasureItem()来计算子菜单项(submenu)的高度。(很令我失望,这也是促使我自己动手重写该类的原因之一。不信看程序,看图)
    摘录自原CMenuEx.cpp第546-560行
    if(uID == 0) //分隔符{::AppendMenu(hNewMenu,MF_SEPARATOR,0,NULL);......// 注意,就是下面那个-1,把分割条的ID从0改到-1,         // 从而是MFC框架误以为找到了ID为-1的菜单项,并且测量了它的尺寸// 而实际上ID为-1的菜单项是不可能被void CWnd::OnMeasureItem()找到的::ModifyMenu(hNewMenu,i,MF_BYPOSITION | MF_OWNERDRAW,-1,(LPCTSTR)pMenuItem);}        
    菜单编辑器中没有分割条菜单的菜单



    原CMenuEx执行的模样



    菜单编辑器中有分割条菜单的菜单



    原CMenuEx执行的模样



  4. 代码不够简练,程序粒度划分不好,可读性差(不过比BCMENU的代码可读性强多了:))。

二、解决问题

  针对以上遇到的问题,我参考BCMENU和原作者的CMenuEx,对CMenuEx类重新进行了组织,类定义如下:

// 声明,因为下面的结构要用到 CMenuEx*,又不支持向后引用,又什么办法啊!class CMenuEx;//自绘菜单数据项结构,就是要传给系统的那个牛X的LPCTSTR指针所指向的东东class CMenuEx : public CMenu{DECLARE_DYNAMIC( CMenuEx )// Constructorpublic:CMenuEx();virtual ~CMenuEx();virtual BOOL DestroyMenu();// Operationpublic:// 加载菜单操作BOOL LoadMenu(UINT nIDResource);BOOL LoadMenu(LPCTSTR lpszResourceName);BOOL LoadMenu(HMENU hMenu);BOOL LoadMenu(CMenu & Menu);// 菜单项操作,如果当前菜单为主菜单(top-level)就调用相应的CMenu的操作。如果是弹出菜单,         // 就将新加入的菜单项定义为自绘菜单BOOL AppendMenu(UINT nFlags, UINT nIDNewItem = 0,LPCTSTR lpszNewItem = NULL);BOOL InsertMenu(UINT nPosition,UINT nFlags,UINT nIDNewItem=0,LPCTSTR lpszNewItem=NULL );BOOL ModifyMenu(UINT nPosition,UINT nFlags,UINT nIDNewItem=0,LPCTSTR lpszNewItem=NULL );BOOL RemoveMenu(UINT nPosition, UINT nFlags);// 加载菜单图像操作//通过菜单索引表加载图像索引,此操作必须在设置过菜单图像后调用void SetImageIndex(const UINT* nIDResource,UINT nIDCount);void LoadToolBar(const CToolBar* pToolBar);// 通过工具栏加载图像,和图像索引// 取自绘菜单项的数据项UINT  GetMenuItemSize() const;LPMENUITEM GetMenuItem(UINT nPosition);// 取子菜单操作,如果位置nPosition存在子菜单,返回该子菜单指针// 如果不存在子菜单,返回NULLCMenuEx* GetSubMenu(int nPosition);// 在当前菜单和所以子菜单中中寻找相应ID// 如果找到,返回ID所在菜单的指针,没找到返回NULLCMenuEx* FindPopupMenuFromID(UINT nID);// Attributesprotected:// 指示为主菜单(top-level menu or menubar)还是弹出菜单(popupmenu)BOOL m_bPopupMenu;// 分割条的默认高度int m_nSeparator;// 绘制菜单需要的颜色COLORREF m_crBackground;// 菜单背景色COLORREF m_crTextSelected;// 菜单项被选中时的文字颜色COLORREF m_crText;// 菜单项文字颜色COLORREF m_crLeft;// 菜单左侧的背景颜色COLORREF m_crSelectedBroder;// 菜单选中框的线条颜色COLORREF m_crSelectedFill;// 菜单选中框的填充颜色// 菜单项图像的尺寸CSize m_szImage;CImageList* m_pImageList;// 菜单项正常的图像列表 CImageList* m_pDisabledImageList;// 菜单项禁用时的图像列表CImageList* m_pHotImageList;// 菜单项被选中时的图像列表protected:// 包含所有菜单项的数组CArray m_MenuItemArr;public:// 设置颜色操作void SetTextSelectedColor(COLORREF color);void SetBackgroundColor(COLORREF color);void SetTextColor(COLORREF color);void SetLeftColor(COLORREF color);void SetSelectedBroderColor(COLORREF color);void SetSelectedFillColor(COLORREF color);// 设置图像列表操作void SetImageList(CImageList* pImageList);void SetDisabledImageList(CImageList* pImageList);void SetHotImageList(CImageList* pImageList);// 设置当前菜单为主菜单还是弹出菜单void SetPopupMenu(BOOL bPopupMenu);// Implementationpublic:// 绘制菜单项的虚拟函数,由MFC框架自动调用virtual void DrawItem(LPDRAWITEMSTRUCT lpDIS);// 更新弹出菜单菜单项操作// 因为有时候系统会通过菜单句柄插入一些非自绘菜单// 该函数就是更新这些非自绘菜单为自绘菜单void UpdatePopupMenu();protected:// 绘制菜单项的辅助函数,想自己的菜单看上去更COOL,就拿他们开刀void DrawBackground(CDC* pDC,CRect rect);void DrawMenuImage(CDC* pDC,CRect rect,LPDRAWITEMSTRUCT lpDIS);void DrawMenuText(CDC*  pDC,CRect rect,LPDRAWITEMSTRUCT lpDIS);void DrawSelected(CDC*  pDC,CRect rect,LPDRAWITEMSTRUCT lpDIS);// Static Memberpublic:// 在CMainFrame的OnMeasureItem()消息映射函数中调用它,用来测量所有菜单项尺寸static void MeasureItem(LPMEASUREITEMSTRUCT lpMIS);// 在CMainFrame的OnInitPopupMenu()消息映射函数中调用它,// 用来更新系统自动添加的菜单项为自绘菜单static void InitPopupMenu(CMenu* pPopupMenu,UINT nIndex,BOOL bSystem);};#endif // !defined(MENUEX_H)      
三、实现方法

  有了以上的强有力的武器,就可以对我们的程序下手了:)在MDI或SDI中使用CMenuEx的时候需要修改以下地方。
  1. 先将MenuEx.h和MenuEx.cpp添加到工程中,在CMainFrame中添加头文件,CMenuEx对象,用于存储菜单图像的CImageList对象和初始化菜单程序。
    #include "MenuEx.h" // 添加头文件class CMainFrame : public CMDIFrameWnd{...public:HMENU InitMainFrameMenu();// 初始化主菜单HMENU InitImageTypeMenu();// 初始化文档模板菜单protected:  // CMenuEx membersCMenuEx  m_menuMainFrame;// 主窗体没有打开任何文档时菜单CMenuEx  m_menuImageType;// 主窗体打开文档时菜单(文档模板菜单)protected:  // CMenuEx''s image list membersCImageListm_imageMenu;// 菜单项正常的图像列表 CImageListm_imageMenuDisable;// 菜单项禁用时的图像列表CImageListm_imageMenuHot;// 菜单项被选中时的图像列表...}        
  2. 撰写菜单图像索引表,初始化菜单程序,初始化菜单图像列表程序, 和两个重要的消息映射函数CMainFrame::OnMeasureItem()和CMainFrame::OnInitPopupMenu()。 (什么?不会添加!,找ClassWizard帮忙或许有点帮助了:))
    // 声明,因为下面的结构要用到 CMenuEx*,又不支持向后引用,又什么办法啊!class CMenuEx;//自绘菜单数据项结构,就是要传给系统的那个牛X的LPCTSTR指针所指向的东东typedef struct tagMENUITEM{CStringstrText;// 菜单名称UINTnID;// 菜单ID号// 分割条的ID是 0// 子菜单的ID是 -1CSizeitemSize;// 菜单项的尺寸,不包括菜单图像的尺寸CImageList*     pImageList;// 菜单项的正常图像列表CImageList*     pDisabledImageList;// 菜单项的禁用图像列表CImageList*     pHotImageList;// 菜单项的选中图像列表UINTnImageIndex;// 菜单项的图像列表索引,-1表示没有图像BOOLbIsSubMenu;// 表示当前菜单项是否为子菜单项CMenuEx*pSubMenu;// 如果是一般菜单,该值为NULL// 如果bIsSubMenu为TRUE,该值为指向子菜单项的CMenuEx*指针} MENUITEM,*LPMENUITEM;///////////////////////////////////////////// 在ManiFram.cpp 中添加菜单图像索引表static UINT nMenuImageIndex[] ={ID_FILE_OPEN,ID_FILE_SAVE,ID_FILE_PRINT,ID_EDIT_COPY,ID_EDIT_PASTE,ID_EDIT_UNDO,ID_EDIT_REDO,ID_APP_ABOUT,ID_IMAGE_LEVEL,ID_IMAGE_EQUALIZE,ID_IMAGE_SMOOTH,ID_IMAGE_SHARP,ID_IMAGE_SIZE,ID_IMAGE_RA,ID_IMAGE_HISTOGRAM,ID_ZOOMOUT,ID_ZOOMIN,};/////////////////////////////////////////////////////////////////////////////// 在ManiFram.cpp 中添加初始化菜单程序void CMainFrame::InitMenuImage(){// 初始化菜单图像列表CBitmap bm;m_imageMenu.Create(20, 20, TRUE | ILC_COLOR24, 9, 0);// 要问我IDB_SMALLMENUCOLOR是什么,当然是是真彩位图了,看图说话了bm.LoadBitmap(IDB_SMALLMENUCOLOR);    m_imageMenu.Add(&bm,(CBitmap*)NULL);bm.Detach();// 还有IDB_SMALLMENUDISABLEm_imageMenuDisable.Create(20, 20, TRUE | ILC_COLOR24, 9, 0);bm.LoadBitmap(IDB_SMALLMENUDISABLE);    m_imageMenuDisable.Add(&bm,(CBitmap*)NULL);bm.Detach();// 还有IDB_SMALLMENUHOTm_imageMenuHot.Create(20, 20, TRUE | ILC_COLOR24, 9, 0);bm.LoadBitmap(IDB_SMALLMENUHOT);    m_imageMenuHot.Add(&bm,(CBitmap*)NULL);bm.Detach();}/*IDB_SMALLMENUCOLOR    IDB_SMALLMENUHOT    IDB_SMALLMENUDISABLE        
    当然,要通过资源编辑器的Import功能将他们导入到资源文件中,不过因为是真彩,所以不能用
    VC的图片编辑器编辑了。 告诉大家个敲门,我是用windows自带的画笔画的:)
    *//////////////////////////////////////////////////////////////////////////////// 在ManiFram.cpp 中添加初始化菜单图像列表程序int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct){// 在CMainFrame::OnCreate中调用菜单图标初始化程序。。。。。。InitMenuImage();。。。。。。}/////////////////////////////////////////////////////////////////////////////HMENU CMainFrame::InitMainFrameMenu(){//初始化主菜单m_menuMainFrame.LoadMenu(IDR_MAINFRAME);{// 这只加载图像的一种方法,是一种两步方法,先加载图像列表m_menuMainFrame.SetImageList(&m_imageMenu);m_menuMainFrame.SetDisabledImageList(&m_imageMenuDisable);m_menuMainFrame.SetHotImageList(&m_imageMenuHot);// 再通过菜单图像索引表为菜单加载图像索引,m_menuMainFrame.SetImageIndex(nMenuImageIndex,                                    sizeof(nMenuImageIndex)/sizeof(UINT));}// 也可以使用另外一种一步方法加载图像/*// 假设MAINFRAM具有m_wndToolBar成员,并且已经设置了真彩位图// 关于设置工具栏的真彩位图,请参考 http://www.vckbase.com/document/viewdoc/?id=576// 或者看我的另外一篇文章 《完美实现真彩工具栏》(还没写出来那:))         // 不过源程序里面已经有实现方法了// 自己看也可以明白的m_menuMainFrame.LoadToolBar(&m_wndToolBar);*/return m_menuMainFrame.Detach();}/////////////////////////////////////////////////////////////////////////////HMENU CMainFrame::InitImageTypeMenu(){// 初始化文档模板菜单m_menuImageType.LoadMenu(IDR_IMAGETYPE);m_menuImageType.SetImageList(&m_imageMenu);m_menuImageType.SetDisabledImageList(&m_imageMenuDisable);m_menuImageType.SetHotImageList(&m_imageMenuHot);//通过菜单图像索引表为菜单加载图像索引m_menuImageType.SetImageIndex(nMenuImageIndex,sizeof(nMenuImageIndex)/sizeof(UINT));return m_menuImageType.Detach();}/////////////////////////////////////////////////////////////////////////////void CMainFrame::OnInitMenuPopup(CMenu* pPopupMenu, UINT nIndex, BOOL bSysMenu) {// 记住,顺序一定不能反,因为有些MFC自动添加的菜单是在CMDIFrameWnd::OnInitMenuPopup()         // 中添加的.// 如果反了,当然就找不到新加入的菜单了CMDIFrameWnd::OnInitMenuPopup(pPopupMenu, nIndex, bSysMenu);// 静态函数,看好了,别忘了写CMenuEx啊CMenuEx::InitPopupMenu(pPopupMenu, nIndex, bSysMenu);}/////////////////////////////////////////////////////////////////////////////void CMainFrame::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct) {// 都是她惹的祸"CMDIFrameWnd::OnMeasureItem()",不对子菜单项的尺寸进行测量// 害的我们只好映射这个函数了CMDIFrameWnd::OnMeasureItem(nIDCtl, lpMeasureItemStruct);// 静态函数,看好了,别忘了写CMenuEx啊CMenuEx::MeasureItem(lpMeasureItemStruct);}       
  3. 在CXXXApp::InitInstance()中添加代码,XXX代表你自己的程序了
    BOOL CXXXApp::InitInstance(){......CMultiDocTemplate* pDocTemplate;pDocTemplate = new CMultiDocTemplate(IDR_IMAGETYPE,RUNTIME_CLASS(CImageDoc),RUNTIME_CLASS(CChildFrame), // custom MDI child frameRUNTIME_CLASS(CImageView));AddDocTemplate(pDocTemplate);// create main MDI Frame windowCMainFrame* pMainFrame = new CMainFrame;if (!pMainFrame->LoadFrame(IDR_MAINFRAME))return FALSE;m_pMainWnd = pMainFrame;// 这些才是要添加的代码,别弄错了// 初始化文档模板菜单pDocTemplate->m_hMenuShared=pMainFrame->InitImageTypeMenu();// 初始化主窗体菜单pMainFrame->m_hMenuDefault=pMainFrame->InitMainFrameMenu();// 更新,具体干什么没研究,反正不调用就出错了:)pMainFrame->OnUpdateFrameMenu(pMainFrame->m_hMenuDefault);// 要添加的代码到这结束......}        

三、总结

说了这么多,也不知道大家看明白没有,没关系,先贴个图,大家看看效果再说了。

效果图一,使用图像索引表加载的小图标菜单



效果图一,工具条加载的大图标菜单



四、结束语

  感谢querw和BCMenu的作者,没有他们的辛勤劳动,后人是没办法站在他们肩膀上的!由于程序写的匆忙,难免有不尽人意和错误的地方,欢迎大家任意修改源程序:) 要说这个菜单做的完美,那是吹牛,世界上哪有完美的东西啊 :) 只要自己觉得完美,就够了。 希望大家能从文章中学到点东西,就好。
 

[] [返回上一页] [打 印]
  • 上一篇文章:C-Visual Toolbar 1.0
  • 下一篇文章:创建客户区窗口,列表框之间项的拖拽操作

  • 相关文章:
  • [组图]完美实现真彩自绘菜单
  • [图文]自绘菜单的实现
  • [图文]一种漂亮的自绘菜单
  • [组图]树型控件拖动的完美实现
  • [图文]完美实现个人建站梦想 全面了解IIS组建方法
  • [图文]完美实现个人建站梦想 全面了解IIS组建方法(6...
  • [图文]完美实现个人建站梦想 全面了解IIS组建方法(5...
  • [图文]完美实现个人建站梦想 全面了解IIS组建方法(4...
  • [图文]完美实现个人建站梦想 全面了解IIS组建方法(3...
  • [图文]完美实现个人建站梦想 全面了解IIS组建方法(2...
  • 完美实现个人建站梦想 全面了解IIS组建方法(1)
关于本站 - 网站帮助 - 广告合作 - 下载声明 - 友情连接 - 网站地图 - 源码发布
Copyright © 2003-2009 Ymyasp.Com. All Rights Reserved .
备案序号:粤ICP备07029071号