模拟时钟控件






4.77/5 (21投票s)
带时区修改的模拟时钟控件。

引言
这是我为 Code Project 社区撰写的第二篇文章。本文将解释如何制作一个模拟时钟控件,该控件模仿 Windows Vista 时钟侧边栏小工具。它模仿了加载和滴答效果,具有 timezone
属性,可用于维护时钟中显示的 timezone
,并且还具有不错的设计器支持功能。
TheClock
是一个模拟时钟控件,它模仿 Windows Vista 侧边栏小工具的行为,尤其是在加载、滴答甚至时区方面。请注意,由于 timezone
功能将具有复杂的方法,我将单独撰写文章,您可以在我完成这篇文章后找到。
背景
随着 Windows Vista 的发布,大多数人都知道它的一个新特性是带有小工具的侧边栏。在所有这些小工具中,有一个控件引起了我的注意,那就是模拟时钟。它具有不错的功能和界面。在观察了它的工作方式之后,我决定基于该小工具制作自己的 VB 模拟时钟。因此,我撰写了这篇文章,与 CodeProject 成员分享,特别是那些已经向该网站贡献了优秀文章的成员,这些文章在我的工作中给了我很大的帮助。
Using the Code
该控件有一个名为 TheClock
的主要组件。该组件必须具备的主要目标是以一种奇特的方式滴答,就像 Windows Vista 小工具一样。它应该具有的第二个功能是 Timezone
功能。本文将解释主要组件本身。我将在另一篇文章中单独讨论 Timezone
功能。
初始化组件
我们需要做的第一件事是创建一个组件。 TheClock
将继承 control
对象,并将实现 ISupportInitialize
,因为我们将在放置控件后添加一些初始值。
#Region "The Clock - Copyright (C) 2007 Michael Rawi"
'The Clock
'Windows Forms Component - NET Framework 2.0
'Copyright (C) 2007 Michael Rawi
'This library is free software; you can redistribute it and/or
'modify it under the terms of the GNU Lesser General Public
'License as published by the Free Software Foundation; either
'version 2.1 of the License, or (at your option) any later version.
'This library is distributed in the hope that it will be useful,
'but WITHOUT ANY WARRANTY; without even the implied warranty of
'MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
'Lesser General Public License for more details.
'Michael Rawi
#End Region
Imports System.DateTime
''' <summary>
''' An analog clock.
''' </summary>
''' <remarks>Created by Michael Rawi</remarks>
<Designer(GetType(TheClockDesigner)), _
Description("A modern analog clock."), _
ToolboxBitmap(GetType(TheClock), "TheClock.png")> _
Public Class TheClock
Inherits Control
Implements System.ComponentModel.ISupportInitialize
...
...
...
End Class
设置变量和属性
在制作控件之前,您应该知道开发控件需要哪些变量。您不必完全知道,因为您以后可以添加其他变量,但您至少应该定义您最需要的变量。对于此控件,我们还需要枚举。
...
...
#Region "Enumeration"
Public Enum Styles
Analog = 0
End Enum
Public Enum SecondTicks
[Default] = 0
Rapid = 1
Original = 2
End Enum
Private Enum FadeStatus
[In] = 0 'Slowly disappeared - 1.0F~0.5F in 0.5 second by 5 Frame
Out = 1 'Slowly reappeared
End Enum
#End Region
#Region "Component Variables"
#Region "Core Variables"
Private WithEvents _tmrTime As Timer
Private WithEvents _tmrTick As Timer
Private WithEvents _tmrLoad As Timer
Private _imgS As Image
Private _imgM As Image
Private _imgH As Image
Private _angleS As Single
Private _angleM As Single
Private _angleH As Single
Private _ComInfo As New Devices.ComputerInfo
#End Region
#Region "Private Properties"
Private _secTick As SecondTicks = SecondTicks.Default
Private _style As Styles
#End Region
#Region "Load Animation"
Private _loadTimeframe As Single
Private _loadHAngle As Single
Private _loadMAngle As Single
Private _loadSAngle As Single
Private _loadMod As Integer
#End Region
#Region "Features"
Private _ShowSecond As Boolean = True
Private _AdvancedMode As Boolean = False
Private _FadeFocus As Boolean = True
Private _FadeStatus As FadeStatus = FadeStatus.Out
Private _FadeAlpha As Single = 0.5F
Private WithEvents _tmrFade As Timer
Private _span As TimeSpan = New TimeSpan(0, 0, 0)
Private _State As String
#End Region
#End Region
...
...
在下一步中,我们将创建一些属性值,以便更容易进行设计时。
...
...
#Region "Properties"
''' <summary>
''' Get or set the style of the clock.
''' </summary>
''' <value>Style</value>
''' <returns>Current Style</returns>
''' <remarks></remarks>
<Description("Get or set the style of the Clock"), Category("Features"), _
DefaultValue(GetType(Styles), "Analog")> _
Public Property Style() As Styles
Get
Return _style
End Get
Set(ByVal value As Styles)
_style = value
End Set
End Property
''' <summary>
''' Get or set the second tick's style.
''' </summary>
''' <value>SecondTick</value>
''' <returns>Current SecondTicks</returns>
''' <remarks></remarks>
<Description("Get or set the second tick's style"), Category("Features"), _
DefaultValue(GetType(SecondTicks), "Default")> _
Public Property SecondTick() As SecondTicks
Get
Return _secTick
End Get
Set(ByVal value As SecondTicks)
_secTick = value
Select Case value
Case SecondTicks.Rapid
_tmrTime.Interval = 100
Case Else
_tmrTime.Interval = 1000
End Select
End Set
End Property
''' <summary>
''' Get or set the second visibility.
''' </summary>
''' <value>Boolean</value>
''' <returns>Second visibility</returns>
''' <remarks></remarks>
<Description("Get or set the second visibility"), Category("Features"), _
DefaultValue(True)> _
Public Property ShowSecond() As Boolean
Get
Return _ShowSecond
End Get
Set(ByVal value As Boolean)
_ShowSecond = value
End Set
End Property
''' <summary>
''' Enable advanced mode. This feature is not ready yet
''' </summary>
''' <value>Boolean</value>
''' <returns>AdvancedMode</returns>
''' <remarks></remarks>
<Browsable(False)> _
Public Property AdvancedMode() As Boolean
Get
Return _AdvancedMode
End Get
Set(ByVal value As Boolean)
_AdvancedMode = value
End Set
End Property
''' <summary>
''' Enable fade focus effect.
''' </summary>
''' <value>Boolean</value>
''' <returns>Current fadefocus effect</returns>
''' <remarks></remarks>
<Description("Enable fade focus effect"), Category("Features"), _
DefaultValue(True)> _
Public Property EnableFadeFocus() As Boolean
Get
Return _FadeFocus
End Get
Set(ByVal value As Boolean)
_FadeFocus = value
Me.Refresh()
End Set
End Property
''' <summary>
''' Set clock time span against current time.
''' </summary>
''' <value>Timespan</value>
''' <returns>Current timespan</returns>
''' <remarks></remarks>
<Description("Set clock time span against current time"), Category("Features"), _
DefaultValue(GetType(TimeSpan), "00:00:00")> _
Public Property ClockSpan() As TimeSpan
Get
Return _span
End Get
Set(ByVal value As TimeSpan)
_span = value
End Set
End Property
#End Region
...
...
需要做的一件重要的事情是锁定控件的大小,因为我们将从背景图像中获得固定大小。这可以通过覆盖 OnSizeChanged
事件来完成。
使东西工作
在初始化步骤之后,现在我们将继续主要功能。 Draw Surface Area
区域包含绘制时针、分针和秒针所需的方法。为了创建所有效果,我们将为此控件设置 3 个计时器。第一个计时器将充当时间指示器。它每秒滴答一次,并告诉控件更新秒针的位置。第二个计时器将执行“弹跳”效果。现在,如果您正确观察 Windows 小工具,您会注意到,在秒针滴答之后,它会稍微弹回,就像真正的时钟一样。为了创建该效果,我们需要一个计时器在秒针滴答之后使其弹起。最后一个计时器将用于初始化过程。在控件加载后,它需要找到当前时间的位置,并以一种优雅的方式将指针放置在正确的位置。这就是我们需要实现 ISupportInitialize
来完成工作的原因。
...
Public Sub EndInit() Implements System.ComponentModel.ISupportInitialize.EndInit
'Activate the clock
If Not Me.DesignMode Then
If ((Now.Hour + _span.Hours) Mod 12) * _
30 > (Now.Minute + _span.Minutes) * 6 Then
_loadMod = 1
Else
_loadMod = 3
End If
_loadTimeframe = (Now.Minute + _span.Minutes) * 6 + (Now.Second \ 10)
_loadHAngle = (((Now.Hour + _span.Hours) Mod 12) * _
30 + ((Now.Minute + _span.Minutes) * 6 \ 12)) / (_loadTimeframe / 6)
_loadMAngle = 6
_tmrLoad = New Timer
_tmrLoad.Interval = 50
_angleH = 0
_angleM = 0
_angleS = 0
Me.Refresh()
_tmrLoad.Start()
End If
End Sub
...
添加设计器支持

