Binary Clock in C# and WPF
https://msdnshared.blob.core.windows.net/media/2016/08/0841.NinjaAwardTinySilver.pngSilver Award Winner
Forewords
In this article we will create a simple binary clock using C# and WPF. 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 represent 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, which 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:
<Rectangle HorizontalAlignment="Left" Height="35" Margin="211,40,0,0" Stroke="#FF033805" VerticalAlignment="Top" Width="38" RadiusX="50" RadiusY="50">
<Rectangle.Effect>
<DropShadowEffect BlurRadius="10" ShadowDepth="10"/>
</Rectangle.Effect>
<Rectangle.Fill>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFFFFF1B" Offset="0"/>
<GradientStop Color="#FF29B413" Offset="0.568"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
Once finished creating each row for our UI, and having embellished everything, the XAML page will look like this:
You could 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 could be controlled 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 consist 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:
// Sets all rectangles opacity to 0.35
foreach (var r in LogicalTreeHelper.GetChildren(MainGrid))
{
if (r is Rectangle) (r as Rectangle).Fill.Opacity = 0.35;
}
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 operations 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 executed LogicalTreeHelper.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:
Task.Factory.StartNew(() =>
{
// while the thread is running...
while (true)
{
// ...get the current system time
DateTime _now = System.DateTime.Now;
// Convert each part of the system time (i.e.: hour, minutes, seconds) to binary, filling with 0s up to a length of 6 char each
String _binHour = Convert.ToString(_now.Hour, 2).PadLeft(6, '0');
String _binMinute = Convert.ToString(_now.Minute, 2).PadLeft(6, '0');
String _binSeconds = Convert.ToString(_now.Second, 2).PadLeft(6, '0');
// For each digit of the binary hour representation
for (int i = 0; i <= _binHour.Length - 1; i++)
{
// Dispatcher invoke to refresh the UI, which belongs to the main thread
H0.Dispatcher.Invoke(() =>
{
// Update the contents of the labels which use decimal h/m/s representation
lbHour.Content = _now.Hour.ToString("00");
lbMinute.Content = _now.Minute.ToString("00");
lbSeconds.Content = _now.Second.ToString("00");
// Search for a rectangle which name corresponds to the _binHour current char index.
// Then, set its opacity to 1 if the current _binHour digit is 1, or to 0.35 otherwise
(MainGrid.FindName("H" + i.ToString()) as Rectangle).Fill.Opacity = _binHour.Substring(i, 1).CompareTo("1") == 0 ? 1 : 0.35;
(MainGrid.FindName("M" + i.ToString()) as Rectangle).Fill.Opacity = _binMinute.Substring(i, 1).CompareTo("1") == 0 ? 1 : 0.35;
(MainGrid.FindName("S" + i.ToString()) as Rectangle).Fill.Opacity = _binSeconds.Substring(i, 1).CompareTo("1") == 0 ? 1 : 0.35;
});
}
}
});
Pretty self-explanatory, the task consists in a neverending loop, which continuously retrieve the current system time. Then, it separates it in its three main parts (hours, minutes, seconds) and proceed 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 ever 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 correct Rectangle, 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
(MainGrid.FindName("H" + i.ToString()) as Rectangle).Fill.Opacity = _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 it's Opacity according to the following rule: if the indexed character from the binary string 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 at: https://code.msdn.microsoft.com/Binary-Clock-in-C-and-WPF-f954c9a5