Pages Menu
TwitterRssFacebook
Categories Menu

Posted by on 25th December, 2008

Another Silverlight clock experiment…

Another Silverlight clock experiment…

I started working on enhancing the previous Silverlight 2.0 clock I had made, en then I saw a Flash implementation of another creative approach of a clock and wondered if I could do that in Silverlight.

Needless to say, I started working on it immediately and below is the result. This is another experiment that I thought I could do in just a few hours and it ended up taking me a bit more. Mostly because of the uneven conversions between the units and the different sizes between the boxes.

Anyways, in a nutshell here is my approach: I have a very simple XAML file with only defining seven empty canvasses as placeholders, organized from top to bottom by a StackPanel. I will create and populate every single object dynamically and on the fly: I have a single method (CreateBasicCancas) that generates all the textBoxes for each individual gear. Then I have a single Storyboard that iterates every millisecond and that storyboard calls one single method (MoveGears) for all gears. It will loop through the collection of Textboxes of a particular canvas and adjust their position accordingly.

There is an occasional bug with the days not rendering properly, but overall it works decently….

Get Microsoft Silverlight

page.xaml.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Media.Imaging;
namespace Silverlight_Clock_2
{

 public partial class Page : UserControl
 {
  private Storyboard StoryBoard;
  private List weekDayNames = new List();
  private List monthNames  = new List();
  private Line timeLine = new Line();

  public Page()
  {
   InitializeComponent();
   InitializeClock();
   StartClock();
  }

  private void InitializeClock()
  {
   // Add the timeline----------------------------------------------------
   this.timeLine.Stroke = new SolidColorBrush(Colors.Red);
   this.timeLine.X1 = LayoutRoot.Width / 2;
   this.timeLine.Y1 = 0;
   this.timeLine.X2 = LayoutRoot.Width / 2;
   this.timeLine.Y2 = LayoutRoot.Height - currentTime.Height + 5;
   this.timeLine.SetValue(Canvas.ZIndexProperty, 1000);
   LayoutRoot.Children.Add(this.timeLine);
   //---------------------------------------------------------------------

   //populate the weekday name list
   weekDayNames.Add("Sunday");
   weekDayNames.Add("Monday");
   weekDayNames.Add("Tuesday");
   weekDayNames.Add("Wednesday");
   weekDayNames.Add("Thurday");
   weekDayNames.Add("Friday");
   weekDayNames.Add("Saturday");

   //populate the monthday list
   monthNames.Add("December");
   monthNames.Add("January");
   monthNames.Add("February");
   monthNames.Add("March");
   monthNames.Add("April");
   monthNames.Add("May");
   monthNames.Add("June");
   monthNames.Add("July");
   monthNames.Add("Augustus");
   monthNames.Add("September");
   monthNames.Add("October");
   monthNames.Add("November");

   generateBasicCanvas(canvasSeconds, 60, "seconds", 40);
   generateBasicCanvas(canvasMinutes, 60, "minutes", 40);
   generateBasicCanvas(canvasHours, 24, "hours", 60);
   generateBasicCanvas(canvasDayNames, 7, "daynames", 150);
   generateBasicCanvas(canvasDays,  31, "days",  60);
   generateBasicCanvas(canvasMonths, 12, "months", 150);
   generateBasicCanvas(canvasYears, 20, "years", 80);
   }

  private void generateBasicCanvas(
   Canvas _canvas,int _units,string _type,int _width)
   {
    _canvas.Height = 40;
    _canvas.Width = LayoutRoot.Width;
    _canvas.Margin = new Thickness(1, 1, 1, 1);
    for (int i = 1; i <= _units; i++)
    {
     //create the individual textboxes
     TextBox digit = new TextBox();
     digit.Width  = _width;
     digit.Height  = this.canvasSeconds.Height;
     digit.BorderThickness = new Thickness(2);
     digit.BorderBrush  = new SolidColorBrush(Colors.Black);
     digit.Background  = new SolidColorBrush(Colors.LightGray);
     digit.FontSize  = 22;
     digit.TextAlignment  = TextAlignment.Center;
     //orders the boxes 
     digit.SetValue(Canvas.LeftProperty, (digit.Width) * i);
     digit.IsReadOnly = true;
     digit.IsTabStop = false;
     ImageBrush digitBackground = new ImageBrush();
     digitBackground.ImageSource = 
      new BitmapImage(new Uri("background (55 x 41).jpg", UriKind.Relative));
     digit.Background = digitBackground;
     
     //sets the content of the textboxes
     switch (_type)
     {
      case "daynames":
       digit.Text = weekDayNames&#91;i-1&#93;;
       break;
      case "months":
       digit.Text = monthNames&#91;i-1&#93;;
       break;
      case "years":
       digit.Text = (2000 + i).ToString();
       break;
      default:
       digit.Text = (i > 9) ? i.ToString() : "0" + i.ToString();
       break;
     }
     
     //determines the clipping area
     RectangleGeometry clipArea = new RectangleGeometry();
     clipArea.Rect = new Rect(0, 0, _canvas.Width, _canvas.Height);
     _canvas.Clip = clipArea;
     _canvas.Children.Add(digit);
    }
   }

