扫一扫
分享文章到微信
扫一扫
关注官方公众号
至顶头条
作者:佚名 来源:microsoft 2007年11月9日
关键字:
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 函数,但要牢记的最重要的事实是(看到这里时,请看一下时钟的表面):
1、圆(时钟表面)分为 360 个均匀的单元(称为“度”),3 点的位置为 0 度,12 点的位置为 90 度,以此类推。也可以将角度作为负值处理,这样 6 点的位置为 -90 度,9 点的位置为 -180 度,以此类推。
2、直角位置是用 Point 对象(如果需要具有浮点精度,则用 PointF 对象)的 x 和 y 坐标来度量的。在本示例中,传递给时钟的 Draw 方法的 Origin 参数指示的是时钟表面的左上角(想象围绕时钟绘制一个矩形,原点位于此矩形的左上角)。当向右侧移动时,x 坐标将增大。当向下移动时,y 坐标将增大。
3、GetPoint 函数可以将角度、圆点和到圆点的距离转换成标准点。也就是说,考虑到此应用程序的特殊几何特点,此过程将极(圆)坐标转换成直角坐标。
4、GetHour/Minute/SecondDegrees 方法用于转换当前时间的小时数、分钟数和秒数,并返回当前时间的特定时钟指针的位置(以度为单位)。例如,如果时间为 21:00,GetHourDegrees 方法将返回 -180 度。
5、Clock 类使用 GetHourDegrees 方法(及其同辈方法)的返回值来显示时钟指针。代码调用 GDI+ DrawLine 方法来绘制各个指针,并对每个指针使用不同的 Pen 对象。Clock 类的构造函数设置在时钟显示时间时要使用的某些特定笔,包括使用 Pen 对象的 StartCap 和 EndCap 属性。使用这些属性,指针的一端将自动显示为箭头,另一端显示为“球形”。
6、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(在代码中或在设计器中)。设置此属性可以将创建和显示菜单项的工作转移到代码中。您必须对每个菜单项的 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 技术水平。
如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。
现场直击|2021世界人工智能大会
直击5G创新地带,就在2021MWC上海
5G已至 转型当时——服务提供商如何把握转型的绝佳时机
寻找自己的Flag
华为开发者大会2020(Cloud)- 科技行者