Click here to Skip to main content
Click here to Skip to main content

Binary Clock 3D Screensaver in WPF

By , 20 Jan 2009
 
BinaryClock_Overview.png

Table of Contents

Introduction

Although you may find several binary clock projects throughout the web, this article will show you not only how to create a binary clock, but also how to host it in space and use it as a 3D rotating screensaver – all of this utilizing Windows Presentation Foundation and its powerful concepts such as data binding, data templates, styling, automatic layout, animation and 3D support.

This binary clock project originated as a mere experiment to help me understand the above WPF concepts. What you see here is therefore part of my WPF learning process, which I thought might be useful also for other enthusiasts of the technology.

Background

Maybe you already know the concept of a Binary Clock: it's a clock where you read the 6 digits of current time as 6 columns, each consisting of some lit and unlit LEDs. Actually, we can better call it a pseudo-binary clock, since it shows the digits in BCD (binary coded decimal) notion. If you like the concept, but don't have it on your desk yet, then you can have it at least as your screensaver by downloading the attached binaries.

The Core - Binary Clock

First, the binary clock has a simple task: to show its six digits, one by one. Secondly, the digits themselves don't even need to know anything about their visual representation, thus to represent a BCD digit, it’s fully enough to have a purely data class for them. The idea behind displaying a data class is to apply a DataTemplate to it. This way we could simply put six instances of the Digit class into some items container of our window and leave the rest of the look-and-feel-work to be done by our DataTemplate.

This is what the basic structure of that template looks like:

<DataTemplate DataType="{x:Type local:Digit}">

    <DataTemplate.Resources>
        ...
    </DataTemplate.Resources>

    <!-- BCD Digits consist of 4 bits -->
    <StackPanel Orientation="Vertical">

        <Rectangle Style="{StaticResource fourthBit}" />
        <Rectangle Style="{StaticResource thirdBit}" />
        <Rectangle Style="{StaticResource secondBit}" />
        <Rectangle Style="{StaticResource firstBit}" />

    </StackPanel>
</DataTemplate> 

Each BCD digit consists of four binary digits/bits. They're stacked one above the other, the least significant bit being placed at the bottom and the most significant bit at the top.

It’s the style applied to any specific bit which tells whether that bit needs to be lit or unlit at a certain moment of time. For example, the least significant bit needs to be lit if the BCD digit’s value modulo 2 gives nonzero (that is, every other second of time). Similarly, the bit above the least significant bit needs to be lit if the BCD digit’s value modulo 4 gives a result of 2 or greater number, etc. This logic can be generalized by using a WPF value converter, having some simple modulo computations inside. The DigitModuloConverter class will tell us if the specified modulo of any integer value is greater than the supplied parameter or not.

class DigitModuloConverter : DependencyObject, IValueConverter
{
    public static DependencyProperty ModuloModeProperty = ...
    public int ModuloMode { ... }
    public object Convert(object value, Type targetType, object parameter,
                          System.Globalization.CultureInfo culture)
    {
        int source = (int)value;
        int param = 0;
        Int32.TryParse(parameter as string, out param);
        return (source % this.ModuloMode > param);
    }
    ...
}

Having that converter in our toolbox, we can directly convert a BCD digit’s value to the on/off state of its bits. This is achieved by Data Triggers of WPF: each bit will use a highlighted fill and highlighted stroke color whenever its assigned DigitModuloConverter tells so. Otherwise, it remains or switches back to a basic, dark color.

<DataTemplate.Resources>

    <!-- Digit converters -->    
    <local:DigitModuloConverter x:Key="moduloTwoConverter" ModuloMode="2" />
    <local:DigitModuloConverter x:Key="moduloFourConverter" ModuloMode="4" />
    <local:DigitModuloConverter x:Key="moduloEightConverter" ModuloMode="8" />
    <local:DigitModuloConverter x:Key="moduloSixteenConverter" ModuloMode="16" />

        ...
    <!-- Appearance of 1st bit in Digit (the least significant one) -->
    <Style TargetType="Rectangle" x:Key="firstBit"
                                  BasedOn="{StaticResource bitsCommonStyle}">
        <Style.Triggers>

            <DataTrigger Binding="{Binding Path=Value,
                                   Converter={StaticResource moduloTwoConverter}}"
                                   Value="True">
                <Setter Property="Stroke" 
			Value="{DynamicResource fullRectBorderBrush}" />

                <Setter Property="Fill" Value="{DynamicResource fullRectBrush}" />
            </DataTrigger>
        </Style.Triggers>
    </Style>

    ...
