Saturday, September 18, 2010

WPF Circular Progress Control – Part 2

In the previous post (WPF Circular Progress Control – Part 1) I tried to explain about drawing a circular progress control.
In this post we will see how to animate the control so that we get the rotating behavior. Ohh there goes we need animation to rotate the control.
Before we apply the animation, we apply the rotate transform to the path we created earlier, RotateTransform rotates an object by a specified angle. We apply RotateTransform to the RenderTransform of path control, Render transforms are typically intended for animating or applying a temporary effect to an element. 
<Path Stretch="Fill" Name="pathCircular">
      <Path.Data>
            <GeometryGroup>
                  <EllipseGeometry Center="20,20" RadiusX="20" RadiusY="20"/>
                  <EllipseGeometry Center="20,20" RadiusX="15" RadiusY="15"/>
            </GeometryGroup>
      </Path.Data>
      <Path.RenderTransform>
            <TransformGroup>
                  <RotateTransform Angle="0"/>
            </TransformGroup>
      </Path.RenderTransform>
      <Path.Fill>
            <LinearGradientBrush StartPoint="0,0.3" EndPoint="0.3,1">
<GradientStop x:Name="GradientStop1" Color="Black" Offset="0"/>
<GradientStop x:Name="GradientStop2" Color="Transparent" Offset="1"/>
            </LinearGradientBrush>
      </Path.Fill>
</Path>

Now we need to define the animation, we need to animation on ‘pathCircular’ object and its ‘RotateTransform’ property.
We define a DoubleAnimation, which updates the value of a property over a period of time. But this would mean that we will be able to specify the value of the property only once, however what we need is to be able set the value of the property twice after certain time interval. For this we need to define DoubleAnimationUsingKeyFrames.
By using DoubleAnimationUsingKeyFrames not only can we have more than two of target values but we can also control the interpolation method of individual DoubleKeyFrame segments. There are three types of DoubleKeyFrame classes, one for each supported interpolation method: LinearDoubleKeyFrame, DiscreteDoubleKeyFrame, andSplineDoubleKeyFrame.
We use  SplineDoubleKeyFrame, Spline key frames like SplineDoubleKeyFrame create a variable transition between values according to the value of the KeySpline property. Each key frame has a target Value and a KeyTime. The KeyTimespecifies the time at which the key frame's Value should be reached. A key frame animates from the target value of the previous key frame to its own target value. It starts when the previous key frame ends and ends when its own key time is reached.
<Storyboard x:Key="rotateAnimation">
      <DoubleAnimationUsingKeyFrames
            BeginTime="00:00:00"
            Storyboard.TargetName="pathCircular"
            Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(RotateTransform.Angle)"
            RepeatBehavior="Forever">
            <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
            <SplineDoubleKeyFrame KeyTime="00:00:00.5" Value="360"/>
      </DoubleAnimationUsingKeyFrames>
</Storyboard>

OK the animation is implemented and applied to the control, now we need the events to start and stop the animation. Here we can define a routed event and in XAML we can define trigger to begin and stop the animation.
<UserControl.Triggers>
      <EventTrigger RoutedEvent="local:CircularProgressControl.StopEvent">
            <StopStoryboard BeginStoryboardName="rotateAnimation"/>
      </EventTrigger>
      <EventTrigger RoutedEvent="local:CircularProgressControl.StartEvent">
<BeginStoryboard Name="rotateAnimation" Storyboard="{StaticResource rotateAnimation}"/>
      </EventTrigger>
</UserControl.Triggers>

Now we need to define these events in the code behind.
StartEvent =
    EventManager.RegisterRoutedEvent (
    "StartEvent",
    RoutingStrategy.Direct,
    typeof (RoutedEventHandler),
    typeof (CircularProgressControl));
StopEvent =
    EventManager.RegisterRoutedEvent (
    "StopEvent",
    RoutingStrategy.Direct,
    typeof (RoutedEventHandler),
    typeof (CircularProgressControl));
 
What we can also do is define a custom dependency property , on update of which we can raise start / stop animation events.
/// <summary>
/// Gets or sets a value indicating whether this instance is in progress.
/// </summary>
/// <value>
///   <c>true</c> if this instance is in progress; otherwise, <c>false</c>.
/// </value>
public bool IsInProgress {
    get { return (bool)GetValue (IsInProgressProperty); }
    set { SetValue (IsInProgressProperty, value); }
}

/// <summary>
/// Using a DependencyProperty as the backing store for IsInProgress.
/// This enables animation, styling, binding, etc...
/// </summary>
public static readonly DependencyProperty IsInProgressProperty =
    DependencyProperty.Register (
        "IsInProgress",
        typeof (bool),
        typeof (CircularProgressControl),
        new UIPropertyMetadata (
            false, new PropertyChangedCallback (OnIsInProgressChanged)));

/// <summary>
/// Called when IsInProgress is changed.
/// </summary>
/// <param name="inSender">The sender.</param>
/// <param name="inEventArgs">The
/// <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance
/// containing the event data.</param>
private static void OnIsInProgressChanged (
    DependencyObject inSender, DependencyPropertyChangedEventArgs inEventArgs) {
    bool newValue = (bool)inEventArgs.NewValue;
    if (newValue) {
        (inSender as CircularProgressControl).RaiseEvent (new RoutedEventArgs (StartEvent));
    }
    else {
        (inSender as CircularProgressControl).RaiseEvent (new RoutedEventArgs (StopEvent));
    }
}

There, we have the control. Now we can use it in other views, bind IsInProgress (Boolean) property with presenter property and it’s done.

Cheers

No comments:

Post a Comment