Click here to Skip to main content
15,893,588 members
Articles / Multimedia / GDI+

Analog Clock Control

Rate me:
Please Sign up or sign in to vote.
4.58/5 (66 votes)
23 Jan 2010CPOL4 min read 319.8K   30.7K   138  
The analog clock control is a control that has almost all the functionality that a clock control can have, and it is fully modifiable.

#Region " Enumarations "

''' <summary>
''' Represents AnalogClock.MarkerStyle enumeration for AnalogClock.Marker object.
''' </summary>
Public Enum MarkerStyle As Integer
    Regular = 0
    Ring = 1
    Triangle = 2
    Custom = 3
End Enum

#End Region

''' <summary>
''' Represents the AnalogClock.Marker object for AnalogClock.Clock.
''' </summary>
<DebuggerNonUserCode()> _
<System.ComponentModel.TypeConverterAttribute(GetType(MarkerConverter))> _
Public Class Marker
    Inherits Element

    Friend Event CustomElementRequest As EventHandler(Of AnalogClock.CustomElementEventArgs)
    Private _tMatrix As Drawing2D.Matrix
    Private _basePath As Drawing2D.GraphicsPath
    Private WithEvents _paintAttributes As PaintAttributes
    Private _smoothMode As SmoothMode
    Private _width As Single
    Private _relativeLength As Single
    Private _style As MarkerStyle
    Private _disposedValue As Boolean

#Region " Properties "

    ''' <summary>
    ''' Gets or sets the paint attributes of the element.
    ''' </summary>
    <System.ComponentModel.Category("Appearance"), _
    System.ComponentModel.Description("Indicates the paint attributes for the element.")> _
    Public Property [PaintAttributes]() As PaintAttributes
        Get
            Return Me._paintAttributes
        End Get
        Set(ByVal value As PaintAttributes)
            If Not Me._paintAttributes.Equals(value) Then
                Me._paintAttributes = value
                Me.OnPropertyChanged(New System.ComponentModel.PropertyChangedEventArgs("PaintAttributes"))
            End If
        End Set
    End Property

    ''' <summary>
    ''' Gets or sets the smoothing mode of the element.
    ''' </summary>
    <System.ComponentModel.DefaultValue(GetType(SmoothMode), "AntiAlias"), System.ComponentModel.Category("Appearance"), _
    System.ComponentModel.Description("Indicates the smoothing mode of the element.")> _
    Public Property [SmoothMode]() As SmoothMode
        Get
            Return Me._smoothMode
        End Get
        Set(ByVal [smoothMode] As SmoothMode)
            If Not [Enum].IsDefined(GetType(SmoothMode), [smoothMode]) Then
                Throw New System.ComponentModel.InvalidEnumArgumentException( _
                "smoothMode", [smoothMode], GetType(SmoothMode))
            Else
                If Me._smoothMode <> [smoothMode] Then
                    Me._smoothMode = [smoothMode]
                    Me.OnPropertyChanged(New System.ComponentModel.PropertyChangedEventArgs("SmoothingMode"))
                End If
            End If
        End Set
    End Property

    ''' <summary>
    ''' Gets or sets the element's relative-length that is directly proportional to its radius.
    ''' </summary>
    <System.ComponentModel.Category("Layout"), System.ComponentModel.TypeConverterAttribute(GetType(PercentageConverter)), _
    System.ComponentModel.Description("Indicates the element's relative length that is directly proportional to its radius.")> _
    Public Property RelativeLength() As Single
        Get
            Return Me._relativeLength
        End Get
        Set(ByVal relativeLength As Single)
            If relativeLength < 0.0F OrElse relativeLength > 1.0F Then
                Throw New ArgumentOutOfRangeException( _
                "relativeLength", relativeLength, "The value should be in the range 0% to 100%.")
            Else
                If Me._relativeLength <> relativeLength Then
                    Me._relativeLength = relativeLength
                    Me.UpdateBasePath()
                    Me.OnPropertyChanged(New System.ComponentModel.PropertyChangedEventArgs("RelativeLength"))
                End If
            End If
        End Set
    End Property

    ''' <summary>
    ''' Gets or sets the element's width.
    ''' </summary>
    <System.ComponentModel.DefaultValue(1.0F), System.ComponentModel.Category("Layout"), _
    System.ComponentModel.Description("Indicates the element's width.")> _
    Public Property Width() As Single
        Get
            Return Me._width
        End Get
        Set(ByVal width As Single)
            If width < 1.0F Then
                Throw New ArgumentOutOfRangeException( _
                "width", width, "The value should be in the range 1 to " & Single.MaxValue)
            Else
                If Me._width <> width Then
                    Me._width = width
                    Me.UpdateBasePath()
                    Me.OnPropertyChanged(New System.ComponentModel.PropertyChangedEventArgs("Width"))
                End If
            End If
        End Set
    End Property

    ''' <summary>
    ''' Gets or sets the element's style. If the style is 'Custom', 
    ''' than related 'AnalogClock.CustomElementRequest' event should be handled.
    ''' </summary>
    <System.ComponentModel.DefaultValue(GetType(MarkerStyle), "Regular"), _
    System.ComponentModel.Category("Appearance"), _
    System.ComponentModel.Description("Indicates the element's style. If the style is 'Custom', " _
    & "than related 'CustomElementRequest' event should be handled.")> _
    Public Property Style() As MarkerStyle
        Get
            Return Me._style
        End Get
        Set(ByVal style As MarkerStyle)
            If Not [Enum].IsDefined(GetType(MarkerStyle), style) Then
                Throw New System.ComponentModel.InvalidEnumArgumentException("style", style, GetType(MarkerStyle))
            Else
                If Me._style <> style Then
                    Me._style = style
                    Me.UpdateBasePath()
                    Me.OnPropertyChanged(New System.ComponentModel.PropertyChangedEventArgs("Style"))
                End If
            End If
        End Set
    End Property

    ''' <summary>
    ''' Gets the element's length.
    ''' </summary>
    <System.ComponentModel.Browsable(False)> _
    Public ReadOnly Property Length() As Single
        Get
            Return Me.Radius * Me._relativeLength
        End Get
    End Property

    ''' <summary>
    ''' Gets the element's graphics path.
    ''' </summary>
    <System.ComponentModel.Browsable(False)> _
    Public ReadOnly Property Path() As Drawing2D.GraphicsPath
        Get
            Return Me._basePath
        End Get
    End Property

