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 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
:
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 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 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 (true)
{
DateTime _now = System.DateTime.Now;
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 (int i = 0; i <= _binHour.Length - 1; i++)
{
H0.Dispatcher.Invoke(() =>
{
lbHour.Content = _now.Hour.ToString("00");
lbMinute.Content = _now.Minute.ToString("00");
lbSeconds.Content = _now.Second.ToString("00");
(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 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 string
s 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 string
s, 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 its 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
<iframe allowfullscreen="" frameborder="0" height="315" src="https://www.youtube.com/embed/CnaHok8uL0k" width="420"></iframe>
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.