Binary Clock in C# And WPF

Foreward

The project itself will serve to show some peculiarities, like the use of Tasks, how to manipulate the UI of a WPF page, and basic data conversions.

Introduction

A binary clock is a clock which displays the current time in a binary format. In the following example, we will create a set of graphical leds, each of which will represent a binary digit. Each led could be set in two statuses: on (which represents the value of 1) or off (which represent the value of zero). From right to left, our leds will represents the values of 1, 2, 4, 8, 16, 32, because we will base our conversion on 24h formatted time, and we need a number of digits that can represent up to the decimal value of 60 (for minutes and seconds).

Each part of the current time (hour, minutes, seconds) will have its own row of six leds, to represent the binary conversion of the decimal value. For example, if we wish to display a time like 10:33:42, our leds must be illuminated according to the following pattern:

A Binary Clock in XAML

The XAML rendering of the concept above is fairly simple. In a new XAML page, we need to create three rows of rectangles, whose border radius will be set to 50 to give them a circular shape. Other settings will refer to the filling color, shape shadows, and so on, to draw a led the way we desire. In our example, the led will be shadowed and colored according to the following XAML code:

  1. <Rectangle HorizontalAlignment="Left" Height="35" Margin="211,40,0,0"   
  2.     Stroke="#FF033805" VerticalAlignment="Top"   
  3.     Width="38" RadiusX="50" RadiusY="50">  
  4.       <Rectangle.Effect>  
  5.           <DropShadowEffect BlurRadius="10" ShadowDepth="10"/>  
  6.       </Rectangle.Effect>  
  7.       <Rectangle.Fill>  
  8.           <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">  
  9.               <GradientStop Color="#FFFFFF1B" Offset="0"/>  
  10.               <GradientStop Color="#FF29B413" Offset="0.568"/>  
  11.           </LinearGradientBrush>  
  12.       </Rectangle.Fill>  
  13. </Rectangle>  
Once we have finished creating each row for our UI, and have embellished everything, the XAML page will look like this:

You may refer to the download section, available at the end of the article for a complete reference of the snippet above.

Source Code

The following section explains how our rectangles c be canontrolled to show a binary representation of the current time.

In our XAML Window, we've declared an Event calling -- more precisely, an event that must be fired on the Page loading (Loaded Event). In the code behind of the Window_Loaded routine, we execute two main operations: the first is merely graphical, and consists in setting each rectangle opacity to 0.35, in order to give the impression of a switched-off led. The second one is the execution of the task which will execute calculations and update the UI. More on that later. First, let's see how we can identify a control declared on a XAML page.

Let's take a look at the loop to set all rectangle's opacity to 0.35:

  1. // Sets all rectangles opacity to 0.35  
  2. foreach (var r in LogicalTreeHelper.GetChildren(MainGrid))  
  3. {  
  4.   if (r is Rectangle) (r as Rectangle).Fill.Opacity = 0.35;  
  5. }  

Speaking about identifying controls, the main difference between WinForms and WPF is that we can't refer to a container's controls by using the property Controls(). The WPF-way of doing that kind of operation passes through the LogicalTreeHelper class. Through it, we can call upon the method GetChildren, indicating the name of the main control for which retrieve children controls. In our case, we've executedLogicalTreeHelper.GetChildren on the control MainGrid (the name which identifies the Grid object of our XAML Page). Then, while traversing the controls array, we check if that particular control is a Rectangle and - if so - we'll proceed in setting its Opacity to the desired value.

The second set of instructions from the Window_Loaded event is the execution of a secondary task for calculating the binary representation of each time part, and updating the UI as well. The code is as follows:

  1. Task.Factory.StartNew(() =>  
  2. {  
  3.     // while the thread is running...  
  4.     while (true)  
  5.     {  
  6.         // ...get the current system time  
  7.         DateTime _now = System.DateTime.Now;  
  8.   
  9.         // Convert each part of the system time (i.e.: hour, minutes, seconds) to   
  10.         // binary, filling with 0s up to a length of 6 char each  
  11.         String _binHour = Convert.ToString(_now.Hour, 2).PadLeft(6, '0');  
  12.         String _binMinute = Convert.ToString(_now.Minute, 2).PadLeft(6, '0');  
  13.         String _binSeconds = Convert.ToString(_now.Second, 2).PadLeft(6, '0');  
  14.   
  15.         // For each digit of the binary hour representation  
  16.         for (int i = 0; i <= _binHour.Length - 1; i++)  
  17.         {  
  18.           // Dispatcher invoke to refresh the UI, which belongs to the main thread  
  19.           H0.Dispatcher.Invoke(() =>  
  20.           {  
  21.              // Update the contents of the labels which use decimal h/m/s representation  
  22.              lbHour.Content = _now.Hour.ToString("00");  
  23.              lbMinute.Content = _now.Minute.ToString("00");  
  24.              lbSeconds.Content = _now.Second.ToString("00");  
  25.   
  26.              // Search for a rectangle which name corresponds to the _binHour current char index.  
  27.              // Then, set its opacity to 1 if the current _binHour digit is 1, or to 0.35 otherwise  
  28.              (MainGrid.FindName("H" + i.ToString()) as Rectangle).Fill.Opacity =   
  29.              _binHour.Substring(i, 1).CompareTo("1") == 0 ? 1 : 0.35;  
  30.              (MainGrid.FindName("M" + i.ToString()) as Rectangle).Fill.Opacity =   
  31.              _binMinute.Substring(i, 1).CompareTo("1") == 0 ? 1 : 0.35;  
  32.              (MainGrid.FindName("S" + i.ToString()) as Rectangle).Fill.Opacity =   
  33.              _binSeconds.Substring(i, 1).CompareTo("1") == 0 ? 1 : 0.35;  
  34.           });  
  35.         }  
  36.     }  
  37. });  

Pretty self-explanatory, the task consists in a neverending loop, which continuously retrieves the current system time. Then, it separates it in its three main parts (hours, minutes, seconds) and proceeds in converting them to their binary representation, through the use of the Convert.ToString() function, to which we'll pass the numeric base for conversion (in our case, 2). Since we need three strings of length equal to six (we have six leds for each row), we need to pad each string up to six characters. So, if for example, we are converting the value of 5, the function will produce 101 as output - a value we will pad to 000101.

A second loop which works up to the length of the binary string related to hours (a value that will be always six), will the provide the UI update, using the Dispatcher property to Invoke the update method of an object which runs on another thread (please refer to «VB.NET: Invoke Method to update UI from secondary threads» for further details on Invoke method). For each digit contained in our strings, we need to identify the correctRectangle, to update its Opacity value.

We can accomplish this kind of task through the FindName() function: given a parent object (MainGrid, in our case), FindName will proceed in referring an UI control, if the passed parameter corresponds to an existent control name. Since we've named the Rectangles for each time part with a progressive number (0 to 5, beginning with H for hours, M for minutes, S for seconds), we can ask the function to retrieve the control whose name starts with a particular letter, and continues with an index equal to the current binary string index.

Let's see one of those lines for the sake of clarity: with the following line:

  1. (MainGrid.FindName("H" + i.ToString()) as Rectangle).Fill.Opacity =   
  2.         _binHour.Substring(i, 1).CompareTo("1") == 0 ? 1 : 0.35;  
We are asking: retrieve from MainGrid a control whose name is equal to "H" + the current loop index. Consider it as a Rectangle, then set its Opacity according to the following rule: if the indexed character from the binarystring is 1, then the Opacity must be 1, otherwise it must be set to 0.35. Executing the program will result in what can be seen in the following video.

Demonstrative Video

Download

The complete source code for the article sample can be downloaded from here.

Read more articles on C#:


Recommended Free Ebook
Similar Articles