WPF animation: binding to the "To" attribute of storyboard animation WPF animation: binding to the "To" attribute of storyboard animation wpf wpf

WPF animation: binding to the "To" attribute of storyboard animation


I've had similar situations in ControlTemplates where I've wanted to bind the "To" attribute to a value (rather than hard-coding it), and I finally found a solution.

Quick side note: If you dig around on the web you'll find examples of people being able to use data binding for the "From" or "To" properties. However, in those examples the Storyboards are not in a Style or ControlTemplate. If your Storyboard is in a Style or ControlTemplate, you'll have to use a different approach, such as this solution.

This solution gets around the freezable issue because it simply animates a double value from 0 to 1. It works with a clever use of the Tag property and a Multiply converter. You use a multibinding to bind to both a desired property and your "scale" (the Tag), which get multiplied together. Basically the idea is that your Tag value is what you animate, and its value acts like a "scale" (from 0 to 1) bringing your desired attribute value to "full scale" once you've animated the Tag to 1.

You can see this in action here. The crux of it is this:

<local:MultiplyConverter x:Key="multiplyConverter" /><ControlTemplate x:Key="RevealExpanderTemp" TargetType="{x:Type Expander}">    <!-- (other stuff here...) -->    <ScrollViewer x:Name="ExpanderContentScrollView">        <!-- ** BEGIN IMPORTANT PART #1 ...  -->        <ScrollViewer.Tag>            <sys:Double>0.0</sys:Double>        </ScrollViewer.Tag>        <ScrollViewer.Height>            <MultiBinding Converter="{StaticResource multiplyConverter}">               <Binding Path="ActualHeight" ElementName="ExpanderContent"/>               <Binding Path="Tag" RelativeSource="{RelativeSource Self}" />            </MultiBinding>        </ScrollViewer.Height>        <!-- ...end important part #1.  -->        <ContentPresenter x:Name="ExpanderContent" ContentSource="Content"/>    </ScrollViewer>  <ControlTemplate.Triggers>    <Trigger Property="IsExpanded" Value="True">        <Trigger.EnterActions>            <BeginStoryboard>                <Storyboard>                   <!-- ** BEGIN IMPORTANT PART #2 (make TargetProperty 'Tag') ...  -->                   <DoubleAnimation Storyboard.TargetName="ExpanderContentScrollView"                         Storyboard.TargetProperty="Tag"                         To="1"                         Duration="0:0:0.4"/>                    <!-- ...end important part #2 -->               </Storyboard>            </BeginStoryboard>        </Trigger.EnterActions>    </Trigger>  </ControlTemplate.Triggers></ControlTemplate>

With this value converter:

public class MultiplyConverter : IMultiValueConverter{    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)    {       double result = 1.0;       for (int i = 0; i < values.Length; i++)       {           if (values[i] is double)               result *= (double)values[i];       }       return result;    }   public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)   {       throw new Exception("Not implemented");   }}


As far as I know, you can't bind the animation to/from because the animation has to be freezable.


I like @Jason Frank's solution. However it is even easier and less error-prone if you don't use the Tag, but instead e.g. the Width property of an empty dummy Border element. It is a native double property, so no need for <sys:Double> syntax and you can name the Border just like you would do with a variable like so:

<!-- THIS IS JUST USED FOR SLIDING ANIMATION MATH --><!-- animated Border.Width    From 0 to 1 --><Border x:Name="Var_Animation_0to1" Width="0"/><!-- animated Border.Width    From 0 to (TotalWidth-SliderWidth) --><Border x:Name="Var_Slide_Length">    <Border.Width>        <MultiBinding Converter="{mvvm:MathConverter}" ConverterParameter="a * (b-c)">            <Binding ElementName="Var_Animation_0to1" Path="Width"/>            <Binding ElementName="BackBorder" Path="ActualWidth"/>            <Binding ElementName="Slider" Path="ActualWidth"/>        </MultiBinding>    </Border.Width></Border>

That makes the bindings much more readable.

The Animation is always 0..1, as Jason pointed out:

<BeginStoryboard Name="checkedSB">    <Storyboard Storyboard.TargetProperty="Width" Storyboard.TargetName="Var_Animation_0to1">        <DoubleAnimation To="1" Duration="00:00:00.2"/>    </Storyboard></BeginStoryboard>

Then bind whatever you want to animate to the Width of the dummy border. This way you can even chain converters to each other like so:

<Border x:Name="Slider" HorizontalAlignment="Left"        Margin="{Binding ElementName=Var_Slide_Length, Path=Width, Converter={StaticResource DoubleToThickness}, ConverterParameter=x 0 0 0}"/>

Combined with the MathConverter you can do almost anything in styles:https://www.codeproject.com/Articles/239251/MathConverter-How-to-Do-Math-in-XAML