</DataTemplate.Resources>

When all of this is set up, it’s enough to have a timer which updates our BCD digit values each second. The BCD digits – i.e. instances of the Digit class – are declared in XAML and their update is done in the setTimeToDigits() function.

private void setTimeToDigits()
{
    System.DateTime now = System.DateTime.Now;

    this.hourUpper.Value = (int)now.Hour / 10;
    this.hourLower.Value = now.Hour % 10;

    this.minUpper.Value = (int)now.Minute / 10;
    this.minLower.Value = now.Minute % 10;

    this.secUpper.Value = (int)now.Second / 10;
    this.secLower.Value = now.Second % 10;
}

The Digit instances need to have their IsUpperDigit and IsHourDigit properties properly set. The styles of the bits read these attributes and hide some unnecessary bits based on them. E.g. the BCD digit of “tens” of hours will have the upper 2 bits hidden, because we have only 24 hours in a day. Also, the BCD digit of “tens” of minutes will have the most significant bit hidden, since even to display 59 minutes we need only 3 bits for the “tens” part.

<StackPanel ...>
    <StackPanel ...>
        <ContentControl>
            <local:Digit x:Name="hourUpper" IsUpperDigit="True" IsHourDigit="True"/>

        </ContentControl>

        <ContentControl>
            <local:Digit x:Name="hourLower" IsHourDigit="True" />
        </ContentControl>

    </StackPanel>
    <StackPanel ...>
        <ContentControl>

            <local:Digit x:Name="minUpper" IsUpperDigit="True" />

        </ContentControl>
        <ContentControl>
            <local:Digit x:Name="minLower" />
        </ContentControl>
    </StackPanel>

    <StackPanel ...>
        <ContentControl>
            <local:Digit x:Name="secUpper" IsUpperDigit="True" />
        </ContentControl>

        <ContentControl>

            <local:Digit x:Name="secLower" />
        </ContentControl>
    </StackPanel>
</StackPanel>

Screensaver in 3D and Configuration with Live Preview

What a Windows screensaver needs to be is basically an *.exe module accepting some well-defined command line arguments. A screensaver primarily needs to display a full screen window, but it should also support a preview mode and provide some optional settings widow for customization.

This project goes even further: it supports not only a simple preview mode, but inside the settings window it offers also a live preview to interactively show how each change will affect the output. 

BinaryClock_Settings_Video.png 

Click to see the video demonstrating the Live Preview.

This could be achieved by reusing the BinaryClockPage class, which provides the core functionality for the binary clock (see the previous section).

The 3D effect of the clock is achieved by adding the Digit class instances into a ViewPort3D object. This way we project 2D Rectangles to a 3D surface in space:

<Viewport3D>

    ...
    <!-- 2D elements on 3D surface -->

    <Viewport2DVisual3D>
        <!-- Give the plane a slight rotation -->
        <Viewport2DVisual3D.Transform>
            <RotateTransform3D>
                <RotateTransform3D.Rotation>

                    <AxisAngleRotation3D x:Name="planeRotation"
                                         Angle="0"
                                         Axis="0, -1, -0.17" />

                </RotateTransform3D.Rotation>

            </RotateTransform3D>
        </Viewport2DVisual3D.Transform>

        <!-- The Geometry, Material, and Visual for the Viewport2DVisual3D -->
        <Viewport2DVisual3D.Geometry>

            <MeshGeometry3D ... />
        </Viewport2DVisual3D.Geometry>

        <Viewport2DVisual3D.Material>
            <DiffuseMaterial Viewport2DVisual3D.IsVisualHostMaterial="True"
                             Brush="White"/>
        </Viewport2DVisual3D.Material>
        ...
    </Viewport2DVisual3D>

    ...
</Viewport3D>

The binary clock has a rotating animation around the vertical axis. The speed of this rotation can be adjusted through the settings window with the help of a simple slider. Another slider serves for setting the camera distance, thus making the binary clock appear closer or further from the screen.  