#End Region

#Region " Methods "

    ''' <param name="name">The name of the element.</param>
    ''' <param name="angle">The angle of the element in degrees.</param>
    ''' <param name="clockRadius">The cluck's radius for the element.</param>
    ''' <param name="col">The color of the element.</param>
    ''' <param name="width">The width of the element.</param>
    ''' <param name="relativeLength">The relative length of the 
    ''' element that is directly proportional to its radius.</param>
    ''' <param name="relativeRadius">The relative radius of the 
    ''' element that is directly proportional to the clock's radius.</param>
    Sub New(ByVal name As String, _
            ByVal clockRadius As Single, _
            ByVal relativeRadius As Single, _
            ByVal angle As Single, _
            ByVal col As Color, _
            ByVal width As Single, _
            ByVal relativeLength As Single)

        Me.New(name, clockRadius, relativeRadius, _
               angle, col, width, relativeLength, True)
    End Sub

    ''' <param name="name">The name of the element.</param>
    ''' <param name="angle">The angle of the element in degrees.</param>
    ''' <param name="clockRadius">The cluck's radius for the element.</param>
    ''' <param name="col">The color of the element.</param>
    ''' <param name="width">The width of the element.</param>
    ''' <param name="relativeLength">The relative length of the 
    ''' element that is directly proportional to its radius.</param>
    ''' <param name="relativeRadius">The relative radius of the 
    ''' element that is directly proportional to the clock's radius.</param>
    ''' <param name="visible">The visibility of the element.</param>
    Sub New(ByVal name As String, _
           ByVal clockRadius As Single, _
           ByVal relativeRadius As Single, _
           ByVal angle As Single, _
           ByVal col As Color, _
           ByVal width As Single, _
           ByVal relativeLength As Single, _
           ByVal visible As Boolean)

        Me.New(name, angle, col, MarkerStyle.Regular, _
               visible, clockRadius, _
               width, relativeLength, relativeRadius, _
               New PaintAttributes(AnalogClock.PaintObject.Brush), _
               SmoothMode.AntiAlias, _
               Nothing)
    End Sub

    ''' <param name="name">The name of the element.</param>
    ''' <param name="angle">The angle of the element in degrees.</param>
    ''' <param name="col">The color of the element.</param>
    ''' <param name="style">The style of the element.</param>
    ''' <param name="visible">The visibility of the element.</param>
    ''' <param name="clockRadius">The cluck's radius for the element.</param>
    ''' <param name="width">The width of the element.</param>
    ''' <param name="relativeLength">The relative length of the 
    ''' element that is directly proportional to its radius.</param>
    ''' <param name="relativeRadius">The relative radius of the 
    ''' element that is directly proportional to the clock's radius.</param>
    ''' <param name="paintAttributes">The paint attributes of the element.</param>
    ''' <param name="smoothMode">The smoothing mode of the element.</param>
    Sub New(ByVal name As String, _
            ByVal angle As Single, _
            ByVal col As Color, _
            ByVal style As MarkerStyle, _
            ByVal visible As Boolean, _
            ByVal clockRadius As Single, _
            ByVal width As Single, _
            ByVal relativeLength As Single, _
            ByVal relativeRadius As Single, _
            ByVal [paintAttributes] As PaintAttributes, _
            ByVal [smoothMode] As SmoothMode, _
            ByVal tag As Object)

        MyBase.New(name, angle, col, visible, relativeRadius, clockRadius, tag)
        Me._style = style
        Me._width = width
        Me._relativeLength = relativeLength
        Me._paintAttributes = [paintAttributes]
        Me._smoothMode = [smoothMode]
        Me._tMatrix = New Drawing2D.Matrix
        Me._basePath = New Drawing2D.GraphicsPath
    End Sub

    ''' <summary>
    ''' Paints the clock's element.
    ''' </summary>
    ''' <param name="e">A System.Windows.Forms.PaintEventArgs 
    ''' object that will paint the element.</param>
    Friend Overrides Sub Paint(ByVal e As System.Windows.Forms.PaintEventArgs)
        If Me.Visible Then
            Dim arg As New AnalogClock.PaintEventArgs(e.Graphics, e.ClipRectangle)
            Me.OnElementPainting(arg)
            arg.Graphics.SmoothingMode = CType(Me._smoothMode, Drawing2D.SmoothingMode)
            If Me._width > 1 Then
                If Me._paintAttributes.PaintObject = AnalogClock.PaintObject.Brush Then
                    If arg.Brush IsNot Nothing Then
                        arg.Graphics.FillPath(arg.Brush, Me._basePath)
                    Else
                        Dim br As SolidBrush = New SolidBrush(Me.Color)
                        arg.Graphics.FillPath(br, Me._basePath)
                        br.Dispose()
                    End If
                Else
                    If arg.Brush IsNot Nothing Then
                        Dim p As New Pen(arg.Brush, Me._paintAttributes.PenWidth)
                        arg.Graphics.DrawPath(p, Me._basePath)
                        p.Dispose()
                    Else
                        Dim p As New Pen(Me.Color, Me._paintAttributes.PenWidth)
                        arg.Graphics.DrawPath(p, Me._basePath)
                        p.Dispose()
                    End If
                End If
            Else
                Dim p As New Pen(Me.Color)
                arg.Graphics.DrawPath(p, Me._basePath)
                p.Dispose()
            End If
            arg.Dispose()
            Me.OnElementPainted(e)
        End If
    End Sub

    ''' <summary>
    ''' Updates the element's base-path.
    ''' </summary>
    Protected Friend Sub UpdateBasePath()
        If Me._style = MarkerStyle.Regular Then
            Me.ToRegular()
        ElseIf Me._style = MarkerStyle.Ring Then
            Me.ToRing()
        ElseIf Me._style = MarkerStyle.Triangle Then
            Me.ToTriangle()
        ElseIf Me._style = MarkerStyle.Custom Then
            Me._basePath.Reset()
            Dim e As New CustomElementEventArgs
            Me.OnCustomElementRequest(e)
            If e.CustomPath IsNot Nothing Then
                Me._basePath.AddPath(e.CustomPath, False)
                e.Dispose()
            End If
        End If
        Me.RotateMarker()
    End Sub

    ''' <summary>
    ''' Rotate the element's base-paths.
    ''' </summary>
    Private Sub RotateMarker()
        If Me._basePath.PointCount > 0 Then
            Me._tMatrix.Reset()
            Me._tMatrix.RotateAt(90 - Me.Angle, Me.PivotalPoint)
            Me._basePath.Transform(Me._tMatrix)
        End If
    End Sub

    ''' <summary>
    ''' Releases the unmanaged resources used by the 
    ''' AnalogClock.Element and optionally releases the managed resources.
    ''' </summary>
    ''' <param name="disposing">True to release both base and derived class 
    ''' resources; false to release only base class resources.</param>
    Protected Overrides Sub Dispose(ByVal disposing As Boolean)
        If Not Me._disposedValue Then
            If disposing Then
                Me._tMatrix.Dispose()
                Me._basePath.Dispose()
            End If
        End If
        Me._disposedValue = True
        MyBase.Dispose(disposing)
    End Sub

#End Region

#Region " Events "

    Private Sub _paintAttributes_PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) _
    Handles _paintAttributes.PropertyChanged
        Me.OnPropertyChanged(e)
    End Sub

#End Region

#Region " OnEvent "

    ''' <summary>
    ''' Raises the AnalogClock.Element.PropertyChanged event.
    ''' </summary>
    ''' <param name="e">A System.ComponentModel.PropertyChangedEventArgs 
    ''' that contains the event data.</param>
    Protected Overrides Sub OnPropertyChanged(ByVal e As System.ComponentModel.PropertyChangedEventArgs)
        If e.PropertyName = "RelativeRadius" OrElse e.PropertyName = "ClockRadius" Then
            Me.UpdateBasePath()
        End If
        MyBase.OnPropertyChanged(e)
    End Sub

    ''' <summary>
    ''' Raises the AnalogClock.Marker.CustomElementRequest event.
    ''' </summary>
    ''' <param name="e">An AnalogClock.CustomElementEventArgs
    ''' that contains the event data.</param>
    Protected Overridable Sub OnCustomElementRequest(ByVal e As AnalogClock.CustomElementEventArgs)
        RaiseEvent CustomElementRequest(Me, e)
    End Sub

#End Region

#Region " Styles "

    ''' <summary>
    ''' Sets the element's base-path to regular style (at 12 o'clock position).
    ''' </summary>
    Private Sub ToRegular()
        'Reset the base-path
        Me._basePath.Reset()
        If Me._width <> 0.0F AndAlso Me.Length <> 0.0F Then
            Dim pOuter, pInner As PointF
            pOuter = New PointF(Me.PivotalPoint.X, Me.ClockRadius - Me.Radius)
            pInner = New PointF(Me.PivotalPoint.X, Me.ClockRadius - (Me.Radius - Me.Length))
            Me._basePath.AddLine(pOuter, pInner)
            'If the hand's width is biger than 1, than widen the path
            If Me._width > 1 Then
                Dim p As New Pen(Color.Transparent, Me._width)
                Me._basePath.Widen(p)
                p.Dispose()
            End If
            Me._basePath.CloseAllFigures()
        End If
    End Sub

    ''' <summary>
    ''' Sets the element's base-path to triangle style (at 12 o'clock position).
    ''' </summary>
    Private Sub ToTriangle()
        'Reset the base-path
        Me._basePath.Reset()
        If Me._width <> 0.0F AndAlso Me.Length <> 0.0F Then
            Dim pOuter, pInner As PointF
            Dim points(2) As PointF
            pOuter = New PointF(Me.PivotalPoint.X, Me.ClockRadius - Me.Radius)
            pInner = New PointF(Me.PivotalPoint.X, Me.ClockRadius - (Me.Radius - Me.Length))
            points(0) = New PointF(pOuter.X - Me._width / 2, pOuter.Y)
            points(1) = New PointF(pOuter.X + Me._width / 2, pOuter.Y)
            points(2) = pInner
            Me._basePath.AddLines(points)
            Me._basePath.CloseAllFigures()
        End If
    End Sub

    ''' <summary>
    ''' Sets the element's base-path to pointed style (at 12 o'clock position).
    ''' </summary>
    Private Sub ToRing()
        'Reset the base-path
        Me._basePath.Reset()
        If Me._width <> 0.0F AndAlso Me.Length <> 0.0F Then
            Dim pOuter, pInner As PointF
            pOuter = New PointF(Me.PivotalPoint.X, Me.ClockRadius - Me.Radius)
            pInner = New PointF(Me.PivotalPoint.X, Me.ClockRadius - (Me.Radius - Me.Length))
            Dim rect As New RectangleF(pOuter.X - Me._width / 2, pOuter.Y, Me._width, Me.Length)
            Me._basePath.AddEllipse(rect)
            Me._basePath.CloseAllFigures()
        End If
    End Sub

#End Region

End Class

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) ZipEdTech
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions