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….
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[i-1]; break; case "months": digit.Text = monthNames[i-1]; 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>