具有位图的酷炫所有者绘制菜单 - 版本 3.03






4.92/5 (68投票s)
此类实现了一个所有者绘制菜单类,该类模仿了 XP、Office 和 Visual C++ 中使用的菜单样式。
![]() |
![]() |
3.01 版新增内容
正如你所见,我添加了新的 Office XP 菜单绘图样式。我刚买了一台装有 Windows XP 的机器,注意到我们所有应用程序中的菜单看起来都很糟糕。所以我决定做些什么,在两年没看这个类之后,我添加了新的菜单绘图样式,并添加了大量的修复和用户请求。现在新的绘图样式并不完全像微软的,但对我来说看起来足够好了。对于使用旧类的人来说,只需用新的 BCMenu
.cpp 和 .h 文件替换旧的即可。
该类目前在 XP 上使用新样式,在 Win9x/NT 和 2000 上使用旧样式。但是,如果你喜欢新样式并想在所有 Windows 平台上使用它,只需将 BCMenu.cpp 文件顶部的以下行更改为:
UINT BCMenu::original_drawmode=BCMENU_DRAWMODE_ORIGINAL;
to
UINT BCMenu::original_drawmode=BCMENU_DRAWMODE_XP;
同样,如果你认为我做得不好,你可以在所有平台上将绘图样式更改为原始样式。
其他新增功能包括支持大于 16 色的图像。示例包含 256 色和 1600 万色的图像。还有一个关于如何绘制禁用选项的选项。在 XP 模式下,它们未被选中,但可以更改。我还修复了多个具有相同命令 ID 的菜单项无法获得图像的问题(只有第一个可以!)。有关更新的更多信息,请参阅本文底部。
引言
此类 BCMenu 实现了一个派生自 CMenu
类的所有者绘制菜单。其目的是模仿 Visual C++ 5.0 和 MS Word 中使用的菜单样式。我不能声称所有代码都来自我,其中一些部分来自 Ben Ashley 和 Girish Bharadwaj 提供的代码。他们的代码与我的代码的区别很简单,我的代码可以非常轻松地在你的应用程序中构建那些带有位图的酷炫菜单。我移除了图标加载功能,并用位图替换了它。位图允许你直接从资源编辑器中的工具栏中使用 16X15 的工具栏位图。此外,位图没有缩放,因此它们始终看起来很好。你还可以加载位图资源并为复选标记定义位图。我还添加了默认复选标记绘图功能、分隔符、键盘快捷键文本的正确对齐、键盘快捷键、弹出菜单项的正确对齐、显示外观更改时的正确系统颜色更改,以及 Ben Ashley 的 LoadMenu
函数在复杂子菜单系统中的错误修复。我还做了许多其他修改,多得无法列出或记住。我还使用了 Jean-Edouard Lachand-Robert 的禁用位图抖动功能来创建禁用状态的位图。我必须承认,这比 DrawState
函数做得好得多。如果你发现任何错误、内存泄漏或只是更好的方法,请告诉我。我使用的是 Visual C++ 5.0,并未测试与早期 VC 版本的兼容性。我在 Win 95/NT 上以各种分辨率和颜色调色板大小进行了测试。
安装 (MDI 应用程序)
好了,闲话少说,让我们谈谈实现。为了方便起见,我将首先一步一步列出将菜单实现到 MDI 应用程序中的方法。
- 使用应用程序向导创建你的 MDI 应用程序。
- 将 BCMenu.cpp 和 BCMenu.h 文件插入到你的工作区中。
- 在 MainFrm.h 头文件中,向你的
CMainFrame
类添加以下公共成员函数。HMENU NewMenu(); HMENU NewDefaultMenu();
- 在 MainFrm.h 头文件中,向你的
CMainFrame
类添加以下公共成员变量。BCMenu m_menu,m_default;
- 添加以下行
#include "BCMenu.h"
到 MainFrm.h 头文件的顶部。
- 打开 Mainfrm.cpp 实现文件,并按照下面的列表添加
NewMenu
和NewDefaultMenu
成员函数。重要提示:请务必将下面LoadMenu
调用中的IDR_MYMENUTYPE
菜单 ID 替换为你应用程序中菜单关联的菜单 ID。在资源视图的菜单文件夹中查找。HMENU CMainFrame::NewMenu() { static UINT toolbars[]={ IDR_MAINFRAME }; // Load the menu from the resources // ****replace IDR_MENUTYPE with your menu ID**** m_menu.LoadMenu(IDR_MYMENUTYPE); // One method for adding bitmaps to menu options is // through the LoadToolbars member function.This method // allows you to add all the bitmaps in a toolbar object // to menu options (if they exist). The first function // parameter is an array of toolbar id's. The second is // the number of toolbar id's. There is also a function // called LoadToolbar that just takes an id. m_menu.LoadToolbars(toolbars,1); return(m_menu.Detach()); } HMENU CMainFrame::NewDefaultMenu() { m_default.LoadMenu(IDR_MAINFRAME); m_default.LoadToolbar(IDR_MAINFRAME); return(m_default.Detach()); }
- 编辑你
CWinApp
派生类的InitInstance
成员函数,并在指示的位置添加以下突出显示的代码。// create main MDI Frame window CMainFrame* pMainFrame = new CMainFrame; if (!pMainFrame->LoadFrame(IDR_MAINFRAME)) return FALSE; m_pMainWnd = pMainFrame; // This code replaces the MFC created menus with // the Ownerdrawn versions pDocTemplate->m_hMenuShared=pMainFrame->NewMenu(); pMainFrame->m_hMenuDefault=pMainFrame->NewDefaultMenu(); // This simulates a window being opened if you don't have // a default window displayed at startup pMainFrame->OnUpdateFrameMenu(pMainFrame->m_hMenuDefault); // Parse command line for standard shell commands, // DDE, file open CCommandLineInfo cmdInfo; ParseCommandLine(cmdInfo);
- 向你的
CMainFrame
类添加WM_MEASUREITEM
、WM_MENUCHAR
和WM_INITMENUPOPUP
消息的事件处理程序。通过在类视图中右键单击CMainFrame
类,然后选择“添加 Windows 消息处理程序”来执行此操作。从“为类可用的消息筛选”组合框中选择“窗口”选项。选择消息并添加处理程序。然后编辑处理程序并添加以下代码。//This handler ensure that the popup menu items are // drawn correctly void CMainFrame::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct) { BOOL setflag=FALSE; if(lpMeasureItemStruct->CtlType==ODT_MENU){ if(IsMenu((HMENU)lpMeasureItemStruct->itemID)){ CMenu* cmenu = CMenu::FromHandle((HMENU)lpMeasureItemStruct->itemID); if(m_menu.IsMenu(cmenu)||m_default.IsMenu(cmenu)){ m_menu.MeasureItem(lpMeasureItemStruct); setflag=TRUE; } } } if(!setflag)CMDIFrameWnd::OnMeasureItem(nIDCtl, lpMeasureItemStruct); } //This handler ensures that keyboard shortcuts work LRESULT CMainFrame::OnMenuChar(UINT nChar, UINT nFlags, CMenu* pMenu) { LRESULT lresult; if(m_menu.IsMenu(pMenu)||m_default.IsMenu(pMenu)) lresult=BCMenu::FindKeyboardShortcut(nChar, nFlags, pMenu); else lresult=CMDIFrameWnd::OnMenuChar(nChar, nFlags, pMenu); return(lresult); } //This handler updates the menus from time to time void CMainFrame::OnInitMenuPopup(CMenu* pPopupMenu, UINT nIndex, BOOL bSysMenu) { CMDIFrameWnd::OnInitMenuPopup(pPopupMenu, nIndex, bSysMenu); if(!bSysMenu){ if(m_menu.IsMenu(pPopupMenu)||m_default.IsMenu(pPopupMenu)) BCMenu::UpdateMenu(pPopupMenu); } }
- 如果你正在调试,或者你在混合使用标准菜单和 BCMenu(你可能有不同的文档模板使用标准菜单),那么你应该在项目设置中启用 RTTI。
好了,就这样。编译程序,然后在文件菜单中查看。你应该能看到位图。我尝试了上下文菜单,它们似乎工作正常。
安装 (SDI 应用程序)
- 使用应用程序向导创建你的 SDI 应用程序。
- 将 BCMenu.cpp 和 BCMenu.h 文件插入到你的工作区中。
- 在 MainFrm.h 头文件中,向你的
CMainFrame
类添加以下公共成员函数。HMENU NewMenu();
- 在 MainFrm.h 头文件中,向你的
CMainFrame
类添加以下公共成员变量。BCMenu m_menu;
- 添加以下行
#include "BCMenu.h"
到 MainFrm.h 头文件的顶部。
- 打开 Mainfrm.cpp 实现文件,并按照下面的列表添加
NewMenu
和NewDefaultMenu
成员函数。重要提示:请务必将下面LoadMenu
调用中的IDR_MAINFRAME
菜单 ID 替换为你应用程序中菜单关联的菜单 ID。在资源视图的菜单文件夹中查找。HMENU CMainFrame::NewMenu() { // Load the menu from the resources // ****replace IDR_MAINFRAME with your menu ID**** m_menu.LoadMenu(IDR_MAINFRAME); // One method for adding bitmaps to menu options is // through the LoadToolbar member function.This method // allows you to add all the bitmaps in a toolbar object // to menu options (if they exist). The function parameter // is an the toolbar id. There is also a function called // LoadToolbars that takes an array of id's. m_menu.LoadToolbar(IDR_MAINFRAME); return(m_menu.Detach()); }
- 编辑你
CWinApp
派生类的InitInstance
成员函数,并在指示的位置添加以下突出显示的代码。// Dispatch commands specified on the command line if (!ProcessShellCommand(cmdInfo)) return FALSE; CMenu* pMenu = m_pMainWnd->GetMenu(); if (pMenu)pMenu->DestroyMenu(); HMENU hMenu = ((CMainFrame*) m_pMainWnd)->NewMenu(); pMenu = CMenu::FromHandle( hMenu ); m_pMainWnd->SetMenu(pMenu); ((CMainFrame*)m_pMainWnd)->m_hMenuDefault = hMenu;
- 向你的
CMainFrame
类添加WM_MEASUREITEM
、WM_MENUCHAR
和WM_INITMENUPOPUP
消息的事件处理程序。通过在类视图中右键单击CMainFrame
类,然后选择“添加 Windows 消息处理程序”来执行此操作。从“为类可用的消息筛选”组合框中选择“窗口”选项。选择消息并添加处理程序。然后编辑处理程序并添加上面的 MDI 代码。将对CMDIFrameWnd
的引用替换为CFrameWnd
。 - 如果你正在调试,或者你在混合使用标准菜单和
BCMenu
(你可能有不同的文档模板使用标准菜单),那么你应该在项目设置中启用 RTTI。
改进和错误修复
3.03 版
-
完全改变了位图的存储方式。为了改善 Win9x 平台的资源内存管理,我改为使用单个图像列表存储所有位图。现在每个菜单项不再存储一个 CImageList 对象来存储位图。用户会注意到所有平台上的 GDI 对象利用率都有所下降。
- 改进了 XP 菜单的禁用浮雕外观。
- 修复了大工具栏尺寸的位图。在使用大于 16x15 的位图之前,必须使用 SetIconSize。
- 修复了高彩色位图的读取。现在只要使用标准的 RGB(192,192,192) 作为背景颜色,即可正确禁用和显示它们。
- 复选标记看起来更像 Office XP。
- XP 风格下的分隔符占用的空间更少。
- 现在可以改变加速键右对齐的工作方式。通过在 BCMenu.cpp 文件中将 xp_space_acclerators 和 original_space_accelerators 的值从 TRUE 改为 FALSE,即可获得 MS 使用的更紧凑的外观(讨厌!)。
3.01 版
- Office XP 菜单可以显示为 3D 弹出式外观。基本上,图标会呈现褪色外观,直到选中菜单项。选中后,它们将以 3D 阴影绘制,显示其全部荣耀色彩。如果你不喜欢这种外观(我认识的一些人喜欢,一些人不喜欢),可以通过在 BCMenu.cpp 文件中将 BOOL BCMenu::xp_draw_3D_bitmaps 变量设置为 FALSE 来轻松关闭它。
- 向类中添加了 DECLARE_DYNAMIC/IMPLEMENT_DYNAMIC,以便我可以使用 IsKindOf。这解决了该类意外尝试删除 CMenu 对象的一些问题。它还使得向顶层菜单栏添加菜单选项更加容易。有关如何执行此操作,请参阅 MDI 示例。
- 修复了字体和默认菜单选项的问题。
- 改进了该类测试在 XP Luna 下运行的方式。这修复了 3.0 类在使用花哨的桌面颜色方案时出现的一些问题。
- 更改了菜单响应系统菜单颜色更改的方式。原始菜单使用菜单颜色着色,而 XP 菜单使用窗口颜色着色。MS 就是这样做的。唯一的例外是在 WinXP Luna 上的原始外观。由于我讨厌白色菜单,所以我使用了 3D 颜色。这似乎是 Developer Studio 的做法。基本上,MS 在 WinXP Luna 上对菜单颜色没有政策,所以我自己制定。
- 添加了一个 MFC 对话框示例。我厌倦了人们询问如何做。实际上很简单。你基本上会为派生的 CDialog 类中的 BCMenu 实例执行所有相同的事情,并在 OnInitDialog 中使用 SetMenu 重写菜单。然后处理 Windows 消息 OnMenuChar、OnInitMenuPopup 和 OnMeasureItem,就像在 MDI 应用程序中一样。确保在 OnClose 处理程序中销毁菜单类。唯一发现的问题(与类无关)是 OnUpdate CCmdUI 的行为。它不像在 MDI 应用程序中那样完成。在处理 OnInitMenuPopup 消息时,不会调用 OnUpdates。因此,我通过从 CFrameWnd::OnInitMenuPopup 中抓取一部分并将其放入我的对话框的 OnInitMenuPopup 处理程序中来使其行为相同。如果有人知道更好的方法,请告诉我。
版本 3.0
- 添加了 Office XP 绘图样式。
- 添加了对大于 16 色的菜单图像的支持。
- 可以设置禁用菜单选项是否可选。
- 改进了图像的内存存储。不再为菜单选项存储整个工具栏图像。
- 添加了
SetMenuText
方法。 - 添加了
RemoveMenu
函数,该函数接受弹出菜单标题而不是其位置。 - 添加了
GetSubMenu
函数,该函数返回菜单的指针。 - 修复了多个具有相同命令 ID 的菜单项只有一个获得图像的问题。
InsertMenu
现在接受 -1 作为位置,并正确地将选项附加到菜单的末尾。- 修复了 RichEditViews 的问题
- 如果菜单项在资源编辑器中被赋予了选中状态,它就不会被应用。已修复。
- 重新格式化并改进了类布局。
2.63 版
RemoveMenu
/DeleteMenu
对弹出菜单的断言已被修复。- 更新了示例。
2.62 版
- 动态添加和删除分隔符时存在一些问题。
- 使用
MF_BYCOMMAND
标志的InsertMenu
工作不正常。
2.61 版
ImageListDuplicate
函数始终使用偏移量 0 调用,而不是用户定义的偏移量。- 改进了
ImageListDuplicate
函数。现在它可以处理 16x16 的位图,并且可以处理更多的颜色深度。 - 现在可以通过
pContext
BCMenuData
成员变量将用户信息附加到菜单项。 - 添加了一个
FindMenuItem
函数,允许你通过命令 ID 检索菜单数据。 - 添加了一个
m_bDynIcons
成员变量。如果设置为 TRUE,则资源 ID 被加载为图标而不是位图。
版本 2.6
- 2.5 版不兼容 VC++ 5.0。我使用了 5.0 中不存在的
CImageList::Create( CImageList* pImageList );
重载。上述重载还调用了一个 Windows 95(无 IE 4.0)不支持的 SDK 函数。因此,如果你使用任何通过CBitmap
或CImageList
对象添加位图的菜单函数,该菜单类就会崩溃。现在已全部修复!
2.5 版
- 添加了对动态创建菜单的支持。新函数包括:
BOOL RemoveMenu(UINT uiId,UINT nFlags);
BOOL DeleteMenu(UINT uiId,UINT nFlags);
BOOL AppendMenu(UINT nFlags,UINT nIDNewItem,const char *lpszNewItem,int nIconNormal=-1);
BOOL AppendMenu(UINT nFlags,UINT nIDNewItem,const char *lpszNewItem,CImageList *il,int xoffset);
BOOL AppendMenu(UINT nFlags,UINT nIDNewItem,const char *lpszNewItem,CBitmap *bmp);
BOOL InsertMenu(UINT nPosition,UINT nFlags,UINT nIDNewItem,const char *lpszNewItem,int nIconNormal=-1);
BOOL InsertMenu(UINT nPosition,UINT nFlags,UINT nIDNewItem,const char *lpszNewItem,CImageList *il,int xoffset);
BOOL InsertMenu(UINT nPosition,UINT nFlags,UINT nIDNewItem,const char *lpszNewItem,CBitmap *bmp);
CBitmap
对象或CImageList
对象来定义追加或插入的菜单项的位图。 - 添加了新函数
BCMenu* AppendODPopupMenuA(LPCSTR lpstrText);
- 用于动态添加弹出菜单的简单方法。BOOL ModifyODMenuA(const char *lpstrText,UINT nID,CImageList *il,int xoffset);
BOOL ModifyODMenuA(const char *lpstrText,UINT nID,CBitmap *bmp)
;
- 现在可以使用
Cbitmap
对象或CImageList
对象来定义位图。 BCMenu
现在维护一个所有已创建BCMenu
的列表。使用现在是静态成员函数的BCMenu::Ismenu
,你可以轻松确定一个菜单是否是BCMenu
。- 修复了一些资源泄漏。
2.4 版
- 更新了代码以兼容 VC++ 6.0。修复了
GetMenuItemInfo
问题。 - 如果每个菜单选项中都存在制表符,则
InsertSpaces
函数会出现问题。 - 如果弹出菜单项位于另一个弹出菜单中,
FindMenuOption
将无法找到它。 - 为
pFont
和nID
添加了初始化。
2.3 版(来自 Stefan Kuhr)
- Unicode 和 ANSI 之间的转换现在通过 MFC Tech Note 059 中描述的辅助宏进行。转换的内存分配现在主要在堆栈上进行,这在 Win32 上比在堆上更安全、更快。而且代码看起来更好、更短。
BCMenuData
中的 Unicode 字符串现在将在堆上分配,因此它只需要它需要的内存占用。BCMenuData
具有新的成员函数来访问现在是私有的字符串变量wchar_t *m_szMenuText
。- 纠正了 BCMenu 代码中应使用可移植常量字符串 (_T()) 的一些情况。
- 改进了旧 Shell 上的外观。在 WinNT 3.51 和 Win32s 上现在看起来非常漂亮。你现在可以在 NT3.51 上运行示例。
- 函数 '
IsNewShell
' 现在被 'IsShellType
' 替换。BCMenu 有一个新的静态成员函数BOOL IsNewShell(void)
。 - 在示例中添加了 Unicode 项目设置。
- 通过使用 _T() 宏,将示例中的字符串更改为可移植版本。
- 将示例中所有项目设置的警告级别设置为最高级别 (W4),并纠正了所有内容,使其在 VC 5 中编译时没有任何警告。
版本 2.2
- 修复了
AddBitmapToImageList
中的内存泄漏。这仅影响浮动弹出菜单(右键按钮)。
版本 2.1
- 在
DrawItem
中没有删除禁用位图,导致资源泄漏。现已修复。
版本 2.0
- 1.9 版在不同桌面颜色方案下,新禁用外观存在一些问题。现已修复。
- 图像列表方面存在一个 bug。对于非默认桌面颜色方案,3D 颜色未正确设置以匹配工具栏。
1.9 版
- 菜单图标的禁用外观现在匹配 Joerg Koenig 的扁平工具栏类中工具栏按钮的禁用外观,该类可从 https://codeproject.org.cn/menu/<font%20color=#008000>//www.codeguru.com/ 获取。如果你喜欢旧外观,可以通过调用
SetDisableOldStyle
成员函数来恢复。
1.8 版
- 进行了修改以支持 OCX 控件。
1.7 版
- 添加了 UNICODE 和 Win32s 旧 Shell 支持(WIN 3.1 和 WINNT 3.5)。
- 添加了集成的单选按钮和复选按钮。
- 现在使用
AfxLoadSysColorBitmap
加载位图。 - 现在使用
WM_INITMENUPOPUP
消息处理程序来更新菜单,而不是OnUpdateWindowNew
。
1.6 版
- 多个
LoadMenu
/DestroyMenu
组合导致崩溃。现已修复。
版本 1.5
- 现在可以轻松地为弹出选项添加位图。有关实现说明,请参阅示例应用程序中的
CMainFrame::Newmenu
成员函数。 UpdateMenu
成员函数现在可以更新菜单,以反映菜单选项文本的动态更改。
版本 1.4
- 在菜单上对加速键文本的大小和位置进行了更多工作。
版本 1.3
- 键盘快捷键在 MDI 窗口弹出菜单中与活动视图菜单选项一起使用时无效。
- 键盘快捷键与最近使用(MRU)文件列表一起使用时无效。此列表之后的菜单选项无法通过键盘快捷键选择。
- 出于某种原因,MFC 对第一级弹出菜单的
MeasureItem
结果的处理不同。展开的制表符大小计算(GetTabbedTextExtent
)对于子菜单来说效果很好,但 MFC 似乎为第一级弹出菜单填充了过多的空间。我试图变得聪明并手动减小尺寸,但这搞砸了子菜单。我在MeasureItem
中恢复了原始的制表符大小计算。
版本 1.2
- 用于存储复选标记位图的内存未在菜单销毁时释放。这导致了内存泄漏。现已修复。
- 添加了
AddFromToolBar
成员函数,用于将工具栏中的所有位图加载到菜单选项中(如果存在)。如果你想将工具栏中的所有位图映射到菜单选项,请使用此函数。这取代了你必须调用的许多 ModifyODMenu 函数调用。如果你只想将部分工具栏选项映射到菜单选项,则必须使用ModifyODMenu
方法。
版本 1.1
- 当用户键入一个不存在的键盘快捷键时,程序崩溃。
- 如果第一个项包含位图,在使用键盘快捷键时,它未正确绘制。
- 大字体导致了一些对齐问题,现已修复。
AppendODMenu
和ModifODMenu
成员函数的原型不正确。LoadMenu(LPCTSTR lpszResourceName)
重载的成员函数不起作用。- 在代码中添加了 RTTI 检查(如果项目设置中定义了)。这有助于调试,并且如果你混合使用标准菜单和 BCMenu,则需要它。
- 添加了 Word 97 单选按钮样式,用于带有位图的选中菜单选项。