Introduction
The LinearGradientBrushAnimation
is similar to the ColorAnimation
in System.Windows.Media.Automation
but it works with LinearGradientBrush
es.
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 LinearGradiantBrush
es 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 Label
s with different animations.
History
- 14th March, 2010: Initial post