Every time the slider for rotation speed is moved, the rotation animation needs to be restarted to apply the new animation duration.

Multi-monitor Support

The screensaver will handle multiple monitors present in your system in a very straightforward way: it will replicate the same content on each monitor. Should you notice that the clocks in these different instances are not always synchronized then yes, you're simply right. This visual side effect is caused by the fact that each clock instance uses its own timer, started only after the previous instance was created. The issue can possibly be addressed in some future update of the article.

Skinning Support

Skinning of BinaryClock screensaver is achieved utilizing WPF's ResourceDictionary concept. This allows us to change skins in runtime. Each "skin" is placed into a separate XAML file containing a ResourceDictionary, so that we can switch to use any of these XAML files in runtime. Each ResourceDictionary defines the same brush objects, but with different color settings. Thus, simply by loading a different ResourceDictionary in runtime, our binary clock colors will be automatically switched to the new skin (this is fully transparent to us, since we reference these brushes as DynamicResource objects).

Code snippet from the XAML file defining the "Blue" skin:

<ResourceDictionary ...>
    <!-- Unlit LED border color -->
    <SolidColorBrush x:Key="emptyRectBorderBrush" Color="#FF00087F" />
	...
</ResourceDictionary>

This is how the skin's elements are used by BinaryClock:

<!-- Basic appearance of all Digits -->
<Style TargetType="Rectangle" x:Key="bitsCommonStyle">
	<Setter Property="Stroke" Value="{DynamicResource emptyRectBorderBrush}" />
	...
</Style>

Future Enhancements

If you like this screensaver and have some proposals for improvements, please feel free to let me know, as I still plan to add some new features in the future. These may include new settings to adjust 3D effects, possibility to have a readable, decimal hint, but also bugfixes, etc. 

History 

  • 18 Jan 2009: Skinning support
  • 23 Dec 2008: Small bugfix for multi-monitor systems, based on a reader comment
  • 22 Dec 2008: Initial revision

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Attila Kúr
Software Developer
Slovakia Slovakia
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralA suggestionmembermbghtri9 Jan '09 - 5:00 
Thanks for the article.
I have a problem with nearly all the binary clocks that I've seen for sale, that this program falls into as well.
To me, it doesn't make any sense to use 6 columns of binary numbers to display the time. This is like taking a number, breaking it into base 10 chunks, and then again breaking it into base 2 chunks. There is an unnecessary extra step in there.
You really only have 3 data points to display: hours, minutes and seconds. Why not have just 3 columns? What is the point in breaking it out into 6? Is it just because a standard digital clock has up to 6 numbers in base 10? I don't see the point in keeping that affectation when moving to a binary clock. That is like putting a shutter click sound on a digital camera, or an exhaust pipe on an electric car.
 
Am I the only one that is bothered by this?
I'm not criticizing you directly, just the way most binary clocks are designed.
 
// mbghtri ToDo:
// Put Signature Here

GeneralRe: A suggestionmemberAttila Kúr9 Jan '09 - 6:23 
Thank you for the feedback.
 
I completely agree with you that these are not truly binary clocks.
Actually, they are BCD clocks, as stated in the article. And I think the design of breaking the time into 6 columns improves the readability for human beings, nothing else. After a time, as you get used to it, you do not need to add up the values like 1+2+4 in your head anymore, you rather just "see" that a "0111" is actually a "seven". And it's much more easier to visually remember the "look" of 10 digits than the look of 60 digits.
 
After all, we're all human - if binary clocks were not intended for people but for machines, then we wouldn't even need the 3 columns: to represent current time as pure data, the number of seconds elapsed since midnight would fully suffice, in a single, high column. Smile | :) But this is not the case with us, humans, is it?
GeneralRe: A suggestionmemberdelyk21 Jan '09 - 4:41 
While breaking up the digits bothers me a little too, mbghtri, I have some worries about using a 3 column metaphor as well.
 
It would require 6 bits to represent seconds and minutes in a single column and require the user to perform more difficult additions in order to "read" the number. Example: 32+16+8+2+1 and by the time the user has finally figured out the digits a significant amount of time has passed and that time is no longer valid.
 
