Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

LinearGradientBrushAnimation

0.00/5 (No votes)
14 Mar 2010 1  
Creating and Using a LinearGradientBrushAnimation

Introduction

The LinearGradientBrushAnimation is similar to the ColorAnimation in System.Windows.Media.Automation but it works with LinearGradientBrushes.

Background

I wanted to restyle the button-class and planned to animate the background when I move the mouse-cursor over it. For that, I have to animate the background brush. I found samples for ColorAnimation, but I used a LinearGradientBrush as a background brush. I found something in the web to do it by code, but it was my plan to add this functionality directly in XAML.

LinearGradientBrushAnimationBase

First, I had a look with the Reflector tool and started with the class LinearGradientBrushAnimationBase which is derived from AnimationTimeline. I changed all Color types into LinearGradientBrush types. After that, it looked like this:

public abstract class LinearGradientBrushAnimationBase : AnimationTimeline
{
    protected LinearGradientBrushAnimationBase()
    {
    }

    public new LinearGradientBrushAnimationBase Clone()
    {
        return (LinearGradientBrushAnimationBase)base.Clone();
    }

    public sealed override object GetCurrentValue(object defaultOriginValue, 
                  object defaultDestinationValue, AnimationClock animationClock)
    {
        if (defaultOriginValue == null)
        {
            throw new ArgumentNullException("defaultOriginValue");
        }
        if (defaultDestinationValue == null)
        {
            throw new ArgumentNullException("defaultDestinationValue");
        }
        return this.GetCurrentValue((LinearGradientBrush)defaultOriginValue, 
              (LinearGradientBrush)defaultDestinationValue, animationClock);
    }

    public LinearGradientBrush GetCurrentValue(LinearGradientBrush 
           defaultOriginValue, LinearGradientBrush defaultDestinationValue, 
           AnimationClock animationClock)
    {
        base.ReadPreamble();
        if (animationClock == null)
        {
            throw new ArgumentNullException("animationClock");
        }
        if (animationClock.CurrentState == ClockState.Stopped)
        {
            return defaultDestinationValue;
        }
        return this.GetCurrentValueCore(defaultOriginValue, 
               defaultDestinationValue, animationClock);
    }

    protected abstract LinearGradientBrush GetCurrentValueCore(LinearGradientBrush 
              defaultOriginValue, LinearGradientBrush defaultDestinationValue, 
              AnimationClock animationClock);

    // Properties
    public override Type TargetPropertyType
    {
        get { return typeof(LinearGradientBrush); }
    }
}

LinearGradientBrushAnimation

The next step is to add a LinearGradiantBrushAnimation class which is derived from the base class. The complete class looks like this:

public class LinearGradientBrushAnimation : LinearGradientBrushAnimationBase
{
    public static readonly DependencyProperty FromProperty;
    public static readonly DependencyProperty ToProperty;

    // Methods
    static LinearGradientBrushAnimation()
    {
        Type propertyType = typeof(LinearGradientBrush);
        Type ownerType = typeof(LinearGradientBrushAnimation);
        PropertyChangedCallback propertyChangedCallback = 
          new PropertyChangedCallback(
          LinearGradientBrushAnimation.AnimationFunction_Changed);
        ValidateValueCallback validateValueCallback = 
          new ValidateValueCallback(LinearGradientBrushAnimation.ValidateValues);
        FromProperty = DependencyProperty.Register("From", propertyType, 
                       ownerType, new PropertyMetadata(null, propertyChangedCallback), 
                       validateValueCallback);
        ToProperty = DependencyProperty.Register("To", propertyType, 
                     ownerType, new PropertyMetadata(null, propertyChangedCallback), 
                     validateValueCallback);
    }

    private static bool ValidateValues(object value)
    {
        return true;
    }

    private static void AnimationFunction_Changed(DependencyObject d, 
                        DependencyPropertyChangedEventArgs e)
    {
        LinearGradientBrushAnimation animation = (LinearGradientBrushAnimation)d;
        //animation.PropertyChanged(e.Property);
    }

    public LinearGradientBrushAnimation()
    {
    }

    public LinearGradientBrushAnimation(LinearGradientBrush fromValue, 
           LinearGradientBrush toValue, Duration duration)
        : this()
    {
        this.From = fromValue;
        this.To = toValue;
        base.Duration = duration;
    }

