Silverlight.FX Effects in Depth

An in-depth walkthrough of using the effect behaviors and animations in Silverlight.FX - from basic declarative use of out-of-the-box features, to writing your own effect-enabled Silverlight controls to writing your own custom effects.

I blogged about effects and transitions for Silverlight in the past - the first time on declaratively attaching some simple effect behaviors and the second time on using effect-enabled higher-level controls. I got comments asking more details on how this part of the Silverlight.FX works, especially how these features can be used programmatically and how such controls can be built, and I think this topic warrants a post all by itself (you can tell, I love this topic). I put this together some while back, but then PDC-related topics and vacation kept this from being published. Anyway, here it is...

You can download the code for entire Silverlight.FX framework and associated samples so you can play with it and use it in your own applications. The rest of the post describes the nuts and bolts of using and extending the effects and animation features.

Attaching Effects Declaratively
I'll start with the most basic scenario as a refresher so everyone is on the same page.

<Rectangle x:Name="redRect" Fill="Red" Opacity="0.5">
  <fxui:Interaction.Behaviors>
    <fxui:HoverEffect>
      <fxeffects:Fade FadeOpacity="1" />
    </fxui:HoverEffect>
  </fxui:Interaction.Behaviors>
</Rectangle>

Interaction.Behaviors is an attached property that represents a collection of behaviors (those following SilverlightFX closely will notice some changes as a result of some refactoring). A behavior is a fairly general concept - it is simply an encapsulation of some functionality, such as a set of event handlers, into a reusable component that can be attached to an element to "mixin" some new functionality.

In the xaml snippet above, HoverEffect derives from EffectBehavior which itself derives from Behavior. An instance of this behavior has been attached to the Rectangle. Once attached, it subscribes to the MouseEnter and MouseLeave events and handles them by playing the associated effect.

Every EffectBehavior is associated with an Effect instance. Each effect encapsulates two animations - one in the forward direction to the desired end-state and one in the reverse direction back to the start state. In the example above, a Fade effect has been specified which fades-in the element in the forward direction (on MouseEnter) and fades-out the element to its initial opacity in the reverse direction (on MouseLeave). Fade is just one of the several out-of-the-box effects and you can build your own as described later in the post.

Customizing Effects
Effects are like any other component you can declare and customize using xaml. Perhaps the two most interesting properties are an effect's Duration and Easing. Lets change the default linear interpolation that lasts 250 ms.

<fxeffects:Fade FadeOpacity="1" Duration="0:0:0.5" Easing="QuadraticInOut" />

The Easing property is an enum which offers a number of interpolation styles such as ease-in/ease-out (the most common, represented by QuadraticInOut) as well as Bounce, Elastic etc. Duration is a basic time span - you'll want to make sure this isn't too big - often times what demos well doesn't work well, especially if its something that a user is going to encounter repeatedly. I've heard a good rule of thumb is to find what demos well, and then at least half it.

Everything that can be expressed in xaml can be expressed imperatively if needed in c#:

Effect fade = new FadeEffect() {
    FadeOpacity = 1,
    Duration = TimeSpan.FromMilliseconds(500),
    Easing = EffectEasing.QuadraticInOut
};
HoverEffect hoverEffect = new HoverEffect() { Effect = fade };

BehaviorCollection behaviors = Interaction.GetBehaviors(redRect);
behaviors.Add(hoverEffect);

Building Controls that Use Effects Programmatically
In this particular scenario the idea is you're writing a custom control that consumes an effect directly and you don't want the additional xaml syntax overhead of attaching a behavior (since your control is going to trigger the effect directly).

Lets say we have a Sprite control that has an Image property. Something like the following:

[TemplatePart(Name = "ImagePresenter", Type = typeof(Image))]
public class Sprite : Control {
    public static readonly DependencyProperty ImageProperty =
        DependencyProperty.Register("Image", typeof(ImageSource), typeof(Sprite),
                                    null);

