.NET中的自绘机制
原著:Dino Esposito
翻译:Abbey
原文出处:MSDN Magazine Feb 2004(Cutting Edge)
原代码下载:CuttingEdge0402.exe (182KB)
每次 Microsoft 推出象 Office 或者 Visual Studio 这样拳头产品的新版本时,都会推出一些新的特性,其中包括了新的菜单样式(Menu Style)。当新的菜单样式以各自的方式集成到成品中后,第三方的开发商便会掀起一阵模仿浪潮,利用一些定制控件和组件来仿效它。如果你正在使用这些产品,那么你惟有升级到新版本才能享受提供的新的特性。否则,你的应用程序将继续使用大约十年前随 Windows 95 上市时的那种 Windows 经典菜单用户界面。
虽然 Microsoft 在其主要产品中定期更新菜单样式,但其可用来定制应用程序的菜单 API 却自 Windows 3.x 和 文件管理器以来一直没有发生太大的变化。从 Win16 平台开始,应用程序就已经可以有权自已绘制一个个的菜单项来实现自定义菜单。这种技术今天称作自绘(Owner-drawing),并已大量用于其它一些系统组件与控件上,包括列表框、按钮和组合框。
Figure 1 自绘控件
自定义控件的绘图在 Win16 和 Win32 平台下是很无聊的事情。从功能上讲并不是特别复杂,但是代码的编写和维护着实令人讨厌。在本期栏目中,我将深入研究 .NET框架为窗口菜单提供的自绘机制。最终目的是创建一个自定义的组件——只要你将它拖放到某个 Windows 窗体的组件托盘(Component Tray)上,它就允许你按照给定的主题定制菜单的外观。作为例子,我将混合使用 Visual Studio.NET 和 Office 2003 的菜单样式。如 Figure 1 所示,一旦你理解了 .NET 中的自绘控件,那么你也能完成这个示例。
自定义菜单的显示
在.NET框架中,与窗口菜单相关的类包括 MainMenu、ContextMenu 和 MenuItem,这些类都是从公共类——Menu 类派生而来 的。某个窗体的最顶层菜单总是一个 MainMenu 类实例。应用程序的菜单包括了一个弹出式菜单的列表,每个弹出式菜单又由若干个菜单项和子菜单组成。ContextMenu 类对应动态上下文菜单,应用程序中所有活动可见的对象都可以显示这样的一个菜单。 上下文菜单是一个独立的子菜单,并且没有办法与顶层菜单显示的其它子菜单区分开来。最后,MenuItem对象则对应在MainMenu 或者 ContextMenu 对象 内显示的某个独立的菜单项。总之,任何.NET框架下的菜单都是一个 MenuItem 对象集合——无论它在哪里、以及如何被显示。
Win32平台下的自绘功能比较容易通过其 MenuItem 类为数不多的属性和事件来实现。如果你已是一位熟练的 Win32 程序员,你应该赏识.NET下自绘编程模型 的简捷与效率。如果你从未接触过 Win32的自绘控件,你将无法想象它会有多痛苦。再回到.NET的自绘菜单,只要你简单地把在 MenuItems 属性中找到的每一个MenuItem对象的 OwnerDraw 属性设置为真(TRUE)即可。此外,每个 MenuItem 对象都能处理一对事件-DrawItem 和 MeasureItem,它们 收集用于 Win32 平台的底层信息。下面的Visual Basic.NET 代码演示了如何将一个菜单项变成自绘菜单项。
Sub MakeItemOwnerDraw(ByVal item As MenuItem) item.OwnerDraw = True AddHandler item.DrawItem, AddressOf StdDrawItem AddHandler item.MeasureItem, AddressOf StdMeasureItem End Sub在绘制菜单之前,当菜单需要知道某个菜单项的大小时,便触发 MeasureItem 事件,当菜单需要绘制特定项目时,则触发 DrawItem 事件 。Windows 使菜单足够大以适应最大的菜单项。MeasureItem 事件处理函数根据给定的菜单字体大小计算并返回菜单项的文本大小,而 DrawItem 事件处理函数则将 某些菜单文本及其位图、花哨的背景,甚或是你想在菜单上显示的任何东西绘制在给定的Graphics对象上。
重写窗体菜单
重写某个窗体菜单,使之更多彩、更直观的步骤很简单:只要设置每个 MenuItem 对象的OwnerDraw 属性为True,并为关键事件编写适当的事件处理函数。怎样 以最佳方式实现这些呢?你会选择从Menu或MainMenu类派生一个新的类吗?甚至创建一个新的组件与这些缺省的菜单