Delay's Blog

Silverlight, WPF, Windows Phone, .NET, and more...

A bit more(er) than meets the eye [Easily animate and update LayoutTransformer with AnimationMediator!]

A bit more(er) than meets the eye [Easily animate and update LayoutTransformer with AnimationMediator!]

  • Comments 3

Yesterday I posted the code for AnimationMediator, a simple Mediator class to make it easy to animate the Transitions of a LayoutTransformer. Today, Silverlight Toolkit teammate Ted Glaza asked why the LayoutTransformerName property was present and I said that while I'd really wanted to use Binding+ElementName with the LayoutTransformer property, it didn't work for me when I tried. Ted kindly pointed out that in order for that to succeed, the LayoutTransformer property needed to be a DependencyProperty - and I'd used a simple CLR property instead. :(

Aside: What happened is that I originally used a DependencyProperty, convinced myself it wasn't working, then simplified to a plain CLR property. But I obviously did something wrong along the way, because the scenario I wanted to enable works great once LayoutTransformer is a DependencyProperty.

 

So I've updated the implementation of AnimationMediator accordingly; you can get the new version by downloading the source code again - or from below. And with this update there's no need to use the LayoutTransformerName property on Silverlight 3 (in fact, I've removed it from the code!) - so the scenario from last time simplifies to the following:

<local:AnimationMediator
    x:Name="RotationMediator"
    LayoutTransformer="{Binding ElementName=ButtonTransformer}"
    AnimationValue="{Binding Angle, ElementName=Rotation, Mode=TwoWay}"/>
Aside: Binding's ElementName property isn't present on Silverlight 2, so the LayoutTransformerName property is still relevant on that platform. Therefore, I have not changed the AnimationMediator implementation I originally included in the text of yesterday's post.

 

AnimatingLayoutTransformer Demo

 

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

 

As long as I was updating the sample application, I wanted to take the opportunity to show off two other things as well. The first is that AnimationMediator is good for more than just Storyboards - you can also use it with XAML-only bindings to other UI elements! In this case, I've added a TextBlock and two Sliders and hooked those Sliders up to the ScaleTransform of a LayoutTransformer for the text. As you'll see if you run the demo, it works just like you'd expect and it's 100% XAML, no code. :) The second thing I wanted to show is a proof of my claim that you can use two AnimationMediators with the same LayoutTransformer - which I do here.

This is what the XAML for the new scaling scenario looks like:

<!-- Applies the LayoutTransform for TextBlock -->
<layoutToolkit:LayoutTransformer
    x:Name="TextTransformer">

    <!-- A scale transformation-->
    <layoutToolkit:LayoutTransformer.LayoutTransform>
        <ScaleTransform
            x:Name="Scale"/>
    </layoutToolkit:LayoutTransformer.LayoutTransform>

    <!-- Text being scaled -->
    <TextBlock
        Text="Scale Me!"
        FontSize="50"
        FontWeight="Bold"
        HorizontalAlignment="Center"
        VerticalAlignment="Center"/>

</layoutToolkit:LayoutTransformer>

<!-- An AnimationMediator for each Slider Binding -->
<local:AnimationMediator
    x:Name="ScaleXMediator"
    LayoutTransformer="{Binding ElementName=TextTransformer}"
    AnimationValue="{Binding ScaleX, ElementName=Scale, Mode=TwoWay}"/>
<local:AnimationMediator
    x:Name="ScaleYMediator"
    LayoutTransformer="{Binding ElementName=TextTransformer}"
    AnimationValue="{Binding ScaleY, ElementName=Scale, Mode=TwoWay}"/>

<!-- A Slider for X and Y -->
<Slider
    Maximum="5"
    Value="{Binding AnimationValue, ElementName=ScaleXMediator, Mode=TwoWay}"
    Grid.Row="1"/>
<Slider
    Maximum="5"
    Value="{Binding AnimationValue, ElementName=ScaleYMediator, Mode=TwoWay}"
    Grid.Row="2"/>

 

So thanks for pointing this out, Ted - AnimationMediator is now just about as simple as can be!

 

 

PS - Here's the updated implementation of AnimationMediator:

