Ir para o conteúdo

GDI+ - Relógio Analógico

Introdução

A framework oferece-nos um acesso amigável à API GDI+ nativa do sistema operativo, que é o túnel para o desenho de gráficos bidimensionais no ecran ou na impressora. Através deste acesso, são nos disponibilizados métodos para desenhar virtualmente tudo o que a nossa imaginação possa conceber. A melhor maneira para entender os básicos do desenho é observar a sua utilização e integração num exemplo completo. Exactamente para esse efeito, vamos ao longo deste artigo desenhar um relógio analógico 100% funcional e 100% apoiado no "túnel para o GDI+" da framework.

Nota: Este "túnel para a GDI+" encontra-se disponível na framework através do namespace System.Drawing.

Para a prática!

Importante: A falta de explicação no corpo do artigo é compensada pelos comentários nos blocos de código.

Comecemos por declarar e instanciar tudo o que vamos precisar para o relógio.

    'instanciamos uma variavel do tipo Graphics e igualamos ao metodo Form.CreateGraphics
    'para obter uma referencia à superficie de desenho da form
    Private GR As Graphics = Me.CreateGraphics
    'declaramos a constante de PI
    '(tambem o poderiamos fazer utilizando a constante de PI na classe Math)
    Const PI As Double = 3.14159265
    'declaramos a constante da conversão de radianos
    Const ConvRad As Double = PI / 180

    'declaramos o temporizador que actualiza a informação das horas
    Private WithEvents Temporizador As New Timer
    'declaramos as variáveis que representam o centro do form
    Private CentroX As Single = Me.Width / 2
    Private CentroY As Single = Me.Height / 2

Depois vamos configurar e definir o necessário do form e do temporizador.

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        'alteramos o smoothingmode para um modo com anti-aliasing, para melhorar o aspecto
        GR.SmoothingMode = Drawing2D.SmoothingMode.HighQuality
        'preparamos o form para o relogio
        'retiramos os limites
        Me.FormBorderStyle = Windows.Forms.FormBorderStyle.None
        'atribuímos o novo tamanho
        Me.Size = New Size(300, 300)
        'definimos a sua cor de fundo como cor transparente (parece não funcionar na maioria dos casos)
        'Me.TransparencyKey = Me.BackColor
        'estipulamos a posição do relogio no canto inferior direito
        Me.Left = (My.Computer.Screen.WorkingArea.Width) - (Me.Width)
        Me.Top = (My.Computer.Screen.WorkingArea.Height) - (Me.Height)

        'preparamos o temporizador
        Temporizador.Interval = 1000
        Temporizador.Enabled = True

        'desenha todos os elementos na ordem correcta
        RefrescarGraficos()
    End Sub

De seguida começamos a adicionar as subs do desenho em si. Começamos pelo desenho dos limites do relógio.

    Private Sub DesenharLimites()
        'definimos o rectangulo para desenhar o circulo principal
        Dim R1 As New Rectangle(0, 0, 200, 200)

        'centramos o rectangulo com o form
        R1.X = ((CentroX) - R1.Width / 2)
        R1.Y = ((CentroY) - R1.Height / 2)

        'desenhamos o círculo, preenchido
        GR.FillEllipse(Brushes.Black, R1)

        'repetimos o processo para desenhar um outro círculo mais pequeno
        'de cor diferente para fazer de fundo do relógio
        Dim Rect As New Rectangle(0, 0, 190, 190)
        Rect.X = ((CentroX) - Rect.Width / 2)
        Rect.Y = ((CentroY) - Rect.Height / 2)
        GR.FillEllipse(Brushes.AliceBlue, Rect)

    End Sub

