65.9K
CodeProject 正在变化。 阅读更多。
Home

带图标、标题和阴影的自定义绘制菜单

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.95/5 (198投票s)

2002 年 5 月 29 日

CPOL

12分钟阅读

viewsIcon

3063952

downloadIcon

35087

轻松使用自定义绘制菜单,支持新的 Office 产品风格,包括标题、阴影和图标。

STYLE_XP STYLE_ORIGINAL
STYLE_XP_2003 STYLE_ICY

引言

此类 CNewMenu 实现了一个从 CMenu 类派生的自定义绘制菜单。此类目标是实现与 Microsoft 新产品菜单相同的外观,并支持 图标标题。我从 Brent Corkum 的优秀类 BCMenu 中获得了灵感。我借鉴了一些想法和几乎所有函数,但重新实现了几乎所有代码,并扩展了一些其他有用的功能。改变应用程序的整个菜单外观是一项艰巨的工作——改变边框和添加阴影。此外,在 Windows 2000 下或在使用主题的 Windows XP 下更改所有菜单也很棘手。总的来说,我希望现在您使用此类开发新应用程序会很容易。

此新菜单可在 Windows 95/98/Me 和 Windows NT4.0/2000/XP 下运行。如果您想使用中文或其他特殊字符,可以以 Unicode 支持编译您的项目。但请记住,您必须已安装 Visual Studio 的 Unicode 库,否则会发生链接错误。

此外,如果您希望程序能在所有平台上运行,则必须使用 Visual Studio 6.0 进行编译。我尚未弄清楚为什么在 Windows NT 4.0 下使用 Visual Studio 7.0 编译时,绘制效果不正确。此外,如果您不喜欢扁平边框,可以使用 STYLE_XXX_NOBORDER,这样菜单将具有标准的系统菜单边框。

特别适用于旧版 Windows,如 Win95 或 WinNT 4.0

CNewMenu 也能在这些系统上运行,但您不会拥有所有功能。因此,新的 Office 2003 菜单样式只有标准的菜单栏颜色,因为这些系统不支持更改菜单栏或菜单背景。当您使用新的 Visual Studio 7.0 / 7.1 时,必须 #define WINVER 0x0400,这样就不会出现运行时错误。

菜单中的精美标题

还可以为菜单添加标题。您可以将标题添加到菜单顶部或左侧。背景可以是渐变色或纯色。使用 SetMenuTitle 函数,您可以添加或删除菜单的标题。渐变绘制也适用于 Windows 95。

// Set a title to the system menu 
 m_SystemNewMenu.SetMenuTitle(_T("New Menu Sample"), 
                              MFT_GRADIENT|MFT_LINE|MFT_CENTER);

图标的新效果:增亮、发光和灰度

 

STYLE_XP 菜单样式下,您可以选择启用或禁用对图标的附加绘制样式。禁用的图标会呈现灰色外观,未选中的图标会呈现发光外观。您可以在示例中看到这些绘制样式之间的区别。

如何在项目中 O使用 CNewMenu

  1. 最简单的方法是将项目中的所有 CDialog, CFrameWnd, CMDIFrameWnd, CMDIChildWnd, CMiniFrameWndCMultiDocTemplate 替换为新的类 CNewDialog, CNewFrameWnd, CNewMDIFrameWnd, CNewMDIChildWnd, CNewMiniFrameWndCNewMultiDocTemplate
  2. 在从 CWinApp 派生的应用程序实现中,在 InitInstance 函数中创建框架或对话框窗口之前设置菜单样式。
    // Set drawing style of all menus to XP-Mode or STYLE_ORIGINAL
    CNewMenu::SetMenuDrawMode(CNewMenu::STYLE_XP);
  3. 当您有多个文档模板时,在创建模板后立即添加加载菜单图标的代码。
    CNewMultiDocTemplate* pDocTemplate; 
    pDocTemplate = new CNewMultiDocTemplate(IDR_MDINEWTYPE,
                                            RUNTIME_CLASS(CMDINewMenuDoc),
                                            // custom MDI child frame
                                            RUNTIME_CLASS(CChildFrame),
                                            RUNTIME_CLASS(CMDINewMenuView));
    
    AddDocTemplate(pDocTemplate);
    
    
    // Loading toolbar icons into the menu 
    pDocTemplate->m_NewMenuShared.LoadToolBar(IDR_DRAWBAR);
    
    
  4. 在主框架的 OnCreate 函数末尾插入加载图标的代码。例如:
    m_DefaultNewMenu.LoadToolBar(IDR_MAINFRAME);
    
  5. 最后,别忘了在 stdafx.h 中包含 NewMenu.h 的定义,并将 NewMenu.cpp 添加到项目中。
  6. 现在您已准备好使用此类了。

