Tip: Cannot animate ‘…’ on an immutable object instance


For the most part, you can animate any property in a WPF application.  For example, the following is a rectangle that animates it’s fill color on mouse enter and leave:


 


<Window x:Class="Scratch.Window1"


    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"


    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"


    xmlns:local="clr-namespace:Scratch"


    Foreground="Black"


    Name="MyWindow"


     >


 


  <Rectangle Width="100" Height="100"


    Fill="Green" >


    <Rectangle.Triggers>


      <EventTrigger RoutedEvent="Rectangle.MouseEnter">


        <BeginStoryboard>


          <Storyboard TargetProperty="Fill.Color">


            <ColorAnimation To="Red" Duration="0:0:1" />


          </Storyboard>


        </BeginStoryboard>


      </EventTrigger>


      <EventTrigger RoutedEvent="Rectangle.MouseLeave">


        <BeginStoryboard>


          <Storyboard TargetProperty="Fill.Color">


            <ColorAnimation Duration="0:0:1" />


          </Storyboard>


        </BeginStoryboard>


      </EventTrigger>


    </Rectangle.Triggers>


  </Rectangle>


 


</Window>


 


If you animate a property that’s databound, however, you might get the following exception:  “Cannot animate 'Fill.Color' on an immutable object instance.”  For example, you’ll get this if you change the rectangle’s fill above to:


 


Fill="{Binding ElementName=MyWindow, Path=Foreground}" >


 


The reason for this is a known issue where the animation is trying to make a copy of the Window’s “black” foreground brush, but is unable to because of the interaction with the binding.


 


As a workaround, you can update the binding to make a copy of the brush for the rectangle.  That doesn’t interfere with the binding – any change to the window’s foreground will still be propagated to the rectangle – but the rectangle will make its own copy for a local animation.  So the Fill ends up looking like this:


 


Fill="{Binding ElementName=MyWindow, Path=Foreground, Converter={x:Static local:MyCloneConverter.Instance}}"


 


… which is referencing an IValueConverter for the binding that looks like this:


 


internal class MyCloneConverter : IValueConverter


{


    public static MyCloneConverter Instance = new MyCloneConverter();


 


    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)


    {


        if (value is Freezable)


        {


            value = (value as Freezable).Clone();


        }


 


        return value;


    }


 


    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)


    {


        throw new NotSupportedException();


    }


 


}


 


 

Comments (2)

  1. tgp1994 says:

    I actually found a little shortcut around this, by setting the value to anything other than 0. 5 works fine for me, but I guess that’s only a patch on the tire, so to speak.

  2. Brendan says:

    this is great helped me solve my problem immediately

    +1

Skip to main content