自绘菜单

完美实现真彩自绘菜单

作者:阿福(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);  
    		else
    			TRACE1("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 )
		
// Constructor
public:	
	CMenuEx();	
	virtual ~CMenuEx();
	virtual BOOL DestroyMenu();
	
// Operation
public:
	// 加载菜单操作
	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存在子菜单,返回该子菜单指针
	// 如果不存在子菜单,返回NULL
	CMenuEx* GetSubMenu(int nPosition);
	// 在当前菜单和所以子菜单中中寻找相应ID
	// 如果找到,返回ID所在菜单的指针,没找到返回NULL
	CMenuEx* FindPopupMenuFromID(UINT nID);
	
// Attributes
protected:
	// 指示为主菜单(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);
	
	// Implementation
public:
	// 绘制菜单项的虚拟函数,由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 Member
public:
	// 在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 members
    	CMenuEx  m_menuMainFrame;		// 主窗体没有打开任何文档时菜单
    	CMenuEx  m_menuImageType;		// 主窗体打开文档时菜单(文档模板菜单)
    	
    protected:  // CMenuEx''s image list members	
    	CImageList	m_imageMenu;		// 菜单项正常的图像列表 
    	CImageList	m_imageMenuDisable;	// 菜单项禁用时的图像列表
    	CImageList	m_imageMenuHot;		// 菜单项被选中时的图像列表
    	...
    }        
  2. 撰写菜单图像索引表,初始化菜单程序,初始化菜单图像列表程序, 和两个重要的消息映射函数CMainFrame::OnMeasureItem()和CMainFrame::OnInitPopupMenu()。 (什么?不会添加!,找ClassWizard帮忙或许有点帮助了:))
    // 声明,因为下面的结构要用到 CMenuEx*,又不支持向后引用,又什么办法啊!
    class CMenuEx;
    //自绘菜单数据项结构,就是要传给系统的那个牛X的LPCTSTR指针所指向的东东
    typedef struct tagMENUITEM
    {
    	CString		strText;		// 菜单名称
    	UINT		nID;		// 菜单ID号
    	// 分割条的ID是 0
    	// 子菜单的ID是 -1
    				
    	CSize		itemSize;		// 菜单项的尺寸,不包括菜单图像的尺寸
    	
    	CImageList*     pImageList;		// 菜单项的正常图像列表
    	CImageList*     pDisabledImageList;	// 菜单项的禁用图像列表
    	CImageList*     pHotImageList;	// 菜单项的选中图像列表
    	UINT		nImageIndex;	// 菜单项的图像列表索引,-1表示没有图像
    	
    	BOOL		bIsSubMenu;		// 表示当前菜单项是否为子菜单项
    	
    	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_SMALLMENUDISABLE
    	m_imageMenuDisable.Create(20, 20, TRUE | ILC_COLOR24, 9, 0);
    	bm.LoadBitmap(IDB_SMALLMENUDISABLE);    
    	m_imageMenuDisable.Add(&bm,(CBitmap*)NULL);
    	bm.Detach();
    	// 还有IDB_SMALLMENUHOT
    	m_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 frame
    		RUNTIME_CLASS(CImageView));
    	AddDocTemplate(pDocTemplate);
    	
    	// create main MDI Frame window
    	CMainFrame* 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的作者,没有他们的辛勤劳动,后人是没办法站在他们肩膀上的!由于程序写的匆忙,难免有不尽人意和错误的地方,欢迎大家任意修改源程序:) 要说这个菜单做的完美,那是吹牛,世界上哪有完美的东西啊 :) 只要自己觉得完美,就够了。 希望大家能从文章中学到点东西,就好。

 

自绘菜单的实现

作者: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很简单:

  1. 把MenuEx.h和MenuEx.cpp加入到你的工程中;
  2. 声明一个CMenuEx对象.例如m_Menu;
  3. 调用m_Menu.LoadMenu(IDR_MENU1),读入菜单;
  4. 若需要使用菜单位图则调用m_Menu.LoodToolBar();

效果如下:


主菜单


弹出式菜单

最后,对《一种漂亮的自绘菜单》的作者郑恒给予我的帮助表示衷心感谢!

 

一种漂亮的自绘菜单
作者:郑恒 (lbird)

下载本文示例工程

以前还是菜鸟时就觉得QQ的菜单做得很漂亮,想着自已的程序如果有那种菜单多好。
现在积累了一定的知识,就自已设计了一个类似的菜单控件类。并把它发表出来供大家使用和参考,难免有不足的地方请高手不吝赐教!
菜单效果如下:
 

 一、CMenuEx菜单类主要接口函数:

1、void InitMenu(CMenu *pMenu,UINT uToolBar,CToolBar *pToolBar);
说明:这是最主要的一个接口。如果要改变主窗口的菜单则应在主窗口的OnInitMenu(CMenu *pMenu)中调用该函数(如:m_menu.InitMenu(pMenu,IDR_MAINFRAME,&m_wndToolBar)),当然主窗口必须要有工具栏,才会产生菜单项位图。最后还必须重载主窗口的OnMeasureItem()和OnDrawItem()。并在两个函数中分别
调用菜单类的另外两个接口DrawItem()和MeasureItem();