/// <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 { return (LayoutTransformer)GetValue(LayoutTransformerProperty); }
        set { SetValue(LayoutTransformerProperty, value); }
    }
    public static readonly DependencyProperty LayoutTransformerProperty =
        DependencyProperty.Register(
            "LayoutTransformer",
            typeof(LayoutTransformer),
            typeof(AnimationMediator),
            new PropertyMetadata(LayoutTransformerPropertyChanged));
    private static void LayoutTransformerPropertyChanged(
        DependencyObject o,
        DependencyPropertyChangedEventArgs e)
    {
        var layoutTransformer = (LayoutTransformer)(e.NewValue);
        if (null != layoutTransformer)
        {
            // Update now to be safe
            layoutTransformer.ApplyLayoutTransform();
        }
    }

    /// <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)
        {
            throw new InvalidOperationException(
                "AnimationMediator's LayoutTransformer property must not be null.");
        }
        // The Transform hasn't been updated yet; schedule an update to run after it has
        Dispatcher.BeginInvoke(() => LayoutTransformer.ApplyLayoutTransform());
    }
}
  • PingBack from http://blogs.msdn.com/delay/archive/2009/04/09/a-bit-more-than-meets-the-eye-easily-animate-layouttransformer-with-animationmediator.aspx

  • I am experiencing some odd behaviour in SL4 that might warrant some further investigation by someone who better understands dependency properties and the ValueChangedEvent notification chain. When trying to animate multiple transform properties together (ie. Scale.X and Rotate.Angle) the fiollowing XAML works

    <Storyboard x:Name="myStoryboard1">

                                   <DoubleAnimation Storyboard.TargetName="scaleTransform1ScaleXMediator"

                                                Storyboard.TargetProperty="AnimationValue"

                                                From="2.0" To="1.0" Duration="0:0:2" />

                                   <DoubleAnimation Storyboard.TargetName="scaleTransform1"

                                                Storyboard.TargetProperty="ScaleY"

                                                From=".1" To="1.0" Duration="0:0:2" />

                                   <DoubleAnimation Storyboard.TargetName="skewTransform1"

                                                Storyboard.TargetProperty="AngleX"

                                                From="45" To="0" Duration="0:0:2" />

                                   <DoubleAnimation Storyboard.TargetName="rotateTransform1"

                                                Storyboard.TargetProperty="Angle"

                                                From="45" To="0" Duration="0:0:2" />

                               </Storyboard>

    <layout:LayoutTransformer x:Name="txButton1">

                               <layout:LayoutTransformer.LayoutTransform>

                                   <TransformGroup>

                                       <ScaleTransform x:Name="scaleTransform1" ScaleX="2" ScaleY=".1"/>

                                       <SkewTransform x:Name="skewTransform1" AngleX="45"/>

                                       <RotateTransform x:Name="rotateTransform1" Angle="45"/>

                                   </TransformGroup>

                               </layout:LayoutTransformer.LayoutTransform>

                               <Button x:Name="TestButton1" Width="120" Height="30" Content="ButtonNameHere" ></Button>

                           </layout:LayoutTransformer>

    <animLT:AnimationMediator x:Name="scaleTransform1ScaleXMediator"

                                                    LayoutTransformer="{Binding ElementName=txButton1}"

                                                    AnimationValue="{Binding ScaleX, ElementName=scaleTransform1, Mode=TwoWay}"/>

    Notice I didn't have to create an AnimationMediator for each transform element property (Just the first one) then bind to the others as you might normally think to ...

    maybe this has something to do with how Mode=TwoWay sets up the event notification pipeline ... but I don't know enough to explore it any further.

    if I did create  AnimationMediator 's for each tranform property ... it worked nicely too.

    if this is by-design i apologize I just felt like it was odd behavior based on your documentation ... I expected to have a discrete  AnimationMediator for each property.

    Robbie Symborski

    robbie.symborski@live.com

  • Robbie,

    The key thing that AnimationMediator does is call LayoutTransformer.ApplyLayoutTransform after each update to the relevant Transforms in order to work around a Silverlight limitation where those changes aren't automatically bubbled up to LayoutTransformer. But ApplyLayoutTransform only needs to be called once per "tick" (i.e., when Silverlight updates all its animations to the next "step"). Because updates all happen together and the specifics of your scenario are such that you're changing a bunch of properties that affect the *same* LayoutTransformer, you can get away with only one AnimationMediator - when it calls ApplyLayoutTransform, LayoutTransformer looks around, sees the whole batch of updates together, and reacts accordingly. The fact that this optimization works for you is kind of a fortunate consequence of how things are implemented, I'd say. :)

Page 1 of 1 (3 items)
Leave a Comment
  • Please add 4 and 1 and type the answer here:
  • Post