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

MenuEx - C# 中 Windows Forms 菜单的完整图像和颜色支持

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.75/5 (28投票s)

2005年7月4日

CPOL

4分钟阅读

viewsIcon

201359

downloadIcon

1397

一套组件,用于实现完整的自定义绘制 MenuItem 支持,包括菜单栏的绘制

MenuSuite screen capture

引言

在等待 Visual Studio 2005 推出以及更全面的支持标准菜单的同时,我决定为自己的菜单添加完整的颜色和图像支持。显然,我可以通过编写一个使用 IExtenderProvider 的组件来拦截菜单项的测量和绘制,但我还希望能够绘制菜单栏,并提供一个结构,允许我在不同情况下使用不同的菜单绘制器。

Using the Code

我决定通过一个接口来实现绘制菜单项和菜单栏的支持,这样任何人都可以编写自己的组件来实现该接口。这使得添加自定义菜单绘制器变得非常容易。接口如下所示

/// <summary>
/// Interface used by MenuEx to allow attachment of menu painters.
/// </summary>
public interface IMenuPainter {
    /// <summary>
    /// Paints the menu bar
    /// </summary>        
    /// <param name="g">A Graphics object on which to paint.</param>
    /// <param name="r">The bounding Rectangle for the paint operation.</param>
    /// <remarks>This method will only be called
    ///      when both these items are non- null.</remarks>
    void PaintBar(Graphics g, Rectangle r);

    /// <summary>
    /// Paints a MenuItem
    /// </summary>
    /// <param name="item">The MenuItem to paint.</param>
    /// <param name="e">A DrawItemEventArgs object
    ///          providing data for the paint action.</param>
    /// <param name="image">An Image associated
    ///        with the MenuItem. Note that this may be null.</param>
    /// <param name="imageSize">A MenuImageSize associated with the MenuItem. 
    ///       This indicates the desired size of the image.</param>
    void PaintItem(MenuItem item, DrawItemEventArgs e, Image image, 
                                          MenuImageSize imageSize);

    /// <summary>
    /// Measures a MenuItem object prior to painting it.
    /// </summary>
    /// <param name="item">The MenuItem to measure.</param>
    /// <param name="e">A MeasureItemEventArgs object
    ///         providing data for the measure action.</param>
    /// <param name="imageSize">A MenuImageSize associated with the MenuItem.
    ///         This indicates the desired size of the image.</param>
    void MeasureItem(MenuItem item, MeasureItemEventArgs e, MenuImageSize imageSize);
}

主要的 MenuEx 组件暴露两个属性:ImageListMenuPainter。前者允许您选择用于提供菜单图像的 ImageList,后者允许您附加一个实现了 IMenuPainter 接口的对象来处理菜单的绘制。MenuEx 组件还为窗体上的每个 MenuItem 添加了一个 ImageIndex 属性,以便您可以指定与每个 MenuItem 关联的图像。在运行时,它会拦截每个 MenuItemMeasureItemDrawItem 事件,并将相关信息传递给附加的 IMenuPainter 实现,以便实现菜单项的自定义绘制。

此外,MenuEx 组件会拦截窗体的 FormResize 事件,以便为菜单栏实现绘制支持。我尝试了多种方法来实现这一点,但最终在绘制菜单栏背景时采用了以下方法

/// <summary>
/// Creates a Pattern Brush from the internal barBitmap object,
/// and notifies Windows that this is to be used
/// for painting the Menu Bar
/// </summary>
private void SetBrush() {
    BarBrush = SafeNativeMethods.CreatePatternBrush(barBitmap.GetHbitmap());

    MENUINFO mi = new MENUINFO(form);
    mi.fMask = MIM_BACKGROUND;
    mi.hbrBack = BarBrush;
    SafeNativeMethods.SetMenuInfo(mainMenu.Handle, ref mi);    
    SafeNativeMethods.SendMessage(form.Handle, WM_NCPAINT, 0, 0);
}

下面显示了相关的 Windows API struct 和方法

