In my last post I blogged about using Attached Properties to get around the limitation that only Dependency Properties can be animated. One astute commented noted that he was guessing this could be applied to animations as well and the answer is yet it can. However, it requires one extra step that makes it a little less appealing.
Also I mentioned in my last post, I got this idea from the Climbing Mt Avalon workshop at MIX which has now been posted online and I would recommend watching if you’re doing Silverlight or WPF work. And now on to the code…
Typically if you want to animating something like the width of a grid in a column that isn’t animatable either because it isn’t a double, color, or another easily animatable type, then you would declare a dependency property on your own host class, usually a UserControl, and then animate that instead. A good example is this blog post on the subject which is what I’ve referred to many times.
However, if we take the attached property route instead of putting the code in our user control, we could declare our own attached property to do the work for us. Here is a simple example:
public static class Attachments
{
public static readonly DependencyProperty ColumnWidthProperty =
DependencyProperty.RegisterAttached("ColumnWidth",
typeof(double), typeof(Attachments),
new PropertyMetadata(
new PropertyChangedCallback(OnColumnWidthChanged)));
public static void SetColumnWidth(DependencyObject o, double value)
{
o.SetValue(ColumnWidthProperty, value);
}
public static double GetColumnWidth(DependencyObject o)
{
return (double)o.GetValue(ColumnWidthProperty);
}
private static void OnColumnWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((ColumnDefinition)d).Width = new GridLength((double)e.NewValue);
}
}
Once we have this code we can now simply animate the attached property like so:
<UserControl x:Class="SilverlightApplication1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SilverlightApplication1"
Width="400" Height="300">
<Grid x:Name="LayoutRoot" Background="White">
<Grid.Resources>
<Storyboard x:Name="expandBlue">
<DoubleAnimation Storyboard.TargetName="blue"
To="300" Duration="0:0:1" />
<DoubleAnimation Storyboard.TargetName="red"
To="100" Duration="0:0:1" />
</Storyboard>
<Storyboard x:Name="expandRed">
<DoubleAnimation Storyboard.TargetName="red"
To="300" Duration="0:0:1" />
<DoubleAnimation Storyboard.TargetName="blue"
To="100" Duration="0:0:1" />
</Storyboard>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="blue" local:Attachments.ColumnWidth="200" />
<ColumnDefinition x:Name="red" local:Attachments.ColumnWidth="200"/>
</Grid.ColumnDefinitions>
<Rectangle Grid.Column="0" Fill="Blue" MouseLeftButtonDown="Blue_MouseLeftButtonDown" />
<Rectangle Grid.Column="1" Fill="Red" MouseLeftButtonDown="Red_MouseLeftButtonDown"/>
</Grid>
</UserControl>
Unfortunately if you try the above code (after adding in the mouse event handlers) it won’t work. Why not? Well there seems to be an issue with animating custom attached properties when setting the target property directly in code (actually you’ll notice I left that out above. However, there is a way around it which I found over on Ed’s blog which is to set the target property in code. So here is the code behind with the work around:
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
Storyboard.SetTargetProperty(expandBlue.Children[0], new PropertyPath(Attachments.ColumnWidthProperty));
Storyboard.SetTargetProperty(expandBlue.Children[1], new PropertyPath(Attachments.ColumnWidthProperty));
Storyboard.SetTargetProperty(expandRed.Children[0], new PropertyPath(Attachments.ColumnWidthProperty));
Storyboard.SetTargetProperty(expandRed.Children[1], new PropertyPath(Attachments.ColumnWidthProperty));
}
private void Blue_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
expandBlue.Begin();
}
private void Red_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
expandRed.Begin();
}
}
Once we set the target property via code, then everything works great. However, that is a pain and makes things a lot less clean. But still I think this is a useful approach to animating the properties that are not easily animatable.