Passamos para os marcadores dos minutos.

    Private Sub DesenharMarcadores()
        'os marcadores consistem em 60 marcas à volta do relógio, perto dos seus limites.
        'a sua finalidade é oferecer precisão quando se trata de determinar uma hora

        'obtemos as 60 marcas através de um ciclo.
        'dividimos os 360 graus por as 60 marcas e obtemos a distância, em graus, entre elas
        'utilizamos essa distância para servir de "step" num ciclo que vá do ângulo 0 ao 360
        For i As Double = 0 To 360 Step 360 / 60

            'atribuímos o angulo actual, já convertido para uma variável
            Dim AnguloTemp As Single = i * ConvRad

            'calculamos o ponto inicial exacto através do seno e coseno do angulo actual
            'multiplicamos o resultado das projecções para as afastar do centro, uma vez que são matemáticamente
            'devolvidas abaixo da unidade
            'Calculamos o ponto para as linhas normais (cinco em cinco minutos), as maiores (quartos de hora) e
            'as menores (minutos sem significado)
            Dim TempXInicial As Single = CentroX - (Math.Cos(AnguloTemp) * 80)
            Dim TempYInicial As Single = CentroY - (Math.Sin(AnguloTemp) * 80)

            Dim TempXInicialMaior As Single = CentroX - (Math.Cos(AnguloTemp) * 75)
            Dim TempYInicialMaior As Single = CentroY - (Math.Sin(AnguloTemp) * 75)

            Dim TempXInicialMenor As Single = CentroX - (Math.Cos(AnguloTemp) * 85)
            Dim TempYInicialMenor As Single = CentroY - (Math.Sin(AnguloTemp) * 85)

            'o ponto final é o ponto de origem de qualquer uma das linhas de marcação.
            'o ponto de origem também terá de ser obtido através do calculo das projecções para que cada
            'traço fique perfeitamente inclinado
            Dim TempXFinal As Single = CentroX - (Math.Cos(AnguloTemp) * 90)
            Dim TempYFinal As Single = CentroY - (Math.Sin(AnguloTemp) * 90)

            'declaramos três Pens, uma para cada tipo de marcador...
            Dim M1 As New Pen(Color.Black, 2)
            Dim M2 As New Pen(Color.Black, 2)
            Dim M3 As New Pen(Color.Black, 2)

            '...e estipulamos o estilo das suas terminações.
            'Como são marcadores num relógio e os estamos a desenhar do ponto de distância variável para o fixo
            'estipulamos o StartCap e não o EndCap
            M1.StartCap = Drawing2D.LineCap.Triangle
            M2.StartCap = Drawing2D.LineCap.DiamondAnchor
            M3.StartCap = Drawing2D.LineCap.Triangle

            'aplicamos um pouco de lógica para determinar onde desenhar qual marcador
            If i = 0 Or i = 90 Or i = 180 Or i = 270 Then
                'desenha os marcadores de quartos de hora
                GR.DrawLine(M2, TempXInicialMaior, TempYInicialMaior, TempXFinal, TempYFinal)
            ElseIf i = 30 Or i = 60 Or i = 120 Or i = 150 Or i = 210 Or i = 240 Or i = 300 Or i = 330 Then
                'desenha os marcadores de cinco em cinco minutos
                GR.DrawLine(M3, TempXInicial, TempYInicial, TempXFinal, TempYFinal)
            Else
                'desenha os restantes marcadores comuns
                GR.DrawLine(M1, TempXInicialMenor, TempYInicialMenor, TempXFinal, TempYFinal)
            End If

        Next

    End Sub

Os pormenores como a "marca" e a data.

    Private Sub DesenharMarcaData()
        'por marca e data, entenda-se a suposta "marca" do relógio e a data actual.
        'vamos utilizar uma pequena imagem e a marca fictícia "Tempex"

        'declaramos um bitmap vazio de tamanho 16x16 porque a imagem original é maior
        Dim B As New Bitmap(16, 16)
        'declaramos uma nova variavel do tipo Graphics e determinamos que serve para nos referirmos à superfície
        'de desenho da variável B que é do tipo bitmap.
        'Teóricamente é como fazer uma nova página no paint.
        Dim BG As Graphics = Graphics.FromImage(B)
        'estipulamos o modo de interpolação para um modo de alta qualidade.
        'Como estamos a encolher uma imagem, queremos que isto seja feito da melhor maneira de forma a não
        'perder muita qualidade
        BG.InterpolationMode = Drawing2D.InterpolationMode.HighQualityBicubic
        'utilizamos a variavel do tipo Graphics para desenhar a imagem com o novo tamanho na variável B
        BG.DrawImage(Me.Icon.ToBitmap, New Rectangle(0, 0, 16, 16), New Rectangle(0, 0, CSng(Me.Icon.ToBitmap.Width), CSng(Me.Icon.ToBitmap.Height)), GraphicsUnit.Pixel)

        'por fim, desenhamos na referencia à superficie de desenho do form, a imagem com o novo tamanho e na posição certa
        GR.DrawImage(B, CentroX - 35, CentroY + 28)

        'declaramos as fontes da "marca" e data
        Dim F As New Font("Arial", 14, FontStyle.Bold + FontStyle.Italic, GraphicsUnit.Pixel)
        Dim F2 As New Font("Arial", 9, FontStyle.Regular, GraphicsUnit.Pixel)
        'estipulamos o conteúdo da "marca" e data
        Dim Marca As String = "Tempex"
        Dim Data As String = My.Computer.Clock.LocalTime.ToShortDateString

        'medimos o tamanho da string desenhada antes da desenhar para podermos calcular os centros
        Dim TamStr As New SizeF
        TamStr = GR.MeasureString(Marca, F)
        Dim TamData As New SizeF
        TamData = GR.MeasureString(Data, F2)

        'declaramos um novo Brush gradiente para utilizar com a fonte da "marca"
        Dim GB As New Drawing2D.LinearGradientBrush(New Rectangle(10, 0, TamStr.Width, TamStr.Height), Color.Red, Color.Blue, Drawing2D.LinearGradientMode.Horizontal)
        'desenhamos o texto da "marca" no relógio na posição pretendida
        GR.DrawString(Marca, F, GB, (CentroX - (TamStr.Width / 2)) + 10, CentroY + 28)
        'por fim desenhamos o texto que representa a data na posição pretendida
        GR.DrawString(Data, F2, Brushes.Black, CentroX - (TamData.Width / 2) + 4, CentroY + 45)

    End Sub

