Vishal Vishal - 2 months ago 8
C# Question

Synchronize Minutes Hand With Second Hand as well as Synchronize Hours Hand With Minute Hand

I am learning animations in WPF and so, I decided to create an analogue clock.

Please take a look at my code so that I can explain my problem clearly :

<Window x:Class="Animation.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:PresentationOptions="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:self="clr-namespace:Animation"
Title="MainWindow" Height="350" Width="525">

<Window.DataContext>
<self:MainWindowViewModel />
</Window.DataContext>

<Window.Resources>
<self:SecondsToAngleConverter x:Key="secondsToAngleConverter" />
<self:HoursToAngleConverter x:Key="hoursToAngleConverter" />
</Window.Resources>

<Canvas>
<Ellipse Height="150" Width="150" Fill="Orange" />
<TextBlock Visibility="Collapsed" Text="{Binding DataContext.Second, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, Converter={StaticResource secondsToAngleConverter}}" Width="160" Canvas.Left="10" Canvas.Top="190" />
<Line x:Name="secondHand" X1="75" Y1="75" X2="75" Y2="10" Stroke="Red" RenderTransformOrigin="1,1">
<Line.RenderTransform>
<RotateTransform x:Name="secondHandRotateTransform"/>
</Line.RenderTransform>
<Line.Triggers>
<EventTrigger RoutedEvent="Line.Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="secondHandRotateTransform" Storyboard.TargetProperty="Angle"
To="{Binding DataContext.Second, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, Converter={StaticResource secondsToAngleConverter}}"
Duration="0:0:1"/>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="secondHandRotateTransform" Storyboard.TargetProperty="Angle" Duration="0:0:1" RepeatBehavior="Forever" IsCumulative="True">
<DiscreteDoubleKeyFrame Value="6" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Line.Triggers>
</Line>
<Line X1="75" Y1="75" X2="75" Y2="10" Stroke="Black" StrokeThickness="3" RenderTransformOrigin="1,1">
<Line.RenderTransform>
<RotateTransform x:Name="minuteHandRotateTransform"/>
</Line.RenderTransform>
<Line.Triggers>
<EventTrigger RoutedEvent="Line.Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="minuteHandRotateTransform" Storyboard.TargetProperty="Angle"
To="{Binding DataContext.Minute, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, Converter={StaticResource secondsToAngleConverter}}"
Duration="0:0:1"/>
<DoubleAnimation Storyboard.TargetName="minuteHandRotateTransform" Storyboard.TargetProperty="Angle"
IsCumulative="True" By="6" RepeatBehavior="Forever"
Duration="0:1:0"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Line.Triggers>
</Line>
<Line X1="75" Y1="75" X2="75" Y2="25" Stroke="Black" StrokeThickness="3" RenderTransformOrigin="1,1">
<Line.RenderTransform>
<RotateTransform x:Name="hourHandRotateTransform"/>
</Line.RenderTransform>
<Line.Triggers>
<EventTrigger RoutedEvent="Line.Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="hourHandRotateTransform" Storyboard.TargetProperty="Angle"
To="{Binding DataContext.Hour, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, Converter={StaticResource hoursToAngleConverter}}"
Duration="0:0:1"/>
<DoubleAnimation Storyboard.TargetName="hourHandRotateTransform" Storyboard.TargetProperty="Angle"
IsCumulative="True" By="30" RepeatBehavior="Forever"
Duration="1:0:0"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Line.Triggers>
</Line>
</Canvas>
</Window>


The ViewModel code :

public class MainWindowViewModel
{
public MainWindowViewModel()
{
Hour = DateTime.Now.Hour;
Minute = DateTime.Now.Minute;
Second = DateTime.Now.Second;
}

public static int Hour { get; set; }
public static int Minute { get; set; }
public static int Second { get; set; }

}


Code for converters :

SecondsToAngleConverter.cs
:

public class SecondsToAngleConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null || value == DependencyProperty.UnsetValue)
{
return (6 * DateTime.Now.Second);
}
else
{
return (6 * (int)value);
}
}

public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}


HoursToAngleConverter.cs
:

public class HoursToAngleConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null || value == DependencyProperty.UnsetValue)
{
if (DateTime.Now.Hour > 12)
{
return (30 * (DateTime.Now.Hour - 12));
}
else
{
return (30 * DateTime.Now.Hour);
}
}
else
{
if ((int)value > 12)
{
return (30 * ((int)value - 12));
}
else
{
return (30 * (int)value);
}
}
}

public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}


Scenario :


  1. Seconds, minutes & hours hands are animated for 1 second to set them to the correct
    DateTime.Now
    and then these animations stops.

  2. The animations for animating second hand, minute hand and hour hand begins now.

  3. These animations do not depend on each other.



Problems :

When program starts


  1. Animation for minute hand starts which is given a duration of 1 minute to rotate 6 degrees on its right

  2. If
    DateTime.Now.Second
    returns a value greater than 0, then second hand will need less than 60 seconds to reach 12 on the clock. But at the same time the animation clock of minute hand will not have reached the 1 minute, so there will be some difference in the position of the minute hand than a physical clock.



Similarly


  1. Animation for hour hand starts which is given a duration of 1 hour to rotate 30 degrees on its right

  2. If
    DateTime.Now.Minute
    returns a value greater than 0, then minute hand will need less than 60 minutes to reach 12 on the clock. But at the same time the animation clock of hour hand will not have reached the 1 hour, so There will be some difference in the position of hour hand than a physical clock.


Answer

I have to use two more doubleAnimations with two more Converters. The code is as below:

From the above code I need to add a double animation to 2nd storyboard as follows:

<DoubleAnimation Storyboard.TargetName="minuteHandRotateTransform" Storyboard.TargetProperty="Angle"
                 By="{Binding DataContext.Second, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, Converter={StaticResource synchronizeMinuteHandConverter}}"
                 Duration="0:0:1" />

Similarly for 3rd storyboard :

<DoubleAnimation Storyboard.TargetName="hourHandRotateTransform" Storyboard.TargetProperty="Angle"
                 By="{Binding DataContext.Minute, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, Converter={StaticResource synchronizeHourHandConverter}}" 
                 Duration="0:0:1"/>

SynchronizeMinuteHandConverter.cs :

public class SynchronizeMinuteHandConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (value == null || value == DependencyProperty.UnsetValue)
        {
            return (DateTime.Now.Second / 10);
        }
        else
        {
            if ((int)value > 59)
            {
                value = (int)value - 60;
            }

            return ((int)value / 10);
        }
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

SynchronizeHourHandConverter.cs

public class SynchronizeHourHandConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (value == null || value == DependencyProperty.UnsetValue)
        {
            return (DateTime.Now.Minute / 2);
        }
        else
        {
            if ((int)value > 59)
            {
                value = (int)value - 60;
            }

            return ((int)value / 2);
        }
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}