如何在基于对话框的应用程序中使用 CNewMenu

  1. 在应用程序中,在 InitInstance 函数中创建对话框之前设置菜单样式。
    // Set drawing style of all menus to XP-Mode
    CNewMenu::SetMenuDrawMode(CNewMenu::STYLE_XP);
  2. 在您的项目中,将所有 CDialog 替换为 CNewDialog
  3. 加载位图到菜单的最佳位置是调用基类函数后的 OnInitDialog 函数。
    // CDialogDlg could be your Dialog.
    BOOL CDialogDlg::OnInitDialog() 
    { 
      // Call the baseclass
      CNewDialog::OnInitDialog(); 
    
     
      // Load icons to the menu 
    
    
      m_DefaultNewMenu.LoadToolBar(IDR_MAINFRAME);
    }
    
  4. 别忘了在 stdafx.h 中包含 NewMenu.h 的定义,并将 NewMenu.cpp 添加到项目中。
  5. 特殊功能:我添加了菜单的自动更新。您可以像在框架窗口中一样为更新菜单项添加 OnUpdate 处理程序。如果您想在对话框中拥有标准的菜单处理,您应该重写 OnInitMenuPopup,就像调用更新处理程序一样,在您的
    // For disable automatically
    void CDialogDlg::OnInitMenuPopup(CMenu* pMenu, UINT nIndex,
      BOOL bSysMenu)
    {
      CNewFrame<CDialog>::OnInitMenuPopup(pMenu,nIndex,bSysMenu);
    }

如何在特殊框架窗口中使用 CNewMenu

  1. 通常,您应该重写 measureitemmenucharinitmenupopup。为此,我使用了 CNewFrame 模板类。因此,您可以在项目中将 CYourSpecialBase 替换为 CNewFrame<CYourSpecialBase>
  2. 不要在 IMPLEMENT_DYNAMICIMPLEMENT_SERIALIMPLEMENT_DYNACREATE 中进行更改以进行子类化。
    IMPLEMENT_DYNAMIC(CYourNewSpecialFrame, CYourSpecialBase)
  3. 别忘了在 BEGIN_MESSAGE_MAP 中更改基类。
    BEGIN_MESSAGE_MAP(CYourNewSpecialFrame, CNewFrame<CYourSpecialBase>) 
      //{{AFX_MSG_MAP(CYourNewSpecialFrame) 
      //}}AFX_MSG_MAP
     END_MESSAGE_MAP()
  4. 要将位图加载到菜单中,您可以在 OnCreate 函数中添加代码。
  5. 最后,别忘了在 stdafx.h 中包含 NewMenu.h 的定义,并将 NewMenu.cpp 添加到项目中。

如何在 MDI 应用程序中使用 CNewMenu

  1. 在应用程序的基类中,将 BOOL InitInstance() 函数中的 CMultiDocTemplate 更改为 CNewMultiDocTemplate
    CNewMultiDocTemplate* pDocTemplate; 
    pDocTemplate = new CNewMultiDocTemplate(IDR_MDINEWTYPE,
                                            RUNTIME_CLASS(CMDINewMenuDoc),
                                            // custom MDI child frame
                                            RUNTIME_CLASS(CChildFrame),  
                                            RUNTIME_CLASS(CMDINewMenuView));
    AddDocTemplate(pDocTemplate);
    
  2. 现在插入以下代码,用于从工具栏加载附加的位图到菜单中。
    // Loading toolbar icons into the menu 
    pDocTemplate->m_NewMenuShared.LoadToolBar(IDR_DRAWBAR);
           
    
    // Set drawing style of all menus to XP-Mode
    CNewMenu::SetMenuDrawMode(CNewMenu::STYLE_XP);
    
  3. 在主框架类(CMainFrame)的定义和实现文件中,将基类从 CMDIFrameWnd 更改为 CNewMDIFrame
      // Change the base class in the following makro
    IMPLEMENT_DYNAMIC(CMainFrame, CNewMDIFrameWnd)
    // don't forget to change here the baseclass
    BEGIN_MESSAGE_MAP(CMainFrame, CNewMDIFrameWnd)
    //{{AFX_MSG_MAP(CMainFrame) 
    // NOTE - the ClassWizard will add and remove mapping macros here. 
    // DO NOT EDIT what you see in these blocks of generated code ! 
    ON_WM_CREATE() 
    //}}AFX_MSG_MAP
    END_MESSAGE_MAP()
  4. int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) 末尾添加代码,用于在默认菜单中加载位图。
    m_DefaultNewMenu.LoadToolBar(IDR_MAINFRAME);
    
  5. 最后,将子框架的基类从 CMDIChildWnd 更改为 CNewMDIChildWnd。对所有派生类执行相同操作。
  6. 别忘了在 stdafx.h 中包含 NewMenu.h 的定义,并将 NewMenu.cpp 添加到项目中。

如何在 SDI 应用程序中使用 CNewMenu

  1. 在应用程序中,在 InitInstance 函数中创建对话框之前设置菜单样式。
    // Set drawing style of all menus to XP-Mode
    CNewMenu::SetMenuDrawMode(CNewMenu::STYLE_XP);
  2. 在您的项目中,将所有 CDialog 替换为 CNewDialog
  3. 在主框架类(CMainFrame)的定义和实现文件中,将基类从 CFrameWnd 更改为 CNewFrameWnd
  4. int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) 末尾添加代码,用于在默认菜单中加载位图。
    m_DefaultNewMenu.LoadToolBar(IDR_MAINFRAME);
  5. 别忘了在 stdafx.h 中包含 NewMenu.h 的定义,并将 NewMenu.cpp 添加到项目中。

如何在系统对话框中替换系统菜单

有时更改消息框或 CFileDialog 中的菜单会很有用。不幸的是,MessageBox 没有用于重写的基类。另一个问题是文件对话框。至少有两个对话框,而 CFileDialog 只对显示的 FileDialog 的子对话框进行子类化。对于这些以及其他对话框,我添加了一个特殊的子类化机制。

// Subclass dialog with system menu 
CNewMenuHelper myHelper(NEW_MENU_DIALOG_SUBCLASS|NEW_MENU_DIALOG_SYSTEM_MENU); 
AfxMessageBox(_T("You must restart the application"), MB_OK);

当您不想在下一个显示的对话框中替换菜单边框时,该怎么办?这非常简单!只需在调用之前放置 CNewMenuHelper,如下面的示例所示。

// Subclass dialog with normal border painting 
CNewMenuHelper myHelper(NEW_MENU_DEFAULT_BORDER); 
AfxMessageBox(_T("You must restart the application"), MB_OK);

16 色位图的颜色替换

MFC 库会根据选择的系统颜色替换某些颜色。当您在位图中添加黑色线条时,黑色将被替换为 COLOR_BTNTEXT 系统颜色。遵循颜色映射表。顺便说一下,这与工具栏的颜色映射相同。

  • 颜色 RGB(0x00,0x00,0x00) 将被更改为 COLOR_BTNTEXT(黑色)。

  • 颜色 RGB(0x80,0x80,0x80) 将被更改为 COLOR_BTNSHADOW(深灰色)。

  • 颜色 RGB(0xC0,0xC0,0xC0) 将被更改为 COLOR_BTNFACE(亮灰色)。

  • 颜色 RGB(0xFF,0xFF,0xFF) 将被更改为 COLOR_BTNHIGHLIGHT(白色)。

为菜单添加图标

有几种为菜单添加图标的可能方法。一种简单的方法是使用工具栏资源。但 Developer Studio 只支持 16 色的工具栏位图。您可以这样做:首先定义您的工具栏,然后手动用高彩色位图替换保存的工具栏位图。此外,您可以为每个图标定义不同的位图并将其分配给菜单项。我的方法是定义一个像图像列表中的位图,制作一个包含位图资源 ID 和图标大小的辅助表,后面跟着图标的 ID。表的末尾必须用 NULL ID 标记。

// Define the table
static WORD ToolId[] =
              {  IDR_NEWMENU256, // Resource id of the bitmap-icons 
                 16, 15,         // Dimension of each icon
 

                 ID_FILE_NEW,    // First icon's ID
                 ID_FILE_OPEN,   // Second icon's ID 
                 ID_FILE_SAVE,   // and so on...
 
                 NULL}; // Marker for end of table 

//Load the definition into menu with transparent color
m_DefaultNewMenu.LoadToolBar(ToolId,RGB(192,192,192));

如何为子菜单添加图标

如果您知道菜单文本,为子菜单条目添加或更改标题非常简单。可以使用辅助函数 ModifyODMenu

// set the icon IDB_MORE_C to the submenu "More A"
m_NewMenuShared.ModifyODMenu(0,_T("More A"),IDB_MORE_C);

Windows 2000 下的菜单

奇怪的是,在 Windows 2000 下,有一个句柄为 0x10012 的窗口菜单,它被所有应用程序共享。(我认为在 Windows 98 下,菜单是 0x0090)。我不知道为什么,但这也被解释为某些子类化菜单效果的特殊原因。首先,当您禁用菜单效果时,菜单窗口会交替创建或显示。这意味着第一个显示的菜单通常是特殊菜单——从未创建或销毁——只显示和禁用,第二个是正常创建并在关闭后销毁。第三次,有时,特殊窗口会再次显示,然后下一个菜单正常创建。因此,当您子类化特殊菜单时,您负责在取消子类化之前恢复所有值。更改某些标志或样式时要小心,因为其他应用程序喜欢标准菜单。如果您忘记取消子类化或更改属于该菜单的绘制区域,您甚至可能遇到蓝屏。这真的很糟糕!其次,当您启用菜单效果时,每个显示的菜单都会从头开始创建。因此,是否恢复所有标志或样式并不重要!

让我解释一下我是如何实现子类化所有菜单的目标的。首先,您无法子类化应用程序的系统菜单类 #32768;我也在寻找其他可能性。我决定为所有窗口消息创建一个 Windows 挂钩,并捕获所有创建窗口的消息。在检查了窗口类之后,当找到菜单类时,我就对其进行了子类化。但如何子类化那个从未被创建的特殊菜单呢?我发现发送到该菜单的早期消息之一是 0x01E2,而最近的消息是 WM_SHOWWINDOW。因此,我捕获了这些消息来子类化和取消子类化特殊菜单。只有在这两条消息之间,您才能访问特殊窗口!我不知道为什么,所以请小心。

如何绘制边框

在子类化菜单窗口后,我可以通过处理 WM_NCPAINT 消息来绘制边框。但阴影效果是如何产生的呢?在菜单显示在屏幕上之前,我将菜单区域保存在一个位图中。这就是为什么当背景被更改时它不起作用的原因。然后我将其与阴影绘制结合起来。另一种方法是使用分层窗口,但这在 Windows 95/98 等所有系统上都行不通。

渐变菜单栏