E por fim os ponteiros.

    Private Sub DesenharPonteiros()
        'para desenhar os ponteiros precisamos primeiro de saber exactamente que horas são.
        'A partir desses valores vamos trabalhar sobre os segundos para calcular as posições dos pontos

        'elemento a elemento, apanhamos a hora actual
        Dim Hora As Integer = My.Computer.Clock.LocalTime.Hour
        Dim Minuto As Integer = My.Computer.Clock.LocalTime.Minute
        Dim Segundo As Integer = My.Computer.Clock.LocalTime.Second

        'calculamos depois a quantos segundos corresponde a hora actual, somando o equivalente a segundos de todos
        'os elementos.
        Dim SegundosTotais As Long = (Hora * 3600) + (Minuto * 60) + Segundo

        'sabidos os segundos totais, vamos utilizar esse valor para calcular o equivalente ao angulo nos 360 do círculo
        'do relogio.
        'Através de cálculos de relação (ou regras de 3 simples), é nos possivel determinar exactamente o angulo
        'dos ponteiros
        Dim AnguloSegundo As Double = ((SegundosTotais Mod 60) * 360) / 60
        Dim AnguloMinuto As Double = ((SegundosTotais / 60 Mod 60) * 360) / 60
        Dim AnguloHora As Double = ((SegundosTotais / 3600 Mod 24) * 360 / 12)

        'para além dos três ponteiros, vamos calcular um outro para servir de "rabo" do ponteiro dos segundos.
        'É portanto, inverso ao dos segundos e somamos ao angulo dos segundos os 90 do "offset" mais 180 da inversão
        'prefazendo 270º
        Dim AnguloSegundoInverso As Double = (AnguloSegundo + 270) * ConvRad

        'aplicamos a todos os angulos dos ponteiros um offset de 90º para os meter no sítio certo
        'e convertemos o angulo
        AnguloHora = (AnguloHora + 90) * ConvRad
        AnguloMinuto = (AnguloMinuto + 89.9) * ConvRad
        AnguloSegundo = (AnguloSegundo + 90) * ConvRad

        'com o valor do angulo correcto, está na altura de calcular todos os pontos utilizando as projecções
        'tal e qual como os marcadores, mas desta vez apenas os pontos finais pois o ponto inicial é sempre o mesmo: o centro do relógio
        Dim TempHoraXFinal As Single = CentroX - Math.Cos(AnguloHora) * 40
        Dim TempHoraYFinal As Single = CentroY - Math.Sin(AnguloHora) * 40

        Dim TempMinutoXFinal As Single = CentroX - Math.Cos(AnguloMinuto) * 75
        Dim TempMinutoYFinal As Single = CentroY - Math.Sin(AnguloMinuto) * 75

        Dim TempSegundoXFinal As Single = CentroX - Math.Cos(AnguloSegundo) * 85
        Dim TempSegundoYFinal As Single = CentroY - Math.Sin(AnguloSegundo) * 85

        Dim TempSegundoInversoXFinal As Single = CentroX - Math.Cos(AnguloSegundoInverso) * 18
        Dim TempSegundoInversoYFinal As Single = CentroY - Math.Sin(AnguloSegundoInverso) * 18

        'declaramos todas as Pens para todos os ponteiros
        Dim PHora As New Pen(Color.FromArgb(170, 0, 0, 0), 4)
        Dim PMinuto As New Pen(Color.FromArgb(170, 0, 0, 0), 4)
        Dim PSegundo As New Pen(Color.FromArgb(200, 255, 0, 0), 0.8)
        Dim PSegundoInverso As New Pen(Color.Red, 0.8)

        'e determinamos a terminação das linhas
        PHora.EndCap = Drawing2D.LineCap.ArrowAnchor
        PMinuto.EndCap = Drawing2D.LineCap.ArrowAnchor
        PSegundo.EndCap = Drawing2D.LineCap.ArrowAnchor
        PSegundoInverso.EndCap = Drawing2D.LineCap.Round

        'com tudo calculado e pronto, está na altura de os desenhar
        GR.DrawLine(PHora, CentroX, CentroY, TempHoraXFinal, TempHoraYFinal)
        GR.DrawLine(PMinuto, CentroX, CentroY, TempMinutoXFinal, TempMinutoYFinal)
        GR.DrawLine(PSegundo, CentroX, CentroY, TempSegundoXFinal, TempSegundoYFinal)
        GR.DrawLine(PSegundoInverso, CentroX, CentroY, TempSegundoInversoXFinal, TempSegundoInversoYFinal)

        'por fim, desenhamos um pequeno círculo no centro e no topo de todos os ponteiros
        Dim Rect As New Rectangle(0, 0, 10, 10)
        Rect.X = ((CentroX) - Rect.Width / 2)
        Rect.Y = ((CentroY) - Rect.Height / 2)
        GR.FillEllipse(Brushes.Red, Rect)

    End Sub