    protected override LinearGradientBrush GetCurrentValueCore(LinearGradientBrush 
              defaultOriginValue, LinearGradientBrush defaultDestinationValue, 
              AnimationClock animationClock)
    {
        // check for length of from and to
        if (From.GradientStops.Count != To.GradientStops.Count)
            return From;

        if (animationClock.CurrentProgress == null)
            return From;

        LinearGradientBrush brush = new LinearGradientBrush();
        brush.StartPoint = From.StartPoint + ((To.StartPoint - From.StartPoint) * 
                          (double)animationClock.CurrentProgress);
        brush.EndPoint = From.EndPoint + ((To.EndPoint - From.EndPoint) * 
                        (double)animationClock.CurrentProgress);

        // calc gradientstops
        for (int cnt = 0; cnt < From.GradientStops.Count; cnt++)
        {
            GradientStop stop1 = From.GradientStops[cnt];
            GradientStop stop2 = To.GradientStops[cnt];
            
            // calc color
            Color color1 = stop1.Color;
            Color color2 = stop2.Color;
            Color newColor = Color.Subtract(color2, color1);
            newColor = Color.Multiply(newColor, 
                            (float)animationClock.CurrentProgress);
            newColor = Color.Add(newColor, color1);

            // calc offset
            double offset1 = (double)stop1.Offset;
            double offset2 = (double)stop2.Offset;
            double offset = offset1 + ((offset2 - offset1) * 
                           (double)animationClock.CurrentProgress);
            
            brush.GradientStops.Add(new GradientStop(newColor, offset));
        }
        return brush;
    }

    protected override Freezable CreateInstanceCore()
    {
        return new LinearGradientBrushAnimation();
    }

    // Properties
    public LinearGradientBrush From
    {
        get { return (LinearGradientBrush)base.GetValue(FromProperty); }
        set { base.SetValue(FromProperty, value); }
    }

    public LinearGradientBrush To
    {
        get { return (LinearGradientBrush)base.GetValue(ToProperty); }
        set { base.SetValue(ToProperty, value); }
    }
}

The most important method in this class is GetCurrentValueCore(). This class returns the animated brush depending on the animationClock.CurrentProgress value. This value is the position in the animation, and is between 0.0 and 1.0 as double. As you can see, I animate not just the Color values. All the other properties of the LinearGradientBrush (StartPoint, EndPoint, Offset) are animated too.

Using the Code

Now it's time to test the new class. In this sample, I create a new label in XAML and add two triggers. The first one is for MouseEnter and the second one is for the MouseLeave event. The From and To properties are the LinearGradiantBrushes that have to be animated. The duration is the time the animation takes, in this sample 0.3 seconds. As you can see, the LinearGradiantBrushAnimation works like ColorAnimation directly in XAML, and is very easy to use. You can put the LinearGradiantBrush directly into the code, or in a ResourceDictionary as in the sample.

<Label Content="Label 1" Margin="8" 
  HorizontalContentAlignment="Center" 
  VerticalContentAlignment="Center" 
  Height="121" Width="131" 
  Background="{StaticResource ButtonNormalBackground1}" 
  Foreground="White">
    <Label.Triggers>
        <EventTrigger RoutedEvent="Button.MouseEnter">
            <BeginStoryboard>
                <Storyboard>
                    <res:LinearGradiantBrushAnimation 
                       Storyboard.TargetProperty="Background"
                       Duration="00:00:0.3"
                       AutoReverse="False"
                       From="{StaticResource ButtonNormalBackground1}"
                       To="{StaticResource ButtonNormalBackgroundHover1}" />
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
        <EventTrigger RoutedEvent="Button.MouseLeave">
            <BeginStoryboard>
                <Storyboard>
                    <res:LinearGradiantBrushAnimation 
                       Storyboard.TargetProperty="Background"
                       Duration="00:00:0.3"
                       AutoReverse="False"
                       From="{StaticResource ButtonNormalBackgroundHover1}"
                       To="{StaticResource ButtonNormalBackground1}" />
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </Label.Triggers>
</Label>

In the completed sample are four Labels with different animations.

History

  • 14th March, 2010: Initial post

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here