24

I managed to find out how to make a WPF animation - transition between two colors.

It's called ColorAnimation and works well.

ColorAnimation animation = new ColorAnimation
{
    From = Colors.DarkGreen,
    To = Colors.Transparent,
    Duration = new Duration(TimeSpan.FromSeconds(1.5)),
    AutoReverse = false
};
animation.Completed += new EventHandler(animation_Completed);
SolidColorBrush brush = new SolidColorBrush(Colors.Transparent);
animation.AccelerationRatio = 0.5;

Background = brush;
brush.BeginAnimation(SolidColorBrush.ColorProperty, animation);

I am using this to animate background of my usercontrol. My controls background is SolidColorBrush. Recently I changed to LinearGradientBrush. Now I can use my animation no more.

I need animation from brush to brush, not color to color. And best option is Abstract brush type, which includes SolidColor, LinearGradient etc, so I can animate for example from SolidColorBrush to LinearGradientBrush. Is that even possible? Thank you.

Gabriel
  • 377
  • 1
  • 3
  • 15

3 Answers3

39

Another possible way is, to create a custom animtion class that animate brushes. I found a simple way to do that by creating a class, derivated from AnimationTimeline. We can override some members in the custom class, among other things the AnimationTimeline.GetCurrentValue method. It returns a value depend on the animation progress and the start- and end value.

The simplest way is to create a VisualBrush and crossfade the start- with the end value with the Opacity property on a child control. The result is a class like the following:

public class BrushAnimation : AnimationTimeline
{
    public override Type TargetPropertyType
    {
        get
        {
            return typeof(Brush);
        }
    }

    public override object GetCurrentValue(object defaultOriginValue,
                                           object defaultDestinationValue,
                                           AnimationClock animationClock)
    {
        return GetCurrentValue(defaultOriginValue as Brush,
                               defaultDestinationValue as Brush,
                               animationClock);
    }
    public object GetCurrentValue(Brush defaultOriginValue,
                                  Brush defaultDestinationValue,
                                  AnimationClock animationClock)
    {
        if (!animationClock.CurrentProgress.HasValue)
            return Brushes.Transparent;

        //use the standard values if From and To are not set 
        //(it is the value of the given property)
        defaultOriginValue = this.From ?? defaultOriginValue;
        defaultDestinationValue = this.To ?? defaultDestinationValue;

        if (animationClock.CurrentProgress.Value == 0)
            return defaultOriginValue;
        if (animationClock.CurrentProgress.Value == 1)
            return defaultDestinationValue;

        return new VisualBrush(new Border()
        {
            Width = 1,
            Height = 1,
            Background = defaultOriginValue,
            Child = new Border()
            {
                Background = defaultDestinationValue,
                Opacity = animationClock.CurrentProgress.Value,
            }
        });
    }

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

    //we must define From and To, AnimationTimeline does not have this properties
    public Brush From
    {
        get { return (Brush)GetValue(FromProperty); }
        set { SetValue(FromProperty, value); }
    }
    public Brush To
    {
        get { return (Brush)GetValue(ToProperty); }
        set { SetValue(ToProperty, value); }
    }

    public static readonly DependencyProperty FromProperty =
        DependencyProperty.Register("From", typeof(Brush), typeof(BrushAnimation));
    public static readonly DependencyProperty ToProperty =
        DependencyProperty.Register("To", typeof(Brush), typeof(BrushAnimation));
}

You can use it as always in XAML:

<EventTrigger RoutedEvent="Loaded">
    <BeginStoryboard>
        <Storyboard >
            <local:BrushAnimation Storyboard.TargetName="border"
                                  Storyboard.TargetProperty="Background" 
                                  Duration="0:0:5" From="Red" 
                                  RepeatBehavior="Forever" AutoReverse="True" >
                <local:BrushAnimation.To>
                    <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                        <GradientStop Color="#FF00FF2E" Offset="0.005"/>
                        <GradientStop Color="#FFC5FF00" Offset="1"/>
                        <GradientStop Color="Blue" Offset="0.43"/>
                    </LinearGradientBrush>
                </local:BrushAnimation.To>
            </local:BrushAnimation>
        </Storyboard>
    </BeginStoryboard>
</EventTrigger>

or in code behind:

