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="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://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
Anonymous
June 20, 2009
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.Anonymous
August 07, 2014
this is great helped me solve my problem immediately +1