Mas... está tudo parado!

Os blocos descritos até agora desenham, de facto, um relógio. O problema é que está parado. Mostra apenas a hora do arranque da aplicação e fica simplesmente parado. Teremos de estudar uma forma de o refrescar. Já que o método RefrescarGraficos() desenha o relógio com a hora actual do sistema, deduzimos que por cada vez que ele for chamado, os ponteiros se actualizem. É aqui que entra o temporizador.

    Private Sub Temporizador_Tick(ByVal sender As Object, ByVal e As System.EventArgs) Handles Temporizador.Tick
        RefrescarGraficos()
    End Sub

O exemplo completo

Crie um novo projecto, e substitua todo o código do form de arranque por este:

Public Class Form1

    'instanciamos uma variavel do tipo Graphics e igualamos ao metodo Form.CreateGraphics
    'para obter uma referencia à superficie de desenho da form
    Private GR As Graphics = Me.CreateGraphics
    'declaramos a constante de PI
    '(tambem o poderiamos fazer utilizando a constante de PI na classe Math)
    Const PI As Double = 3.14159265
    'declaramos a constante da conversão de radianos
    Const ConvRad As Double = PI / 180

    'declaramos o temporizador que actualiza a informação das horas
    Private WithEvents Temporizador As New Timer
    'declaramos as variáveis que representam o centro do form
    Private CentroX As Single = Me.Width / 2
    Private CentroY As Single = Me.Height / 2

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        'alteramos o smoothingmode para um modo com anti-aliasing, para melhorar o aspecto
        GR.SmoothingMode = Drawing2D.SmoothingMode.HighQuality
        'preparamos o form para o relogio
        'retiramos os limites
        Me.FormBorderStyle = Windows.Forms.FormBorderStyle.None
        'atribuímos o novo tamanho
        Me.Size = New Size(300, 300)
        'definimos a sua cor de fundo como cor transparente (parece não funcionar na maioria dos casos)
        'Me.TransparencyKey = Me.BackColor
        'estipulamos a posição do relogio no canto inferior direito
        Me.Left = (My.Computer.Screen.WorkingArea.Width) - (Me.Width)
        Me.Top = (My.Computer.Screen.WorkingArea.Height) - (Me.Height)

        'preparamos o temporizador
        Temporizador.Interval = 1000
        Temporizador.Enabled = True

        'desenha todos os elementos na ordem correcta
        RefrescarGraficos()
    End Sub

    Private Sub DesenharLimites()
        'definimos o rectangulo para desenhar o circulo principal
        Dim R1 As New Rectangle(0, 0, 200, 200)

        'centramos o rectangulo com o form
        R1.X = ((CentroX) - R1.Width / 2)
        R1.Y = ((CentroY) - R1.Height / 2)

        'desenhamos o círculo, preenchido
        GR.FillEllipse(Brushes.Black, R1)

        'repetimos o processo para desenhar um outro círculo mais pequeno
        'de cor diferente para fazer de fundo do relógio
        Dim Rect As New Rectangle(0, 0, 190, 190)
        Rect.X = ((CentroX) - Rect.Width / 2)
        Rect.Y = ((CentroY) - Rect.Height / 2)
        GR.FillEllipse(Brushes.AliceBlue, Rect)

    End Sub

    Private Sub DesenharMarcadores()
        'os marcadores consistem em 60 marcas à volta do relógio, perto dos seus limites.
        'a sua finalidade é oferecer precisão quando se trata de determinar uma hora

        'obtemos as 60 marcas através de um ciclo.
        'dividimos os 360 graus por as 60 marcas e obtemos a distância, em graus, entre elas
        'utilizamos essa distância para servir de "step" num ciclo que vá do ângulo 0 ao 360
        For i As Double = 0 To 360 Step 360 / 60

            'atribuímos o angulo actual, já convertido para uma variável
            Dim AnguloTemp As Single = i * ConvRad

            'calculamos o ponto inicial exacto através do seno e coseno do angulo actual
            'multiplicamos o resultado das projecções para as afastar do centro, uma vez que são matemáticamente
            'devolvidas abaixo da unidade
            'Calculamos o ponto para as linhas normais (cinco em cinco minutos), as maiores (quartos de hora) e
            'as menores (minutos sem significado)
            Dim TempXInicial As Single = CentroX - (Math.Cos(AnguloTemp) * 80)
            Dim TempYInicial As Single = CentroY - (Math.Sin(AnguloTemp) * 80)

            Dim TempXInicialMaior As Single = CentroX - (Math.Cos(AnguloTemp) * 75)
            Dim TempYInicialMaior As Single = CentroY - (Math.Sin(AnguloTemp) * 75)

            Dim TempXInicialMenor As Single = CentroX - (Math.Cos(AnguloTemp) * 85)
            Dim TempYInicialMenor As Single = CentroY - (Math.Sin(AnguloTemp) * 85)

            'o ponto final é o ponto de origem de qualquer uma das linhas de marcação.
            'o ponto de origem também terá de ser obtido através do calculo das projecções para que cada
            'traço fique perfeitamente inclinado
            Dim TempXFinal As Single = CentroX - (Math.Cos(AnguloTemp) * 90)
            Dim TempYFinal As Single = CentroY - (Math.Sin(AnguloTemp) * 90)

            'declaramos três Pens, uma para cada tipo de marcador...
            Dim M1 As New Pen(Color.Black, 2)
            Dim M2 As New Pen(Color.Black, 2)
            Dim M3 As New Pen(Color.Black, 2)

            '...e estipulamos o estilo das suas terminações.
            'Como são marcadores num relógio e os estamos a desenhar do ponto de distância variável para o fixo
            'estipulamos o StartCap e não o EndCap
            M1.StartCap = Drawing2D.LineCap.Triangle
            M2.StartCap = Drawing2D.LineCap.DiamondAnchor
            M3.StartCap = Drawing2D.LineCap.Triangle

            'aplicamos um pouco de lógica para determinar onde desenhar qual marcador
            If i = 0 Or i = 90 Or i = 180 Or i = 270 Then
                'desenha os marcadores de quartos de hora
                GR.DrawLine(M2, TempXInicialMaior, TempYInicialMaior, TempXFinal, TempYFinal)
            ElseIf i = 30 Or i = 60 Or i = 120 Or i = 150 Or i = 210 Or i = 240 Or i = 300 Or i = 330 Then
                'desenha os marcadores de cinco em cinco minutos
                GR.DrawLine(M3, TempXInicial, TempYInicial, TempXFinal, TempYFinal)
            Else
                'desenha os restantes marcadores comuns
                GR.DrawLine(M1, TempXInicialMenor, TempYInicialMenor, TempXFinal, TempYFinal)
            End If

        Next

    End Sub

    Private Sub DesenharMarcaData()
        'por marca e data, entenda-se a suposta "marca" do relógio e a data actual.
        'vamos utilizar uma pequena imagem e a marca fictícia "Tempex"

        'declaramos um bitmap vazio de tamanho 16x16 porque a imagem original é maior
        Dim B As New Bitmap(16, 16)
        'declaramos uma nova variavel do tipo Graphics e determinamos que serve para nos referirmos à superfície
        'de desenho da variável B que é do tipo bitmap.
        'Teóricamente é como fazer uma nova página no paint.
        Dim BG As Graphics = Graphics.FromImage(B)
        'estipulamos o modo de interpolação para um modo de alta qualidade.
        'Como estamos a encolher uma imagem, queremos que isto seja feito da melhor maneira de forma a não
        'perder muita qualidade
        BG.InterpolationMode = Drawing2D.InterpolationMode.HighQualityBicubic
        'utilizamos a variavel do tipo Graphics para desenhar a imagem com o novo tamanho na variável B
        BG.DrawImage(Me.Icon.ToBitmap, New Rectangle(0, 0, 16, 16), New Rectangle(0, 0, CSng(Me.Icon.ToBitmap.Width), CSng(Me.Icon.ToBitmap.Height)), GraphicsUnit.Pixel)

        'por fim, desenhamos na referencia à superficie de desenho do form, a imagem com o novo tamanho e na posição certa
        GR.DrawImage(B, CentroX - 35, CentroY + 28)

        'declaramos as fontes da "marca" e data
        Dim F As New Font("Arial", 14, FontStyle.Bold + FontStyle.Italic, GraphicsUnit.Pixel)
        Dim F2 As New Font("Arial", 9, FontStyle.Regular, GraphicsUnit.Pixel)
        'estipulamos o conteúdo da "marca" e data
        Dim Marca As String = "Tempex"
        Dim Data As String = My.Computer.Clock.LocalTime.ToShortDateString

        'medimos o tamanho da string desenhada antes da desenhar para podermos calcular os centros
        Dim TamStr As New SizeF
        TamStr = GR.MeasureString(Marca, F)
        Dim TamData As New SizeF
        TamData = GR.MeasureString(Data, F2)

        'declaramos um novo Brush gradiente para utilizar com a fonte da "marca"
        Dim GB As New Drawing2D.LinearGradientBrush(New Rectangle(10, 0, TamStr.Width, TamStr.Height), Color.Red, Color.Blue, Drawing2D.LinearGradientMode.Horizontal)
        'desenhamos o texto da "marca" no relógio na posição pretendida
        GR.DrawString(Marca, F, GB, (CentroX - (TamStr.Width / 2)) + 10, CentroY + 28)
        'por fim desenhamos o texto que representa a data na posição pretendida
        GR.DrawString(Data, F2, Brushes.Black, CentroX - (TamData.Width / 2) + 4, CentroY + 45)

    End Sub

    Private Sub DesenharPonteiros()
        'para desenhar os ponteiros precisamos primeiro de saber exactamente que horas são.
        'A partir desses valores vamos trabalhar sobre os segundos para calcular as posições dos pontos

        'elemento a elemento, apanhamos a hora actual
        Dim Hora As Integer = My.Computer.Clock.LocalTime.Hour
        Dim Minuto As Integer = My.Computer.Clock.LocalTime.Minute
        Dim Segundo As Integer = My.Computer.Clock.LocalTime.Second

        'calculamos depois a quantos segundos corresponde a hora actual, somando o equivalente a segundos de todos
        'os elementos.
        Dim SegundosTotais As Long = (Hora * 3600) + (Minuto * 60) + Segundo

        'sabidos os segundos totais, vamos utilizar esse valor para calcular o equivalente ao angulo nos 360 do círculo
        'do relogio.
        'Através de cálculos de relação (ou regras de 3 simples), é nos possivel determinar exactamente o angulo
        'dos ponteiros
        Dim AnguloSegundo As Double = ((SegundosTotais Mod 60) * 360) / 60
        Dim AnguloMinuto As Double = ((SegundosTotais / 60 Mod 60) * 360) / 60
        Dim AnguloHora As Double = ((SegundosTotais / 3600 Mod 24) * 360 / 12)

        'para além dos três ponteiros, vamos calcular um outro para servir de "rabo" do ponteiro dos segundos.
        'É portanto, inverso ao dos segundos e somamos ao angulo dos segundos os 90 do "offset" mais 180 da inversão
        'prefazendo 270º
        Dim AnguloSegundoInverso As Double = (AnguloSegundo + 270) * ConvRad

        'aplicamos a todos os angulos dos ponteiros um offset de 90º para os meter no sítio certo
        'e convertemos o angulo
        AnguloHora = (AnguloHora + 90) * ConvRad
        AnguloMinuto = (AnguloMinuto + 89.9) * ConvRad
        AnguloSegundo = (AnguloSegundo + 90) * ConvRad

        'com o valor do angulo correcto, está na altura de calcular todos os pontos utilizando as projecções
        'tal e qual como os marcadores, mas desta vez apenas os pontos finais pois o ponto inicial é sempre o mesmo: o centro do relógio
        Dim TempHoraXFinal As Single = CentroX - Math.Cos(AnguloHora) * 40
        Dim TempHoraYFinal As Single = CentroY - Math.Sin(AnguloHora) * 40

        Dim TempMinutoXFinal As Single = CentroX - Math.Cos(AnguloMinuto) * 75
        Dim TempMinutoYFinal As Single = CentroY - Math.Sin(AnguloMinuto) * 75

        Dim TempSegundoXFinal As Single = CentroX - Math.Cos(AnguloSegundo) * 85
        Dim TempSegundoYFinal As Single = CentroY - Math.Sin(AnguloSegundo) * 85

        Dim TempSegundoInversoXFinal As Single = CentroX - Math.Cos(AnguloSegundoInverso) * 18
        Dim TempSegundoInversoYFinal As Single = CentroY - Math.Sin(AnguloSegundoInverso) * 18

        'declaramos todas as Pens para todos os ponteiros
        Dim PHora As New Pen(Color.FromArgb(170, 0, 0, 0), 4)
        Dim PMinuto As New Pen(Color.FromArgb(170, 0, 0, 0), 4)
        Dim PSegundo As New Pen(Color.FromArgb(200, 255, 0, 0), 0.8)
        Dim PSegundoInverso As New Pen(Color.Red, 0.8)

        'e determinamos a terminação das linhas
        PHora.EndCap = Drawing2D.LineCap.ArrowAnchor
        PMinuto.EndCap = Drawing2D.LineCap.ArrowAnchor
        PSegundo.EndCap = Drawing2D.LineCap.ArrowAnchor
        PSegundoInverso.EndCap = Drawing2D.LineCap.Round

        'com tudo calculado e pronto, está na altura de os desenhar
        GR.DrawLine(PHora, CentroX, CentroY, TempHoraXFinal, TempHoraYFinal)
        GR.DrawLine(PMinuto, CentroX, CentroY, TempMinutoXFinal, TempMinutoYFinal)
        GR.DrawLine(PSegundo, CentroX, CentroY, TempSegundoXFinal, TempSegundoYFinal)
        GR.DrawLine(PSegundoInverso, CentroX, CentroY, TempSegundoInversoXFinal, TempSegundoInversoYFinal)

        'por fim, desenhamos um pequeno círculo no centro e no topo de todos os ponteiros
        Dim Rect As New Rectangle(0, 0, 10, 10)
        Rect.X = ((CentroX) - Rect.Width / 2)
        Rect.Y = ((CentroY) - Rect.Height / 2)
        GR.FillEllipse(Brushes.Red, Rect)

    End Sub

    Private Sub Temporizador_Tick(ByVal sender As Object, ByVal e As System.EventArgs) Handles Temporizador.Tick
        RefrescarGraficos()
    End Sub

    Private Sub RefrescarGraficos()
        Me.SuspendLayout()

        'primeiro limpamos toda a superfície
        GR.Clear(Me.BackColor)
        'de seguida desenham-se os limites
        DesenharLimites()
        'depois os marcadores
        DesenharMarcadores()
        'de seguida os dizeres de "marca" e a data
        DesenharMarcaData()
        'por fim os ponteiros, pois são elementos móveis e estão na camada superior
        DesenharPonteiros()

        Me.ResumeLayout()
    End Sub
End Class

Amostra

Imagem do relógio analógico obtido