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);
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;
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;
}
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)
{
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);
for (int cnt = 0; cnt < From.GradientStops.Count; cnt++)
{
GradientStop stop1 = From.GradientStops[cnt];
GradientStop stop2 = To.GradientStops[cnt];
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);
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();
}
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
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.