2、void SetHighLightColor(COLORREF crColor);
3、void SetBackColor(COLORREF);
4、void SetTextColor(COLORREF);
以上三个接口应该不用再说明 。

QQ的菜单采用的颜色是
SELECTTEXT_COLOR RGB(0,0,127)
TEXT_COLOR RGB(0,0,0)
BK_COLOR RGB(143,167,207) 效果不错

5、void SetImageLeft(UINT idBmpLeft)
说明:这也是个重要的接口。要实现纵向位图(像“开始”菜单)。可以在适当的地方调用该函数。参数当然是位图的ID值。注意选择位图时要选好看一点的哦!要记得高宽的比例。
6、void InitPopupMenu(CMenu *pMenu,UINT uToolBar,CToolBar *pToolBar)
与InitMenu不同的是:InitMenu并不修改第一级菜单为自绘风格,而
该函数有包括第一级菜单.
但必须注意:该类的任一实例都只能调用这两个函数中的一个,不能一同使用!!!

 二、CMenuEx类的具体使用步骤:

2.1 用CMenuEx类加载主窗口菜单的具体步骤:

1、添加CMenuEx成员变量
CMenuEx m_menu
2、CMainFrame类添加WM_INITMENU消息,并在实现函数中加入代码:
m_menu.InitMenu(pMenu,IDR_MAINFRAME,&m_wndToolBar);
IDR_MAINFRAME是工具栏资源ID,m_wndToolBar是工具栏对象 
3、CMainFrame类添加WM_DRAWITEM和WM_MEASUREITEM消息,并在实现函数分加入:
if(!nIDCtl) m_menu.DrawItem(lpDrawItemStruct);
if(!nIDCtl) m_menu.MeasureItem(lpMeasureItemStruct);
只须这三步就大功造成了!
但是有时你会发现菜单的位图错位了,这不是程序的错。
这是因为“工具栏位图”的个数与“工具栏按钮个数”不符,
你只需把不用的位图删掉就可以,或者添加对应的菜单项,反正使这两个数目一致就对了

4、如果你想要让你的菜单拥有“纵向位图”(像“开始”菜单),就必须在CMainFrame在OnCreate()中加入:
m_menu.SetImageLeft(IDB_BITMAP1);//IDB_BITMAP1是指定位图
2.2 用CMenuEx类加载弹出菜单的具体步骤:

1、在CChildView类中加入成员变量 CMenuEx m_menu和CToolBar *m_pToolBar 
为什么要加入m_pToolBar,这里做一下说明:
因为在CMenuEx的接口函数InitPopupMenu()中需要工具栏对象指针,而工具栏对象又偏偏是CMainFrame类的成员
所以设此指针变量指向工具栏对象

2、给CChildView类的成员m_pToolBar赋值。
当CView派生类由不得CxxxxDocTemplate产生时,赋值的地方比较特殊
请在CxxxWinApp类中的InitInstance()的最后加入代码:
CMainFrame *pFrame=(CMainFrame *)m_pMainWnd;
	CChildView *pView=(CChildView *)pFrame->GetActiveView();
	pView->m_pToolBar=&(pFrame->m_wndToolBar);//注意:原m_wndToolBar是私有变量,要改成public的哦!
当CView派生类是CMainFrame类的成员变量时,就很简单,直接在OnCreate()中设置

3、在CChildView的构造函数中加入设置弹出菜单的代码
m_menu.CreatePopupMenu();
	m_menu.AppendMenu(0,ID_EDIT_UNDO,"撤消");
	m_menu.AppendMenu(MF_SEPARATOR,0);
	m_menu.AppendMenu(0,ID_EDIT_COPY,"复制");
	m_menu.AppendMenu(0,ID_EDIT_CUT,"剪切");
	m_menu.AppendMenu(0,ID_EDIT_PASTE,"粘贴");//当菜单ID与工具栏按钮ID一样时就会显示位图
4、在CChildView的析构函数中加入代码:
m_menu.DestroyMenu(); //释放资源
5、在CChildView类中加入WM_INITMENUPOPUP消息,并在其实现函数中加入代码:
if(!bSysMenu)
		m_menu.InitPopupMenu(pPopupMenu,IDR_MAINFRAME,m_pToolBar);
6、在CChildView类中加入WM_CONTEXTMENU消息,并在其实现函数中加入弹出菜单的代码:
m_menu.TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON,point.x,point.y,this);
好了!介绍完了。
大家可以在适当的地方(比如CMainFrame:OnCreate)中加入:
m_menu.SetHighLightColor(RGB(0,0,127));
m_menu.SetTextColor(RGB(0,0,0));
m_menu.SetBackColor(RGB(143,167,207));

来设置菜单颜色,怎么样:) 欢迎大家多提建议!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值