    public ImageSource Image {
        get { return (ImageSource)GetValue(ImageProperty); }
        set { SetValue(ImageProperty, value);
    }

    public Sprite() {
        DefaultStyleKey = typeof(Sprite);
    }
}

With the following default style:

<Style TargetType="app:Sprite">
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="app:Sprite">
        <Grid>
          <Image x:Name="ImagePresenter" Source="{TemplateBinding Image}" />
        </Grid>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

Now we want to add an Effect property and play it whenever the mouse is pressed in forward and reverse directions in an alternating fashion.

public class Sprite : Control {
    ...
    // Create an Effect property so the designer can select the effect to play
    public static readonly DependencyProperty EffectProperty =
        DependencyProperty.Register("Effect", typeof(Effect), typeof(Sprite), null);

    public Effect Effect {
        get { return (Effect)GetValue(EffectProperty); }
        set { SetValue(EffectProperty, value);
    }

    private EffectDirection _direction;

    public Sprite() {
        ...
        // This is the event we're going to use to trigger the effect
        MouseLeftButtonDown += OnMouseLeftButtonDown;
    }

    private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e) {
        Effect effect = Effect;
        if (effect != null) {
            IAttachedObject attachedObject = (IAttachedObject)effect;
            if (attachedObject.AssociatedObject != this) {
                // Attach the effect to the visual tree the first time around
                attachedObject.Attach(this);
           }

           EffectDirection direction = _direction;
           _direction = direction == EffectDirection.Forward ? EffectDirection.Reverse : EffectDirection.Forward;

            effect.PlayEffect(direction);
        }
    }
}

There are basically three steps to using an Effect programmatically in your control:

  1. Create one or more properties of type Effect.
  2. Attach the effect to an element on in the visual tree. If the effect needs to target a different element than the one it is attached to, you can set its Target property to a different element.
  3. Figure out the direction in which to play the effect, and call PlayEffect.

Pretty simple? Here is a bit of xaml that shows this sprite control in action:

<app:Sprite Image="/Silverlight.png">
  <app:Sprite.Effect>
    <fxeffects:Pulsate Duration="0:0:1" Easing="QuadraticInOut" />
  </app:Sprite.Effect>
</app:Sprite>

Creating a Custom Effect
The framework includes a number of stock effects out-of-the-box. However you can also create your own custom packaged effect by deriving from Effect and building two animations - one that represents forward direction, and the other that represents the reverse.

The animations that an effect produces themselves derive from ProceduralAnimation, which forms the basis for a procedural animation framework deep inside Silverlight.FX that takes care of scheduling the actual animation, performing interpolation and easing, repetition, composition etc. A couple of notes on this part of the framework:

  • You can use it independently without effects. What effects really provide are a XAML friendly mechanism to define and declare instances of prepackaged animations. One place I used it directly was in building animated layout panels such as TilePanel.
  • You can create both duration-bound and unbounded animations, though effects typically use only the former.

For purposes of this post, I'll build a FlashBulb effect (similar to the equivalent powerpoint effect).

public class FlashBulb : Effect {

