WPF: DataBinding to Calculated Values//The IValueConverter interface

Posted on 3/5/2007 @ 12:46 AM in #Silverlight and WPF by | Feedback | 9673 views

Okay consider a simple simple problem.

I am trying to write a "Clock". I want to make it simple and seamless enough that you simply DataBind this control to a DateTime variable, and it should display the time. No, not a digital clock, but one with arms and legs. Okay just arms - an Hour arm, and a Minute Arm.

This sounds simple enough, seems like all I have to do for the hour arm is, set a RotateTransform to time.Hour * 30 degrees, right? (30 = 360/12).

So this syntax should work, right?

<RotateTransform Angle="{Binding Path=Hour*30}">

Bad news - that won't work! You cannot DataBind to a calculated value.

Okay, you can't DataBind like the above, but you can acheive what you are trying to by implementing a "Conversion" using the IValueConverter interface.

What the IValueConverter interface lets you do is, it lets you convert back and forth between a DataBound value, and what the real value is. So, an "Hour" value of 2, should translate to 60 degrees, and vice versa. This is a perfect job for a IValueConverter class.

So, go ahead and implement two IValueConverters, one for Minute and one for Hour as shown below -

public class HourToAngle : IValueConverter

{

    #region IValueConverter Members

 

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)

    {

        DateTime time = System.Convert.ToDateTime(value) ;

        double Angle = time.Hour * 30;

        Angle += 12 * time.Minute / 60;

        return Angle;

    }

 

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)

    {

        // Not required.

        return null;

    }

 

    #endregion

}

 

public class MinuteToAngle : IValueConverter

{

    #region IValueConverter Members

 

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)

    {

        DateTime time = System.Convert.ToDateTime(value);

        double Angle = time.Minute * 6;

        return Angle;

    }

 

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)

    {

        // Not required;

        return null;

    }

 

    #endregion

}

Add the above converters to the front end XAML as resources -

<Grid.Resources>

  <custom:HourToAngle x:Key="hourToAngle"/>

  <custom:MinuteToAngle x:Key="minuteToAngle"/>

</Grid.Resources>

 

("custom" is a namespace I referenced in my XAML)

and then go ahead and implement the Binding as shown below -

<Rectangle x:Name="HourHand" Canvas.Top="21" Canvas.Left="48" Fill="Black" Width="4" Height="30">

  <Rectangle.RenderTransform>

    <RotateTransform x:Name="HourHand2" CenterX="2" CenterY="30" Angle="{Binding Mode=OneWay, Converter={StaticResource hourToAngle}}">

    </RotateTransform>

  </Rectangle.RenderTransform> 

</Rectangle>

 

<Rectangle x:Name="MinuteHand" Canvas.Top="6" Canvas.Left="49" Fill="Black" Width="2" Height="45">

  <Rectangle.RenderTransform>

    <RotateTransform CenterX="1" CenterY="45" Angle="{Binding Mode=OneWay, Converter={StaticResource minuteToAngle}}">

    </RotateTransform>

  </Rectangle.RenderTransform>

</Rectangle>

 

The above is an extract out of a Clock Template I adopted from an article I saw on MSDN. For the full source of the Clock Template, please see here.

Now assuming that the ClockTemplate is defined somewhere in your XAML, you can use the template like this -

<Grid.Resources>

  <ObjectDataProvider x:Key="dateTime" ObjectType="{x:Type s:DateTime}"/>

</Grid.Resources>

<Control Template="{StaticResource clockTemplate}"

         Width="120" Height="108"

         DataContext="{Binding Path=Now, Source={StaticResource dateTime}}">

</Control>

</Grid>

 

This will now produce a nice looking clock like this -

        Mission Accomplished!!

Sound off but keep it civil:

Older comments..


On 3/15/2007 5:13:24 AM Jobi said ..
A small suggestion to use a Single converter instead of two. We can create a TimeToAngle class which can return all the Angle for Minute,Seocnd and Hour so that you can databind the object to the entire clock. I have implemented something like that.


On 3/15/2007 6:55:11 PM Sahil Malik said ..
Jobi,

Yeah a single converter can do that. I just wanted to get it done, blog about it, and catch some sleep. But yes I agree, a single class can make this code cleaner.

SM