The "chunks" as you call them allow a user to scan the readout and quickly determine if the hour or minute or second is >10 or less than 10. You can approximate the time much more quickly by ignoring the lesser chunks.
 
Shouldn't be too hard to add a switch and let the user decode the layout though, the same way as regular clocks let you choose between 12H and 24H modes.
GeneralRe: A suggestionmembermbghtri22 Jan '09 - 10:03 
Atilla,
Thank you for pointing out that in your article you describe this as a BCD clock. I missed that detail on the first read. D'Oh! | :doh:
 
delyk,
I like your suggestion to make it a user-configurable option.
For a single Hours column, there is only 1 higher bit, while there are 2 higher bits for the Minutes and Seconds column.
I've found that the extra two bits of precision take an extra couple of seconds to translate in my head, but a little practice soon makes it feel natural.
 
In fact, overall there are fewer bits to read when using just 3 columns. With 6 columns, there is a maximum of 20 total bits to read. With 3 columns, there are 17 bits max.
 
// mbghtri ToDo:
// Put Signature Here

GeneralMemory Leakmembers_reckers6 Jan '09 - 9:33 
After running the binary version of this app for about 5 minutes the memory usage was up to 1,29 GB for "BinaryClock.exe". Shortly after it crashed.
There seems to be a serious memory leak.
I'm running WinXP Pro SP3 with framework 3.5 SP1.
GeneralRe: Memory LeakmemberAttila Kúr6 Jan '09 - 10:02 
Thank you for the feedback; I will examine your problem as soon as possible.
GeneralRe: Memory LeakmemberAttila Kúr9 Jan '09 - 11:52 
I've done some tests now on two different computers with exactly the same configuration you described. I found in both cases that even after a much longer time, the memory usage was still about 20 MB. Sorry, but I can't see how your memory consumption could be so huge. Can you maybe provide some more info on this issue? (if it happens each and every time, even after a fresh start of Windows, etc.)
Examining the code in search for potential WPF memory leaks also showed no serious problems that could lead to crash due to insufficient memory; no objects are being repeatedly created again and again.
GeneralRe: Memory Leakmembers_reckers12 Jan '09 - 2:41 
I still have this problem on my machine. It must be something specific to my configuration that's causing the issue. When I find some time I'll grab the source and run it in debug mode to get to the bottom of this. I'll let you know if/when I find something, but it might be some time before I can get to it.
Thanks for looking into it though.
GeneralRe: Memory LeakmemberSpaceQ11 Feb '09 - 9:44 
I reproduced memory leak on windows 7 windows server 2008 sp1 .net 3.5 +vista sp1 when using
Viewport3D with sofware renderer and focusing textbox. When I run same code on my GPU accelerated renderer it doesn't leak. IMHO there is problem from WPF 3.5 sp1 software renderer when it transforms controls changes to 3D. Try Nvidia or AMD/ATI graphic card first.
GeneralGreat! [modified]membersk8er_boy28723 Dec '08 - 3:17 
I've never used WPF, but this is a great example of what it can do, and some info in the article is useful to know (i.e., how to display a preview of the screen saver).
 
EDIT: I think the screen saver window has a thick frame. Can you remove it?
 
modified on Tuesday, December 23, 2008 9:24 AM

GeneralRe: Great! [modified]memberAttila Kúr23 Dec '08 - 4:47 
Thank you and you're right: the simplest solution indeed is to remove that border from the main window. Download links are updated now.
 
modified on Tuesday, December 23, 2008 2:55 PM

GeneralRe: Great!memberRoberto Collina30 Dec '08 - 9:15 
I second the "great!". Outstanding example, thank you for sharing. Smile | :)
QuestionSource Code?memberjklucker22 Dec '08 - 9:13 
Is the source code available?
 
I reject your reality and substitute my own!
- Adam Savage, Mythbuster
-George W Bush
 
life is like a roll of toilet paper. The closer it gets to the end, the faster it goes.
 
My definition of an expert in any field is a person who knows enough about what's really going on to be scared.
- PJ Plauger

AnswerRe: Source Code?memberAttila Kúr22 Dec '08 - 9:58 
Yes, of course it's available, together with the binaries. It just took some minutes to zip them after publishing the article, sorry.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130523.1 | Last Updated 20 Jan 2009
Article Copyright 2008 by Attila Kúr
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid