Click here to Skip to main content
15,867,308 members
Articles / Desktop Programming / WPF

LinearGradientBrushAnimation

Rate me:
Please Sign up or sign in to vote.
4.78/5 (8 votes)
14 Mar 2010CPOL2 min read 29.4K   532   11   3
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:

C#
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:

C#
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.

XML
<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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer S&I Spectroscopy and Imaging
Germany Germany
Hi, my name is Juergen Schildmann. Since 2007 I have been working as a developer focusing on building applications for physical instruments. Before that I worked as an electrical technician in the cement industry.

Comments and Discussions

 
Questionusing in code Pin
Member 99780799-Oct-13 23:07
Member 99780799-Oct-13 23:07 
QuestionCustom Generic Animations Pin
Yury Goltsman27-Feb-12 3:12
Yury Goltsman27-Feb-12 3:12 
Thank you for the article.
I also wrote animations, but as generic base classes to allow in 10 minutes create animation for a new type. I used your idea and implemented animations for Linear and Radial Gradient brushes.
Welcome to "WPF: Custom Generic Animations"[^]
GeneralMy vote of 5 Pin
SledgeHammer0122-Nov-10 13:35
SledgeHammer0122-Nov-10 13:35 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.