摘要:本文提供了一个简单但极具示范性的示例,说明一个时钟演示程序如何利用 .NET Framework 提供的 GDI+ 功能,同时帮助您提高 Visual Basic .NET 技术水平。(本文包含一些指向英文站点的链接。)
下载 GDIPlus.msi 示例文件。
注意:要运行示例应用程序,需要的环境为安装有 .NET Framework 1.0 Service Pack (SP) 2 的 Microsoft Windows®。本文中出现的所有代码均为 Visual Basic® .NET 版本,是使用 Visual Studio® 2002 编写并测试的。测试工作是在安装有 Windows XP Professional SP1 的系统中完成的。
GDI+ 是由 .NET Framework 中的 System.Drawing 命名空间提供的一组类,它使开发人员可以利用 Windows 内置的图形功能轻松地创建图形应用程序。本文中的简单应用程序演示了 GDI+ 对象及其成员,包括(但不限于)使用 Pen、Brush(纯色和渐变色)、Point、Rectangle、Ellipse 和 Region 对象。在一个简单的时钟演示程序中可以集中应用这么多 GDI+ 功能,这是多么让人惊奇的一件事!
此示例应用程序使您能够使用模拟显示(如下面的图 1 所示)或数字显示来显示当前时间。
图 1:GDI+ 处理此简单时钟应用程序中的所有图形工作
要开始,请将解决方案加载到 Visual Studio .NET 中,然后按 F5 键加载和运行项目。在默认情况下,时钟以模拟外观出现,并显示有窗体边框,但是你可以按照以下方法改变其外观:
从 Context(上下文)菜单中,您可以试着使用以下这些选项:
除了使用时钟作为这次 GDI+ 演示的精妙之处,示例应用程序还将其功能分为示例窗体 frmClock.vb(用于处理应用程序的所有菜单和用户界面)和类 Clock.vb(用于显示时钟本身)。通过将窗体与时钟分离开来,您会发现可以在需要模拟时钟的任何应用程序中重复使用 Clock 类。您需要做的全部工作是:实例化一个 Clock 实例,设置几个属性,然后从 Paint 事件调用 Clock 对象的相应方法。有关详细信息,请参阅下面的工作原理一节。
要在此应用程序以外使用 Clock 类,请将 Clock.vb 文件添加到您自己的项目中。Clock 对象的构造函数需要您提供一个 Form 对象:
Dim MyClock As New Clock(Me)
然后,在处理窗体的 Paint 事件的代码中调用用来显示数字或模拟式时钟的相应代码:
' 模拟时钟: Draw(grfx As Graphics, Radius As Integer, Origin As Point) ' 数字时钟: Draw(grfx As Graphics, ClientRectangle As Rectangle)
对于模拟时钟,必须提供半径和原点(时钟左上角的坐标,而不是圆点)。对于数字时钟,仅需要提供一个描述用来显示时钟的区域的 Rectangle 对象(通常是窗体的 ClientRectangle 属性)。
您很可能希望找到窗体本身的 Timer 控件,用来每隔一秒就刷新显示。这是一个合理的设计,但此处没有使用该设计。Clock 类本身可以维护自己的计时器,并且仅在确定更新显示的时间时使它的父窗体无效。使用此项技术,可以设计多个时钟,并且每个时钟都可以保持自己的时间,而无需担心存在不同的计时器。(认识到时钟并不会使整个父窗体无效是很有用的。整个父窗体无效很不值得,也没有必要。相反,时钟仅会使显示内容的窗体区域无效,即描述时钟的区域无效。) 当 Clock 类使父窗体(或该窗体中的区域)无效时,窗体的 Paint 的事件代码将再次运行,然后时钟将会再次显示。
因为几乎 GDI+ 中的每种方法都需要 Graphics 对象作为显示输出的上下文,所以最简单的方式就是将此图形上下文作为参数通过窗体的 Paint 事件传递给时钟的 Draw 方法。事件过程将接收到作为其参数的 PaintEventArgs 对象,此对象的一个属性中包含窗体的图形上下文。本示例项目通过窗体的 Paint 事件处理程序使用的代码如下:
DemoClock.Draw(e.Graphics, Radius, Origin)
熟悉有关应用程序看起来采用反向工作方式的概念之后(即,窗体通过其 Paint 事件处理程序调用 Clock 类的 Draw 方法,而 Clock 类中的计时器通过使窗体区域无效引发窗体的 Paint 事件),您可能希望深入了解 GDI 时钟工作原理的各个不同方面。
由于不能完全确定计时器的 Tick 事件会按照绝对规律的间隔发生,因此您不能将 Windows Timer 的 Interval 属性设置成精确的一秒,也不能期望时钟会准时更新。相反,此时钟使用的是另一种技术。它将间隔设置成 1/10 秒,每次运行代码时,都会将当前秒与前一次显示的秒进行比较。如果值不同,则 Clock 类知道应当更新显示,然后(仅在这一短暂时刻内)使父窗体无效。您将在 Clock 类的 Timer_Tick 事件处理程序中找到以下代码:
' 在该类中: Private CurrentTime As DateTime = DateTime.Now ' 在 Timer_Tick 事件处理程序中: Static dtmPrevious As DateTime CurrentTime = DateTime.Now If CurrentTime.Second <> dtmPrevious.Second Then dtmPrevious = CurrentTime ParentForm.Invalidate(InvalidRegion) End If
您可能以为再也不会使用中学时的三角学了(如果您曾经费了不少劲学习它),但是在处理圆形对象时,三角学很重要。在本应用程序中,大部分“数学”工作都是要在时钟表面的角度和窗体显示的实际点之间相互转换,这样代码才能在屏幕上绘制出所需的直线和圆。在 Clock 类中,GetPoint、GetHourDegrees、GetMinuteDegrees 和 GetSecondDegrees 过程进行的是将圆坐标和直角坐标相互转换的复杂工作。(有关代码作用的说明,请参阅 GetHourDegrees 方法。) 您将需要深入挖掘大脑的潜力来领会 Sin 和 Cos 函数,但要牢记的最重要的事实是(看到这里时,请看一下时钟的表面):
要进行一些学习才能完全理解 Clock 类所用的三角学。如果只关心 GDI+ 功能,您可以跳过这一部分,只要相信三角学的确发挥了作用。这样,就可以将重点放在直接调用 GDI+ 成员的方法、绘制直线和圆、画笔、笔等上。
GDI+ 可以实现(并且还相当轻松)从代码内部处理每个菜单项的绘制。本示例对 Fill Color(填充颜色)菜单使用所有者描述技术,从窗体类的阵列所描述的颜色范围中选择颜色:
Private LightColors() As Color = _ {Color.LightBlue, Color.LightCoral, _ Color.LightCyan, Color.LightGoldenrodYellow, _ Color.LightGray, Color.LightGreen, _ Color.LightPink, Color.LightSalmon, _ Color.LightSeaGreen, Color.LightSkyBlue, _ Color.LightSlateGray, Color.LightSteelBlue, _ Color.LightYellow, Color.White}
为了创建所有者描述菜单,您必须设置将菜单项的 OwnerDraw 属性设置成 True(在代码中或在设计器中)。设置此属性可以将创建和显示菜单项的工作转移到代码中。您必须对每个菜单项的 DrawItem 和 MeasureItem 事件作出反应,并且您的事件处理代码必须提供显示每个菜单项所需的必要信息。本示例应用程序在包含每个菜单项名称的文本旁边绘制了一个小不同颜色的矩形。此代码使用 GDI+ 来显示矩形和文本。
在时钟上显示渐变不是很难(请参阅 Clock.vb 中的 GradientFill1
、GradientFill2
和 GradientFill3
过程),但是更新渐变填充将占用处理资源,而且无需每秒钟都更新。
要解决这个问题,您需要在内存中保留窗体内容的缓存副本,并仅更新需要每秒钟都更新的一小部分窗体(数字部分通常只显示秒针和秒数)。您可以自己管理这个副本,但是更简单的解决方案是允许窗体使用“双缓冲”。可以使窗体管理自己的更新,这样,显示背景渐变就不会造成时钟每隔一秒就闪烁的情况。
此外,在默认情况下没有处理窗体的 Resize 事件的代码。如果不采取其他步骤,而仅仅是从窗体调用 Clock 类,则重新调整窗体将不会引起时钟重画,直到下一秒 Clock 类使其父窗体无效。
您可以使用 Form 类的 SetStyle 方法来解决这些问题。frmClock_Load 过程包含以下代码(有关详细信息,请参阅 SetStyle 方法的文挡):
Me.SetStyle(ControlStyles.ResizeRedraw, True) Me.SetStyle(ControlStyles.AllPaintingInWmPaint Or _ ControlStyles.UserPaint Or ControlStyles.DoubleBuffer, True)
有一些测试者决定每天都实际使用 GDIClock 应用程序。应他们的要求,本示例应用程序包含将用户设置保存和还原到配置文件的代码。示例包括 AppSettings 类和 RegSettings 类,前者处理从配置文件(位于 Documents and Settings/<UserName>/Application Data/GDIClock 文件夹中)中读写详细信息,后者处理在注册表中保存“启动时运行”信息。如果您对如何读写配置文件中的信息感兴趣,您可能还希望研究这些类。
至此内容就介绍完了。这是一个简单但极具示范性的示例,说明一个时钟演示程序如何利用 .NET Framework 提供的 GDI+ 功能,同时帮助您提高 Visual Basic .NET 技术水平。