一个好的组件应该具有一些使开发时间更容易的功能。其中一个功能是设计器支持。这将使我们的组件更易于使用。
<System.Security.Permissions.PermissionSetAttribute_
(System.Security.Permissions.SecurityAction.Demand, Name:="FullTrust")> _
Public Class TheClockDesigner
Inherits System.Windows.Forms.Design.ControlDesigner
Private lists As DesignerActionListCollection
Public Overrides ReadOnly Property ActionLists() _
As DesignerActionListCollection
Get
If lists Is Nothing Then
lists = New DesignerActionListCollection()
lists.Add( _
New TheClockActionList(Me.Component))
End If
Return lists
End Get
End Property
End Class
时区支持

对于最后一个功能,我们将为组件添加 Timezone
支持。通过添加此功能,用户可以选择她/他需要的控件时间,而不是仅仅遵循 Windows 标准控件。此功能还可以与设计器支持集成,使其更易于使用。此外,它还将具有一些不错的“使用我自己的”功能,可以根据用户的输入调整时钟时间差。
关注点
在创建此控件时,仍然有一个问题困扰着我。由于某种奇怪的原因,我必须调整由 XP 或 Vista 操作系统确定的秒针位置。我目前在这些操作系统上测试了我的项目,发现如果我使用 XP 操作系统,我需要将秒针向左移动 1 像素。此代码可以在 DrawSecondHand
方法中找到。直到现在,知道 XP 坐标与 Vista 坐标略有不同仍然让我感到困惑。
Private Sub DrawSecondHand(ByVal g As Graphics, ByVal Angle As Single)
Dim container As Rectangle
If Angle <> 180 Then
container = New Rectangle(-6, -64, 13, 129)
Else
If _ComInfo.OSVersion.StartsWith("5.1") Then
container = New Rectangle(-7, -65, 13, 129) 'This works on XP
Else
container = New Rectangle(-6, -64, 13, 129) 'This works on Vista
End If
End If
g.TranslateTransform(64.0F, 65.0F)
g.RotateTransform(Angle)
If _FadeFocus Then
Using ia As New Imaging.ImageAttributes
Dim cm As New Imaging.ColorMatrix
cm.Matrix33 = _FadeAlpha
ia.SetColorMatrix(cm)
g.DrawImage(_imgS, container, 0, 0, 13, 129, _
GraphicsUnit.Pixel, ia)
End Using
Else
g.DrawImage(_imgS, container)
End If
'g.DrawImage(_imgS, container)
g.ResetTransform()
End Sub
历史
- 版本 1.0 - 初始发布