The blog of dlaa.me

A bit more than meets the eye [Easily animate LayoutTransformer with AnimationMediator!]

I came across a question on the Silverlight Toolkit support forum yesterday asking how to animate the values of a Transform used by the Silverlight Toolkit's LayoutTransformer. (Background on LayoutTransformer: Motivation and introduction for Beta 1, Significant enhancements and update for Beta 2, Update for RTW, Fixes for two edge case bugs, Usefulness on WPF, Rename for Silverlight Toolkit.) As the questioner discovered, this task isn't quite as easy as it should be due to a difference between Silverlight and WPF.

Aside: I describe the Silverlight limitation in more detail under the third bullet point of the "Notes" in this post (though that workaround has since been invalidated per the first bullet point of the "Notes" of this post).
AnimatingLayoutTransformer Demo

I wanted something that was simple and easy to use - and managed to come up with a pretty reasonable XAML-only solution on my second try. [The first attempt would have been a bit simpler, but didn't work out. :( ] The basic idea is to insert a Mediator between the animation and the LayoutTransformer and let the Mediator make sure the LayoutTransformer is updated when necessary. This task is complicated slightly by the fact that Silverlight won't let you create a Binding on one of its Transforms - but the equivalent effect can be had by putting a TwoWay Binding on the Mediator instead (as discussed in more detail by Jeff Prosise here).

This is what the XAML for the above sample looks like:

<Grid.Resources>
    <!-- Storyboard to animate the Button; targets AnimationMediator -->
    <Storyboard x:Key="Animation">
        <DoubleAnimation
            Storyboard.TargetName="RotationMediator"
            Storyboard.TargetProperty="AnimationValue"
            To="180"
            AutoReverse="True"
            Duration="0:0:0.3"/>
    </Storyboard>
</Grid.Resources>

<!-- Target of animation; forwards changes to the RotateTransform -->
<local:AnimationMediator
    x:Name="RotationMediator"
    LayoutTransformerName="ButtonTransformer"
    AnimationValue="{Binding Angle, ElementName=Rotation, Mode=TwoWay}"/>

<!-- Applies the LayoutTransform for Button -->
<layoutToolkit:LayoutTransformer
    x:Name="ButtonTransformer"
    Grid.Column="1"
    Grid.Row="1">

    <!-- A simple transformation -->
    <layoutToolkit:LayoutTransformer.LayoutTransform>
        <RotateTransform
            x:Name="Rotation"/>
    </layoutToolkit:LayoutTransformer.LayoutTransform>

    <!-- Button being animated -->
    <Button
        Content="Click to Animate!"
        Click="Button_Click"
        FontSize="30"/>

</layoutToolkit:LayoutTransformer>

The sample application is written for Silverlight 3 because I wanted to make use of the new ElementName feature to keep things simple - but the basic idea applies to Silverlight 2 just the same. (However, please note that it may be necessary to hook up the Binding with code on that platform.) And if you find that you need to support multiple Transforms, just add a few more AnimationMediators to the mix! :)

There you have it - with just a little bit of extra typing to add an AnimationMediator, the original scenario works quite nicely!

 

[Click here to download the complete source code for the AnimationMediator sample application.]

 

PS - Here's the implementation of AnimationMediator:

Updated 2009-04-10: Please see this post for an update that makes AnimationMediator a little easier to use on Silverlight 3.

/// <summary>
/// Class that acts as a Mediator between a Storyboard animation and a
/// Transform used by the Silverlight Toolkit's LayoutTransformer.
/// </summary>
/// <remarks>
/// Works around an issue with the Silverlight platform where changes to
/// properties of child Transforms assigned to a Transform property do not
/// trigger the top-level property changed handler (as on WPF).
/// </remarks>
public class AnimationMediator : FrameworkElement
{
    /// <summary>
    /// Gets or sets a reference to the LayoutTransformer to update.
    /// </summary>
    public LayoutTransformer LayoutTransformer { get; set; }

    /// <summary>
    /// Gets or sets the name of the LayoutTransformer to update.
    /// </summary>
    /// <remarks>
    /// This property is used iff the LayoutTransformer property is null.
    /// </remarks>
    public string LayoutTransformerName
    {
        get
        {
            return _layoutTransformerName;
        }
        set
        {
            _layoutTransformerName = value;
            // Force a new name lookup
            LayoutTransformer = null;
        }
    }
    private string _layoutTransformerName;

    /// <summary>
    /// Gets or sets the value being animated.
    /// </summary>
    public double AnimationValue
    {
        get { return (double)GetValue(AnimationValueProperty); }
        set { SetValue(AnimationValueProperty, value); }
    }
    public static readonly DependencyProperty AnimationValueProperty =
        DependencyProperty.Register(
            "AnimationValue",
            typeof(double),
            typeof(AnimationMediator),
            new PropertyMetadata(AnimationValuePropertyChanged));
    private static void AnimationValuePropertyChanged(
        DependencyObject o,
        DependencyPropertyChangedEventArgs e)
    {
        ((AnimationMediator)o).AnimationValuePropertyChanged();
    }
    private void AnimationValuePropertyChanged()
    {
        if (null == LayoutTransformer)
        {
            // No LayoutTransformer set; try to find it by LayoutTransformerName
            LayoutTransformer = FindName(LayoutTransformerName) as LayoutTransformer;
            if (null == LayoutTransformer)
            {
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture,
                    "AnimationMediator was unable to find a LayoutTransformer named \"{0}\".",
                    LayoutTransformerName));
            }
        }
        // The Transform hasn't been updated yet; schedule an update to run after it has
        Dispatcher.BeginInvoke(() => LayoutTransformer.ApplyLayoutTransform());
    }
}