您的位置:首页 > 编程语言 > VB

GDI+和Visual Basic.NET

2006-10-26 22:42 218 查看

使用 GDI+ 和 Visual Basic .NET

Ken Getz
MCW Technologies, LLC
2003年6月

摘要:本文提供了一个简单但极具示范性的示例,说明一个时钟演示程序如何利用 .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+ 对象及其成员,包括(但不限于)使用 PenBrush(纯色和渐变色)、PointRectangleEllipseRegion 对象。在一个简单的时钟演示程序中可以集中应用这么多 GDI+ 功能,这是多么让人惊奇的一件事!

使用应用程序

此示例应用程序使您能够使用模拟显示(如下面的图 1 所示)或数字显示来显示当前时间。



图 1:GDI+ 处理此简单时钟应用程序中的所有图形工作
要开始,请将解决方案加载到 Visual Studio .NET 中,然后按 F5 键加载和运行项目。在默认情况下,时钟以模拟外观出现,并显示有窗体边框,但是你可以按照以下方法改变其外观:

重新调整窗体的大小来重新调整时钟的大小。在窗体工作区中,时钟表面始终居中显示为一个圆,圆的半径为窗体客户区的长度和宽度中较小的那个值。

双击窗体(或单击右键,然后从 Context [上下文] 菜单中选择 Show Frame [显示框架])切换环绕时钟表面的窗体显示。

Context(上下文)菜单中,您可以试着使用以下这些选项:

选择 Analog(模拟)或 Digital(数字)以模拟格式或以数字格式显示时钟。数字格式要简洁得多,但少了些趣味性。

选择 Always on Top(总在最前面)以使时钟显示在所有其他窗口的最前面。(选择此选项设置窗体的 TopMost 属性。)

选择 Run at Startup(在启动时运行)选项使应用程序将相应的项添加到 Windows 注册表中,这样在每次登录时都会加载时钟。(实际上事情是这样的:在测试此应用程序时,有一位测试者非常喜欢这个程序,因此他要求设置这个选项,这样时钟就会始终在桌面上运行。我也采用了这个方式。我们真的都很怀念 Windows NT® Clock 应用程序。)

如果要以模拟方式显示时钟,请选择 CountDown(和间隔)以显示指示时间延迟的饼形区域。最初添加此功能是为了演示 FillPie 方法,现在,此功能用于设置计时器,并使您在超时时看到不停闪烁的警告。

如果要以模拟方式显示时钟,请选择 Gradient(渐变色)选项(和某种渐变色)以四种预设渐变填充中的某一种来显示时钟。您可以查看代码以了解渐变色的工作原理,并且这四种渐变色显示的是 GDI+ 的不同功能。

选择 Fill Color(填充颜色),然后选择一种可用的颜色作为时钟的背景颜色。请注意,此特定菜单显示了 GDI+ 功能的另一个用途 - 此所有者描述菜单包含一个显示颜色的矩形。创建所有者描述菜单并不难,而且具有详细的文档说明。您可以从本示例开始制作自己的个性化菜单、显示图形或位图。

选择 Text Color(文本颜色),此选项使用颜色选择器标准对话框为时钟上的文本选择颜色。

应用程序设计问题

除了使用时钟作为这次 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 类中,GetPointGetHourDegreesGetMinuteDegreesGetSecondDegrees 过程进行的是将圆坐标和直角坐标相互转换的复杂工作。(有关代码作用的说明,请参阅 GetHourDegrees 方法。) 您将需要深入挖掘大脑的潜力来领会 SinCos 函数,但要牢记的最重要的事实是(看到这里时,请看一下时钟的表面):

圆(时钟表面)分为 360 个均匀的单元(称为“度”),3 点的位置为 0 度,12 点的位置为 90 度,以此类推。也可以将角度作为负值处理,这样 6 点的位置为 -90 度,9 点的位置为 -180 度,以此类推。

直角位置是用 Point 对象(如果需要具有浮点精度,则用 PointF 对象)的 x 和 y 坐标来度量的。在本示例中,传递给时钟的 Draw 方法的 Origin 参数指示的是时钟表面的左上角(想象围绕时钟绘制一个矩形,原点位于此矩形的左上角)。当向右侧移动时,x 坐标将增大。当向下移动时,y 坐标将增大。

GetPoint 函数可以将角度、圆点和到圆点的距离转换成标准点。也就是说,考虑到此应用程序的特殊几何特点,此过程将极(圆)坐标转换成直角坐标。

GetHour/Minute/SecondDegrees 方法用于转换当前时间的小时数、分钟数和秒数,并返回当前时间的特定时钟指针的位置(以度为单位)。例如,如果时间为 21:00,GetHourDegrees 方法将返回 -180 度。

Clock 类使用 GetHourDegrees 方法(及其同辈方法)的返回值来显示时钟指针。代码调用 GDI+ DrawLine 方法来绘制各个指针,并对每个指针使用不同的 Pen 对象。Clock 类的构造函数设置在时钟显示时间时要使用的某些特定笔,包括使用 Pen 对象的 StartCapEndCap 属性。使用这些属性,指针的一端将自动显示为箭头,另一端显示为“球形”。

Clock 类通过部分缩写的半径调用 GetPoint 方法,以计算在时钟表面上绘制“滴答”标记和小时数字的位置点。(路径渐变也使用此方法计算圆边缘上点的位置。)

要进行一些学习才能完全理解 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(在代码中或在设计器中)。设置此属性可以将创建和显示菜单项的工作转移到代码中。您必须对每个菜单项的 DrawItemMeasureItem 事件作出反应,并且您的事件处理代码必须提供显示每个菜单项所需的必要信息。本示例应用程序在包含每个菜单项名称的文本旁边绘制了一个小不同颜色的矩形。此代码使用 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 技术水平。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: