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






4.75/5 (28投票s)
一套组件,用于实现完整的自定义绘制 MenuItem 支持,包括菜单栏的绘制
引言
在等待 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
组件暴露两个属性:ImageList
和 MenuPainter
。前者允许您选择用于提供菜单图像的 ImageList
,后者允许您附加一个实现了 IMenuPainter
接口的对象来处理菜单的绘制。MenuEx
组件还为窗体上的每个 MenuItem
添加了一个 ImageIndex
属性,以便您可以指定与每个 MenuItem
关联的图像。在运行时,它会拦截每个 MenuItem
的 MeasureItem
和 DrawItem
事件,并将相关信息传递给附加的 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 贡献者为我提供了想法!