var animation = new BrushAnimation
{
    From = Brushes.Red,
    To = new LinearGradientBrush (Colors.Green, Colors.Yellow, 45),
    Duration = new Duration(TimeSpan.FromSeconds(5)),
};
animation.Completed += new EventHandler(animation_Completed);
Storyboard.SetTarget(animation, border);
Storyboard.SetTargetProperty(animation, new PropertyPath("Background"));

var sb = new Storyboard();
sb.Children.Add(animation);
sb.Begin();

It is also possible to extend the BrushAnimation with constructor overloads etc., so it looks like a .NET given animation type.

Koopakiller
  • 2,838
  • 3
  • 32
  • 47
  • I like the look of this - I will give it a spin. – Will Apr 15 '15 at 23:44
  • Two years later, this is brilliant. – Bob C Mar 07 '17 at 17:56
  • 2
    Two years later, plus a bit, I found that the `new VisualBrush` created each time in `GetCurrentValue` does indeed cause a memory leak (I blame .NET, not your code!). I modified the class to maintain a single VisualBrush (call it "WorkingBrush"), created like you have there, but then just changing the Opacity as follows: `((WorkingBrush.Visual as Border).Child as Border).Opacity = _clock.CurrentProgress.Value;` – Bob C Jun 08 '17 at 13:56
  • Should be marked as the answer; this is a perfect solution – High Plains Grifter Aug 14 '18 at 13:03
  • This could be slower but this kind of performance hit can generally be ignored. I like this solution – Val Aug 18 '20 at 07:04
5

You just need to use the color animation on the gradient stops of the gradient brush. Here is an example that animates a rectangle gradient using a storyboard.

    <Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="GradientBrushAnimation.MainWindow"
    x:Name="Window"
    Title="MainWindow"
    Width="640" Height="480">
    <Window.Resources>
        <Storyboard x:Key="Storyboard1">
            <ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[0].(GradientStop.Color)" Storyboard.TargetName="rectangle">
                <EasingColorKeyFrame KeyTime="0:0:2" Value="Red"/>
            </ColorAnimationUsingKeyFrames>
            <ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[1].(GradientStop.Color)" Storyboard.TargetName="rectangle">
                <EasingColorKeyFrame KeyTime="0:0:2" Value="#FF71FF00"/>
            </ColorAnimationUsingKeyFrames>
        </Storyboard>
    </Window.Resources>
    <Window.Triggers>
        <EventTrigger RoutedEvent="FrameworkElement.Loaded">
            <BeginStoryboard Storyboard="{StaticResource Storyboard1}"/>
        </EventTrigger>
    </Window.Triggers>

    <Grid x:Name="LayoutRoot">
        <Rectangle x:Name="rectangle" Margin="78,102,292,144" Stroke="Black">
            <Rectangle.Fill>
                <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                    <GradientStop Color="Black" Offset="0"/>
                    <GradientStop Color="White" Offset="1"/>
                </LinearGradientBrush>
            </Rectangle.Fill>
        </Rectangle>
    </Grid>
</Window>
Geoff Cox
  • 6,102
  • 2
  • 27
  • 30
  • That works well if i know what type of brush to expect. But in my case, there can be solid color, linear gradient or radialgradient depending on suer settings. Is there general way of brush to brush animation (independent of brush type)? –  Nov 12 '11 at 11:27
2

You can animate the color of the brush if you have a template style in which you give the fill brush a name, like so:

<Rectangle Width="100" Height="100">
  <Rectangle.Fill>
    <SolidColorBrush x:Name="MyAnimatedBrush" Color="Orange" />
  </Rectangle.Fill>
  <Rectangle.Triggers>

    <!-- Animates the brush's color to gray
         When the mouse enters the rectangle. -->
    <EventTrigger RoutedEvent="Rectangle.MouseEnter">
      <BeginStoryboard>
        <Storyboard>
          <ColorAnimation
            Storyboard.TargetName="MyAnimatedBrush"
            Storyboard.TargetProperty="Color"
            To="Gray" Duration="0:0:1" />
        </Storyboard>
      </BeginStoryboard>
    </EventTrigger>  
  </Rectangle.Triggers>
</Rectangle>

As taken from MSDN

landoncz
  • 1,997
  • 14
  • 15