HOWTO: A clock control using XAML and Silverlight

Mark Birbeck's picture

As with our simple map control, our first step is to decide how authors can make use of our control. A typical usage might be:

<xf:output value="'10:10:10'" appearance="xaml:analogue-clock" style="width:150px;height:150px;" />

This will create a static clock, 150px by 150px, showing a time of 12:10:10. We'll also ensure that the control can cope with full time and date formats, such as:

<xf:output value="'20010604T112000Z'" appearance="xaml:analogue-clock" style="width:150px;height:150px;" />

Now we know how authors will use the control, we can go ahead and build it. Once we're finished, it will look something like this:

Silverlight clock as a custom control in XForms/formsPlayer

The XAML definition of the clock

The definition we'll use for the visual side of the clock is pretty much the same as in the example we saw in the last section. The main change is that we don't need the animations that move the clock hands, since we'll be setting the hands based on the bound data. There is a fourth animation though, which fades in the clock, and we'll keep that.

We won't go into XAML in detail, but one point that needs drawing out is that for each attribute we want to modify, we need to provide a unique name for the element that contains that attribute. In the case of the clock, this has already been done, but were we creating the XAML from scratch we would need to follow this rule. To understand why we need to do this let's look at how the minute hand is defined:

  <Path Data="M -4, 16 l 3 70 3 0 2 -70 z" Fill="white">
    <Path.RenderTransform>
      <TransformGroup>
        <RotateTransform x:Name="minuteHandTransform" Angle="180"/>
        <TranslateTransform X="150.5" Y="145"/>
      </TransformGroup>
    </Path.RenderTransform>
  </Path>

The actual shape of the hand is defined by the Path element, but its orientation is defined by the Angle attribute on the RotateTransform element. So that we can write code to gain access to the attribute, we need to be able to locate the element, and to do this we use the XAML x:Name attribute, which uniquely names the element. Once we've located the attribute, setting it to some value--based on the time value fed into the control--will automatically update the display of the minute hand.

Managing the interaction with the XAML object

Our custom control will play the role of interacting with a XAML object on behalf of the backplane. (We'll show how to indicate which XAML file to use, below.) Since much of what is involved in doing this will be common to any control we develop that uses XAML, we have taken the functionality out into a common base class:

<?xml version="1.0"?>
<bindings xmlns="http://www.mozilla.org/xbl">
  <binding extends="http://svn.x-port.net/svn/public/samples/chrome/com.microsoft.xaml/xf-pe-value.xbl">
    ...
  </binding>
</bindings>

Defining properties to hide interfaces

We saw earlier that positioning the hands of the clock requires setting the Angle attribute on each hand. Whilst we could use code to find this attribute each time we need it, this would make our code very specific, since the method for finding attributes in XAML is very different say, to SVG. Our custom control architecture provides a means to map properties in the XAML object to properties on the custom control. The mapping is handled in the XAML-specific code that we are extending, and all we need to do is specify the relationships:

  <binding extends="http://svn.x-port.net/svn/public/samples/chrome/com.microsoft.xaml/xf-pe-value.xbl">
    <implementation>
      <property name="hourOrientation" element="hourHandTransform" attribute="angle" />
      <property name="minuteOrientation" element="minuteHandTransform" attribute="angle" />
      <property name="secondOrientation" element="secondHandTransform" attribute="angle" />

With these mappings in place, we can now write code like this:

this.hourOrientation = 30;

which will have the effect of setting hourHandTransform.angle.

Specifying the XAML object to load

The next part of our custom control indicates the XAML file to load:

      <property name="secondOrientation" element="secondHandTransform" attribute="angle" />
      <constructor>
        this.firstChild.source = "analogue-clock.xaml";
      </constructor>

Finally, as with our map control, we need to provide an implementation for the setValue method, which will need to crack open the data input, and then use the hours, minutes and seconds to set each of the hands on the clock. First we declare the method and its parameter:

      </constructor>
      <method name="setValue">
        <parameter name="newVal" />
        <body>
          ...
        </body>
      </method>
    </implementation>
  </binding>
</bindings>

Next we parse the input value in order to get the hours, minutes and seconds:

        <body>
          try
          {
            String(newVal).match( /((\d{4})(\d{2})(\d{2})T)?(\d{2})[\:]?(\d{2})[\:]?(\d{2})[Z]?/ );

            var hours = parseInt(RegExp.$5, 10);
            var minutes = parseInt(RegExp.$6, 10);
            var seconds = parseInt(RegExp.$7, 10);

Now we can work out the angle of each hand. Each hour is 30 degrees, although the display will look better if we also add a sixtieth of that for each minute. Once we have the angle we can use the this.hourOrientation to set the correct attribute in the XAML object:

            var seconds = parseInt(RegExp.$7, 10);

            var angle = (hours / 12) * 360 + minutes/2;

            angle += 180;
            this.hourOrientation = angle.toString();

Finally, we can do the same thing for the minutes and seconds:

            this.hourOrientation = angle.toString();

            angle = (minutes / 60) * 360;
            angle += 180;
            this.minuteOrientation = angle.toString();

            angle = (seconds / 60) * 360;
            angle += 180;
            this.secondOrientation = angle.toString();
          }
          catch(e)
          {
            // some error handling
          }
       </body>

Using our custom control

All we need to do now is to create a bindings file:

<?xml version="1.0" encoding="UTF-16"?>
<br:bindings
 xmlns:br="http://www.x-port.net/bindingresolver/"
 xmlns:xf="http://www.w3.org/2002/xforms"
 xmlns:xaml="http://schemas.microsoft.com/winfx/2006/xaml"
>
  <br:binding match="xf:output[@appearance='xaml:analogue-clock']/xf:pe--value" binding="analogue-clock.xbl" />
</br:bindings>

Assuming that we call this file my-bindings.xml then we can now use the binding rules in the normal way:

  <head>
    <title>Silverlight Clock</title>
    <link rel="bindings" href="my-bindings.xml" />
    .
    .
    .
  </head>
  <body>
    <xf:output ref="clock" appearance="xaml:analogue-clock" class="clock" />
  </body>

If you have a recent version of formsPlayer installed, as well as a version of Microsoft's Silverlight then you can run this demonstration directly. If you'd like to obtain the source files to edit locally, they are available at http://svn.x-port.net/svn/public/samples/custom-control-tutorial/silverlight-clock/.