internal struct MENUINFO {
    internal int cbSize;
    internal int fMask;
    internal int dwStyle;
    internal int cyMax;
    internal IntPtr hbrBack;
    internal int dwContextHelpID;
    internal int dwMenuData;
    internal MENUINFO(Control owner) {
        cbSize = System.Runtime.InteropServices.Marshal.SizeOf(typeof(MENUINFO));
        fMask = 0;
        dwStyle = 0;
        cyMax = 0;
        hbrBack = IntPtr.Zero;
        dwContextHelpID = 0;
        dwMenuData = 0;
    }
}

[DllImport("user32.dll")]
internal static extern int SetMenuInfo(IntPtr hmenu, ref MENUINFO mi);

此方法允许 MenuEx 组件创建与菜单栏区域大小匹配的位图,允许 IMenuPainter 实现对其进行绘制,然后通过 Windows API SetMenuInfo 方法将其注册为 PatternBrush。有效地,这为用户提供了对菜单栏绘制的控制;但是,此方法存在一些限制 - 请参见下面的关注点

MenuEx 工作完成后,我决定编写几个示例 IMenuPainter 实现,每个都作为 Component 编写。我选择让它们继承自同一个祖先:BaseMenuPainter,但您不必使用它,除非您愿意。一系列 virtual 方法允许继承的组件根据需要覆盖特定菜单属性的绘制或测量。这些组件是

BaseMenuPainter IMenuPainter 的基本实现
Office2003MenuPainter 继承自 BaseMenuPainter,并以 Office 2003 的风格进行绘制
VisualStudioMenuPainter 继承自 BaseMenuPainter,并以 Visual Studio 2003 的风格进行绘制
PlainMenuPainter 继承自 BaseMenuPainter,并以简单的风格进行绘制
SkinMenuPainter 继承自 BaseMenuPainter,并使用多种“皮肤”中的一种进行绘制
ImageMenuPainter 继承自 BaseMenuPainter,并使用背景图像绘制菜单项。

使用组件

使用这些组件非常简单

  • 首先,请确保已生成库并将“ControlVault.MenuSuite.dll”程序集中的所有项添加到工具箱。
  • MainMenu 组件添加到您的窗体,并按照常规方式定义菜单项。
  • 添加一个 ImageList 并选择菜单项所需的图像。
  • MenuEx 组件添加到您的窗体,并将 ImageList 属性设置为上一步添加的图像列表。
  • 将一个 MenuPainter 组件添加到您的窗体。
  • MenuEx 组件的 MenuPainter 属性设置为您添加到窗体的 MenuPainter
  • 遍历窗体中的每个 MenuItem,并根据需要设置 ImageIndex 属性。

就是这样!如果您运行项目,您会发现菜单栏和菜单项已使用选定的 MenuPainter 进行绘制。现在您可以尝试编写您自己的 IMenuPainter 实现。

关注点

在此过程中,我遇到了一些问题

  • 绘制包含子项的菜单项时使用的“箭头”似乎是由 Windows 绘制的,无论您是否喜欢。我决定不干预!欢迎就如何处理这个问题提供建议。
  • 尽管 CreatePatternBrush 的文档表明在 Windows XP 中,您可以使用任何大小的位图,但我遇到了一些奇怪的绘制行为。有些图像比其他的效果更好。同样,欢迎就如何改进图像支持提供任何建议。

历史

  • 2005 年 6 月 30 日 - 提交初始文章
  • 2005 年 7 月 11 日 - 更新了代码以改进文本绘制。更改了 GetTextColor 方法以传递 DrawItemState,以便 BaseMenuPainter 的派生类可以在绘制文本之前确定状态。
  • 2005 年 7 月 11 日 - 纠正了 ImageMenuPainter 中的一个错误,该错误导致 MenuItem 对象的背景被 DrawImage 绘制,而不是使用 TextureBrush

致谢

本文涵盖的一些内容不可避免地之前已经讨论过。虽然这里所有的代码都是我自己的,但以下文章提供了启发

还要感谢无数 Usenet 贡献者为我提供了想法!

© . All rights reserved.