自 Windows 98 或 Windows 2000 起,就可以为菜单和子菜单设置画笔。因此,更改颜色很容易。但我们如何在菜单栏中实现渐变呢?好吧,您可以定义一个屏幕宽度的位图画笔,并将其设置为框架菜单的菜单。还有其他线索吗?哦,是的,在 Windows98/Me 等非 NT 系统上,您必须设置画笔原点,以避免在重绘菜单栏项的部分时位图出现偏移。

Windows XP 下的系统菜单

我曾经认为自定义绘制菜单一直都很容易,但在 Windows XP 上,当启用主题时,这会成为一个棘手的问题——尤其是当显示系统菜单的主框架。与自定义绘制不同,菜单项是由框架绘制的。框架在菜单项的位置绘制了菜单宽度的窗口按钮,而不是绘制它,所以我找到了一个解决方法。我在显示菜单之前增加了菜单项的标识符,并在关闭后恢复它。因此,它在 Windows 2000 和 XP 下都有效。

在 Windows 2000 下调试新菜单的重要信息

在绘制菜单的过程中,您绝对不应该停止调试,因为特殊菜单将不会被恢复或取消子类化。此外,还会出现许多奇怪的效果。这并非禁止,但请谨慎行事,并在调试器停止后出现问题时准备好重启系统。

如何使用 CNewToolBar

CNewToolBar 只能与 CNewMenu 一起使用。在您的项目中,您需要将 CToolBar 替换为 CNewToolBar。此外,如果您希望停靠栏具有与菜单栏相同的颜色,则必须在包含 newmenu.h 之前定义 USE_NEW_DOCK_BAR

历史

2003 年 11 月 30 日 - 版本 1.18

  • CNewToolBar 扩展了 ICY / 2003 样式

2003 年 9 月 11 日 - 版本 1.17

  • 改进了 STYLE_XPSTYLE_XP_2003 的菜单栏画笔处理
  • 修复了 WinNT 4.0 上菜单图标的绘制

2003 年 7 月 12 日 - 版本 1.16

  • 为 Office 2003 样式添加了渐变停靠栏(CNewDockBar
  • 修复了带位图的菜单复选标记
  • 绘制到内存 DC 以最小化闪烁
  • 现在可以更好地处理没有“&”的菜单项宽度

2003 年 6 月 22 日 - 版本 1.15

  • ModifyODMenuInsertODMenu 添加了快捷键处理
  • 将 OS 标识符更改为 WinXP (OS>5.0)
  • 更改了 Office 菜单的颜色方案并更新了菜单栏颜色
  • Office 2003 栏的渐变颜色
  • XP 更改设置边框样式 => 修复了边框绘制,无需重启

2003 年 4 月 23 日 - 版本 1.14

  • 修复了极端宽度下的窗口菜单
  • 使用 AppendMenu 添加的菜单项的快捷键处理现在更好
  • 修正了默认项的 Measureitem
  • 新的 Office 2003 菜单样式(Win95 & WinNT 4.0 上不支持更改菜单栏颜色)
  • 修正了 Win95 & WinNT 4.0 下非活动应用程序的菜单栏绘制

2003 年 1 月 23 日 - 版本 1.13

  • 修复了 SetMenuText
  • 修复了带主题的菜单栏颜色。
  • 为非 CNewMenu 的边框处理添加了一些辅助功能

2002 年 11 月 10 日 - 版本 1.11

  • 修正了 Win 98 下带菜单动画的边框绘制。
  • 修复了子菜单关闭时的边框绘制/重绘。
  • 新的 ICY 菜单样式。
  • 用于更改 CommonDialog 系统菜单的辅助类。

2002 年 7 月 30 日 - 版本 1.10

  • 菜单之间共享图标以节省 GDI 资源。
  • STYLE_XPxx 下绘制图标的新效果。
  • 通过更改系统颜色重新着色菜单图标。
  • 稍微改进了极端大菜单的滚动按钮绘制。
  • 现在支持在菜单中混合不同大小的图标,但它们不会缩放到相同大小。

已知错误

  • 菜单边框带阴影时,背景更改后不会更新。
  • 高对比度模式不支持所有正确的颜色。
© . All rights reserved.