分享
 
 
 

完美实现真彩自绘菜单

王朝vc·作者佚名  2006-01-17
窄屏简体版  字體: |||超大  

完美实现真彩自绘菜单

作者:阿福(geforce_zf)

下载源代码

一、提出问题

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

当应用在多文档界面(MDI)中的时候,无法对系统自动添加菜单和文档模板菜单进行自绘(比如无法对文件->最近文件(MRU)菜单项中的文件列表就是系统自动添加)。原因是类内部没有对CMainFrame::OnInitPopupMenu()消息进行处理的函数,

因此不具备修改系统自动添加菜单项的功能。(BCMENU有这功能,而且工作的不错)

作者提到的 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

{

......

}

......

}

当菜单项中含有子菜单(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执行的模样

代码不够简练,程序粒度划分不好,可读性差(不过比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的时候需要修改以下地方。

先将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

CImageListm_imageMenu;// 菜单项正常的图像列表

CImageListm_imageMenuDisable;// 菜单项禁用时的图像列表

CImageListm_imageMenuHot;// 菜单项被选中时的图像列表

...

}

撰写菜单图像索引表,初始化菜单程序,初始化菜单图像列表程序, 和两个重要的消息映射函数CMainFrame::OnMeasureItem()和CMainFrame::OnInitPopupMenu()。

(什么?不会添加!,找ClassWizard帮忙或许有点帮助了:))

// 声明,因为下面的结构要用到 CMenuEx*,又不支持向后引用,又什么办法啊!

class CMenuEx;

//自绘菜单数据项结构,就是要传给系统的那个牛X的LPCTSTR指针所指向的东东

typedef struct tagMENUITEM

{

CStringstrText;// 菜单名称

UINTnID;// 菜单ID号

// 分割条的ID是 0

// 子菜单的ID是 -1

CSizeitemSize;// 菜单项的尺寸,不包括菜单图像的尺寸

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_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);

}

在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的作者,没有他们的辛勤劳动,后人是没办法站在他们肩膀上的!由于程序写的匆忙,难免有不尽人意和错误的地方,欢迎大家任意修改源程序:)

要说这个菜单做的完美,那是吹牛,世界上哪有完美的东西啊 :) 只要自己觉得完美,就够了。 希望大家能从文章中学到点东西,就好。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有