   private void StartClock()
   {
    this.StoryBoard = new Storyboard();
    this.StoryBoard.Duration = TimeSpan.FromMilliseconds(1);
    this.StoryBoard.Completed +=
     new EventHandler(StoryBoardSeconds_Completed);
    StoryBoard.Begin();
   }

   private void StoryBoardSeconds_Completed(object sender, EventArgs e)
   {
    currentTime.Text =
     DateTime.Now.TimeOfDay + " on " + DateTime.Now.ToLongDateString() + ".";
    MoveGears(canvasSeconds, "seconds",  DateTime.Now.Second);
    MoveGears(canvasMinutes, "minutes",  DateTime.Now.Minute);
    MoveGears(canvasHours,  "hours",  DateTime.Now.Hour);
    MoveGears(canvasDayNames, "dayNames",  (int) DateTime.Now.Day);
    MoveGears(canvasDays,  "days",  DateTime.Now.Day);
    MoveGears(canvasMonths,  "monthNames", DateTime.Now.Month);
    MoveGears(canvasYears,  "years",  DateTime.Now.Year);
    StoryBoard.Begin();
   }

   //one single method that moves all gears independently...
   private void MoveGears(Canvas _canvas,string _type,int _timeMargin)
   {
    DateTime _now = DateTime.Now;
    double _moveUnit = 0;
    int _digitValue = 0;
    foreach (TextBox digit in _canvas.Children)
    {
     switch (_type)
     {
      case "dayNames":
       _digitValue = weekDayNames.IndexOf(digit.Text);
       break;
      case "monthNames":
       _digitValue = monthNames.IndexOf(digit.Text);
       break;
      default:
       _digitValue = Convert.ToInt32(digit.Text);
       break;
     }
     int _unitMargin = Convert.ToInt32(this.LayoutRoot.Width / digit.Width);
     //used to tie the wheel around the initial digits...
     if (_timeMargin > _canvas.Children.Count - _unitMargin && 
      //_digitValue >= 0 && 
      _digitValue < _unitMargin )
     {
      _digitValue += _canvas.Children.Count;
     }
     //used to tie the wheel around the last digits...
     if (_timeMargin < _unitMargin && 
      //_digitValue <= _canvas.Children.Count &&
      _digitValue > _canvas.Children.Count - _unitMargin)
     {
      _digitValue -= _canvas.Children.Count;
     }
     //move the selected object
     switch (_type)
     {
      case "seconds":
       _moveUnit = 
        this.timeLine.X1 + ((_digitValue - _now.Second) * 
        digit.Width)-((double)(_now.Millisecond) * 
        (digit.Width / 1000));
       break;
      case "minutes":
       _moveUnit = 
        this.timeLine.X1 + ((_digitValue - _now.Minute) * 
        digit.Width)-((double)(_now.Second + (_now.Millisecond * 0.001)) * 
        (digit.Width / 60));
       break;
      case "hours":
       _moveUnit = 
        this.timeLine.X1 + ((_digitValue - _now.Hour) * 
        digit.Width)-((double)(_now.Minute + (_now.Second * 0.016)) * 
        (digit.Width / 60));
       break;
      case "dayNames":
       _moveUnit = 
        this.timeLine.X1 + ((_digitValue - (int)_now.DayOfWeek) * 
        digit.Width)-((_now.Hour) * (digit.Width / 24));
       break;
      case "days":
       _moveUnit = 
        this.timeLine.X1 + ((_digitValue - _now.Day) * 
        digit.Width)-((_now.Hour) * (digit.Width / 24));
       break;
      case "monthNames":
       _moveUnit = 
        this.timeLine.X1 + ((_digitValue - _now.Month) * digit.Width)-
        ((_now.Day) * (digit.Width /DateTime.Now.DaysInMonth(
         _now.Year,_now.Month)));
       break;
      case "years":
       _moveUnit = 
        this.timeLine.X1 + ((_digitValue - _now.Year) * digit.Width)-
        ((_now.Month) * (digit.Width / _now.DayOfYear));
       break;
      default:
       break;
     }
     digit.SetValue(Canvas.LeftProperty, _moveUnit);
    }
   }
  }
 }

page.xaml

<UserControl x:Class="Silverlight_Clock_2.Page"
 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
 Width="400" Height="310">
 <Grid x:Name="LayoutRoot" Background="Beige" Width="400" Height="310">
  <StackPanel x:Name="StackRoot" >
   <Canvas x:Name="canvasSeconds" /> 
   <Canvas x:Name="canvasMinutes" />
   <Canvas x:Name="canvasHours" />
   <Canvas x:Name="canvasDayNames" />
   <Canvas x:Name="canvasDays"  /> 
   <Canvas x:Name="canvasMonths" />
   <Canvas x:Name="canvasYears" />
   <TextBlock x:Name="currentTime" Height="20" TextAlignment="Right"/>
  </StackPanel>
  <Border BorderBrush="#000000" BorderThickness="2,2,2,2" Width="400" 
   Height="320" CornerRadius="0, 0, 15, 15"/>
 </Grid>
</UserControl>