    protected override ProceduralAnimation CreateEffectAnimation(EffectDirection direction) {
        FrameworkElement target = GetTarget();

        ScaleTransform scaleTransform = target.RenderTransform as ScaleTransform;
        if (scaleTransform == null) {
            scaleTransform = new ScaleTransform();
            target.RenderTransform = scaleTransform;
            target.RenderTransformOrigin = new Point(0.5, 0.5);
        }

        TimeSpan duration = TimeSpan.FromMilliseconds(Duration.TotalMilliseconds / 2);
        ProceduralAnimationEasingFunction easing = GetEasingFunction();

        // A FlashBulb effect basically grows the element and makes it transparent
        // half way and then restores the element to its initial state during the
        // 2nd half of the animation.
        // This is accomplished with three animations that auto-reverse.
        // As a result the animations are the same regardless of effect direction.
        DoubleAnimation opacityAnimation =
            new DoubleAnimation(target, UIElement.OpacityProperty, duration, 0.25);
        DoubleAnimation scaleXAnimation =
            new DoubleAnimation(scaleTransform, ScaleTransform.ScaleXProperty, duration, 1.1);
        DoubleAnimation scaleYAnimation =
            new DoubleAnimation(scaleTransform, ScaleTransform.ScaleYProperty, duration, 1.1);

        opacityAnimation.EasingFunction = easing;
        opacityAnimation.AutoReverse = true;

        scaleXAnimation.EasingFunction = easing;
        scaleXAnimation.AutoReverse = true;

        scaleYAnimation.EasingFunction = easing;
        scaleYAnimation.AutoReverse = true;

        // Create a composite animation that plays all three in parallel
        return new ProceduralAnimationSet(opacityAnimation, scaleXAnimation, scaleYAnimation);
    }
}

A more involved effect implementation might offer properties for page-level customization, and depending on the effect, might return different animations for forward and reverse effect directions. It might also use derived animations rather than the default DoubleAnimation if it wants to do custom calculation or additional setup/cleanup work before/after the animation by overriding the right methods.

Once this is built, I can use this along with the Sprite control from before as follows:

<app:Sprite Image="/Silverlight.png" Margin="100">
  <app:Sprite.Effect>
    <app:FlashBulb Duration="0:0:1" Easing="QuadraticInOut" />
  </app:Sprite.Effect>
</app:Sprite>

Here is the result (click the Silverlight logo to see the FlashBulb effect in action):

Summary
Hopefully this post explains using effects in your apps and in your controls, and a little bit of what is involved… to help you with enough context to take a deeper look at the samples and the code for the framework. If you have questions or use any of these features then feel free to post them in the comment stream below.

Posted on Sunday, 11/9/2008 @ 8:15 PM | #Silverlight


Comments

12 comments have been posted.

Ganesh

Posted on 11/10/2008 @ 6:57 AM
The flashbulb got stuck at full power when I did a double click . this is in firefox.

Nikhil Kothari

Posted on 11/10/2008 @ 7:25 AM
OK, that was a bug in my sample code - the Sprite control should ignore mouse events while the effect is active and playing... fixed.

Josh Santangelo

Posted on 11/10/2008 @ 8:44 AM
I tried downloading the SilverlightFX sample package and ran into some issues. When I run the "TestApp", the loader stops at 100% and nothing appears. When I ran the Amazon sample, I got multiple network errors after searching -- one for each thumbnail.

It would be nice to see an online demo which doesn't require a download.

Nikhil Kothari

Posted on 11/10/2008 @ 11:53 AM
All of my blog posts do have online demos ... perhaps the best way to list those posts is to go to my Silverlight category page at http://www.nikhilk.net/Category.Silverlight.aspx.

Secondly from the error messages you are getting, it seems likely you've got your startup project set to something other than the Web project, and as a result you're navigating to the test html files that the Silverlight project system creates (i.e. browsing directly against the file system) and not the aspx files (i.e. browsing over http). This causes apps to fail when they try and make an http request. Hope that helps getting the samples running on your machine. I wish there was a way to turn off this behavior of a Silverlight project - its frustrating, and 99.9% time is the undesirable behavior.

Matthew

Posted on 11/14/2008 @ 1:28 PM
Love the framework, exceptional effects and transitions. I modified your flip transition to go vertical instead of horizontal (ScaleY instead of ScaleX) and it works quite nicely.

I was wondering however if there was a way to load the behavior on startup and endlessly loop? Basically I have a KPI Status bar that is going to constantly flip the different KPI data on a dashboard type screen. Note, this is one of my first real tries at implementing silverlight of WPF in an application. I could not figure out where to set the repeat behavior, any help would be appreciated, thanks again!

Nikhil Kothari

Posted on 11/14/2008 @ 5:05 PM
You can certainly have an effect play on startup - instead of something like fxui:HoverEffect, you could use something like fxui:LoadEffect or fxui:TimedEffect, and in the latter case have the effect triggered every so often.

The FlipVertical is a good example... I'll put it on the list to add to the framework down the road.

khos

Posted on 11/18/2008 @ 1:40 AM
Wherre is Microsoft.scripting.hosting? I get compilation errors "Error 1 The type or namespace name 'Scripting' does not exist in the namespace 'Microsoft' (are you missing an assembly reference?) ".

Matthew

Posted on 11/18/2008 @ 8:11 AM
Nikhil,

Thanks for the fxui.TimedEffect! I tried it and it runs 10 seconds after the page loads, however, I still cannot seem to find out where / how to set up repeat behavior. I would like the timedEffect to run every 10 seconds (meanwhile I'll be changing the back side content so when it flips again, it looks new to the user).

Also, how difficult would it be to make the blinds transition only go up or down every time instead of going up then down or down then up?

Matthew

Posted on 11/18/2008 @ 9:06 AM
Bah, wish there was a way to edit. After more testing, I see that the timed effect does run every 10 seconds, but it only animates from front to back every time instead of front to back, then back to front, etc. I set AutoReverse and Reversible both equal to true, but that did not help. I apologize for my ignorance.

Here is a quick and dirty sample of my XAML, maybe you can see something I am doing wrong:

<UserControl x:Class="SilverlightApplication1.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:fxui="clr-namespace:Silverlight.FX.UserInterface;assembly=Silverlight.FX"
xmlns:fxeffects="clr-namespace:Silverlight.FX.UserInterface.Effects;assembly=Silverlight.FX"
xmlns:fxtransitions="clr-namespace:Silverlight.FX.UserInterface.Transitions;assembly=Silverlight.FX">
<UserControl.Resources>
<Style x:Key="statusText" TargetType="TextBlock">
<Setter Property="FontFamily" Value="Verdana" />
<Setter Property="FontSize" Value="20" />
<Setter Property="HorizontalAlignment" Value="Left" />
</Style>
</UserControl.Resources>

<StackPanel Margin="20" Background="White">
<Grid Grid.Column="1" Margin="10" HorizontalAlignment="Left" VerticalAlignment="Center" Cursor="Hand">
<Grid.RowDefinitions>
<RowDefinition Height="100" />
<RowDefinition Height="20" />
</Grid.RowDefinitions>
<fxui:Interaction.Behaviors>
<fxui:TimedEffect Interval="00:00:10">
<fxtransitions:FlipVertical TargetName="flipContainer" Duration="00:00:2" AutoReverse="True" Reversible="True" Easing="QuadraticInOut" />
</fxui:TimedEffect>
</fxui:Interaction.Behaviors>
<Grid x:Name="flipContainer">
<TextBlock Style="{StaticResource statusText}" Opacity="0">Hello World</TextBlock>
<TextBlock Style="{StaticResource statusText}" Opacity="1">Foo Bar</TextBlock>
</Grid>
</Grid>
</StackPanel>
</UserControl>

Rick

Posted on 11/18/2008 @ 5:22 PM
I really like the idea of creating an abstraction layer to make it easy to create instances of animation effects without having to recreate then every time I want to use them. I am having trouble trying to build the SilverlightFX project. Which version of the DLR and Silverlight do I need? I downloaded the DLR from with the latest IronPython release, and I am running Silverlight 2 RC0. I fixed the scripting Microsoft.Scripting... references, and the Type Converters, but I am getting an error now complaining that ScriptRuntimeSetup does not contain a definition for LanguageProviders. Any thoughts on what the problem is? Thanks!

Nikhil Kothari

Posted on 11/18/2008 @ 11:58 PM
@Matthew - You shouldn't have to set Reversible, as Flip is reversible intrinsically. You shouldn't set AutoReverse either since you want to reverse it on the next tick of the timed effect. I will look into this tomorrow ... hopefully. The best way to see what is happening is to put a breakpoint in TimedEffect and see if the expected effect direction is being used when playing the effect.

@Rick - can you download the latest bits attached to this post. A much older version (SL2 beta) used the DLR. The latest code doesn't have a DLR dependency.

Matthew

Posted on 11/20/2008 @ 9:00 AM
Nikhil,

apparently I never tried with both reversible and autoreverse left off. When i did this it worked like the behavior I was expecting. Thank you very much for all of your time.
Post your comment and continue the discussion.