使用 C# 和 IExtenderProvider 实现菜单图像 - 一个更好的陷阱!






4.91/5 (86投票s)
2002 年 11 月 25 日
5分钟阅读

402355

1254
如何使用 C# 中的 IExtender 扩展标准菜单以支持图标
引言
如果您正在阅读本文,您可能已经注意到 Microsoft 在 Visual Studio .NET 中没有提供一个像样的菜单控件。标准的 .NET Framework MainMenu
和 MenuItem
控件是最基础的。
通过其他文章对如何扩展 .NET Framework 菜单功能的长期研究,我感到不满意;要么它们使用了互操作和 Win32 API 调用(我正试图避免这些),要么它们继承了 MenuItem
类,这意味着放弃了 Visual Studio.NET 中的菜单设计器,并且需要手工定制您的菜单实现。我还发现,代码要么过于简单(且不完整),要么过于复杂并集成到一个大型控件套件中,导致难以单独提取菜单功能。
随本提交包含的源项目使用 IExtenderProvider
来创建一个桥梁,连接一个包含菜单图标的 ImageList
控件和标准的 MenuItem
控件。好处是,您可以继续使用 Visual Studio.NET 中的菜单设计器设计您的菜单,并只需扩展它们以支持一个 MenuImage
属性,该属性还可以处理菜单项的拥有者绘制工作。您只需要进行几次拖放操作,设置几个属性值,就可以获得功能齐全的图形菜单,无需编写任何代码。
使用 Extender
使用 MenuImage
Extender 尽可能简单。
- 像往常一样使用 Visual Studio.NET 菜单设计器绘制您的菜单。
- 将一个
ImageList
控件拖到您的窗体上,并在其中填充您的菜单图标。建议使用 16x16 的透明图标。虽然MenuImage
Extender 支持ImageList
支持的任何图像,但我的实现并不使位图透明。 - 将一个
MenuImage
Extender 拖到您的窗体上。打开属性窗口,并从ImageList
属性下拉菜单中选择您的ImageList
控件实例。这会将您的ImageList
连接到 Extender。接下来,选择您的菜单项,并注意一个新的属性 -MenuImage
。输入要与该菜单关联的图像项的数字索引。
就是这样!无需编写代码。当您运行应用程序时,MenuImage
Extender 将从您的 ImageList
中检索指定的图像,并拥有者绘制菜单,如上所示。
拥有者绘制菜单
.NET Framework 菜单默认在 MenuItem
类上不提供图像属性。要添加一个,需要定义您自己的绘制和绘画代码,基本上是从头开始渲染菜单。
为了表明您将绘制自己的菜单项,MenuItem
类提供了一个 OwnerDraw
属性。默认情况下,此属性为 false
。要自定义绘制菜单,请将 OwnerDraw
属性设置为 true
。
注意:MenuImage
Extender 默认会为您完成此操作。如果 OwnerDraw
为 true
,则 MenuItem
类会引发两个事件,可用于绘制菜单。
特别注意:您无需实现控件的子类即可访问这些事件 - 这就是将此功能实现为 Extender 的原因。
MeasureItem
事件用于计算控件所需画布的高度和宽度。此事件在 DrawItem
之前引发。主要活动是设置 ItemHeight
和 ItemWidth
属性以获得正确的大小。
private void OnMeasureItem( Object sender, MeasureItemEventArgs e )
{
// retrieve the image list index from hash table
MenuItem menuItem = (MenuItem) sender ;
// create a menu helper to actually do the menu drawing/painting functions
MenuHelper menuHelper = new MenuHelper( menuItem, e.Graphics, _imageList ) ;
// calculate the menu height and width
e.ItemHeight = menuHelper.CalcHeight() ;
e.ItemWidth = menuHelper.CalcWidth() ;
}
DrawItem
事件用于实际执行绘制。事件参数提供了状态(选中或未选中)、画布的边界,甚至提供了一个 graphics
对象用于绘图。
private void OnDrawItem( Object sender, DrawItemEventArgs e )
{
// derive the MenuItem object, and create the MenuHelper
MenuItem menuItem = (MenuItem) sender ;
MenuHelper menuHelper = new MenuHelper( menuItem, e.Graphics, _imageList ) ;
// draw the menu background
bool menuSelected = (e.State & DrawItemState.Selected) > 0 ;
menuHelper.DrawBackground( e.Bounds, menuSelected ) ;
// if the menu is a seperator, then draw it otherwise, draw a normal menu item
if ( menuHelper.IsSeperator() == true )
menuHelper.DrawSeperator( e.Bounds ) ;
else
{
int imageIndex = this.GetMenuImageIndex( sender ) ;
menuHelper.DrawMenu( e.Bounds, menuSelected, imageIndex ) ;
}
}
这些是拥有者绘制控件的基本原理。为了使主 Extender 类的代码保持简单,我将实际的菜单绘制和绘画功能封装在一个单独的 MenuHelper
类中。有关绘制和绘画实际实现的更多详细信息,请参阅示例项目源代码。
关注点
由于添加图像需要更改菜单文本的默认偏移量,因此您无法将拥有者绘制的菜单项与非拥有者绘制的菜单项混合搭配。要么全部拥有者绘制,要么全不绘制。与此类代码相关的复杂性部分源于需要处理分隔符、非图形菜单、菜单快捷键、子菜单等,选中与未选中的菜单项,以及启用和禁用的状态。
该代码不直接调用任何互操作或 Win32 API 功能。事实上,一旦我开始深入研究,我发现 .NET 提供了大量功能,使我能够保持代码占用空间相对较小 - 它只是分散在许多不同的类中。特别感谢我的致谢名单中提到的各位作者。我使用的一些隐藏技巧很多都来源于他们的文章。
最终注释
我开发这些扩展是为了支持标准的 Windows 风格菜单设计,而不是新的 XP/Office 风格菜单,原因有几个
- 我不如某些人对 XP 风格菜单那样狂热。我认为典型的风格菜单能够完成工作并且视觉上具有吸引力。一个特别的优点是它允许我使用标准的系统颜色和字体,这意味着菜单更有可能在不增加额外编码的情况下正确适应不同的桌面主题。XP 风格菜单需要进行特殊的颜色混合,这会增加复杂性。对于那些决心拥有 XP 风格功能的人来说,支持 XP 风格菜单并不需要太多努力。我可能会考虑在未来的版本中添加一个
MenuStyle
属性,允许用户选择标准或 XP 风格菜单。 - 使用标准的 Windows 风格菜单意味着我无需绘制顶层菜单。同样,如果真的想要 XP 风格菜单,这也不会太难改变,但需要添加一些额外的代码来检测和绘制顶层菜单项。
历史
- 2002 年 11 月 24 日 - 首次提交
致谢
我参考了多位作者的许多不同文章。我想在此致以他们间接贡献的谢意
- VB.NET 菜单中的图标!!!!作者:The New iSoftware Company
- 提供了拥有者绘制菜单基础的一个非常基础的示例。这篇文章在某种程度上让我走上了正确的道路。 - Visual Studio .NET 菜单风格 作者:Carlos H. Perez
- 使用子类化方法对 XP 风格菜单进行了广泛支持。有一些关于拥有者绘制菜单的良好绘制/绘画示例。
许可证
本文未附加明确的许可证,但可能在文章文本或下载文件本身中包含使用条款。如有疑问,请通过下面的讨论区联系作者。
作者可能使用的许可证列表可以在此处找到。