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

27 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.

Bill Puccio

Posted on 1/21/2009 @ 2:23 PM
First off: This Framework is AWESOME. here is my question though: I am trying to call the "PlayEffect" method to fire a move event in C# and I am getting a null reference error on the following line :

mvQuestionNumbers.PlayEffect(EffectDirection.Forward);

Bill Puccio

Posted on 1/21/2009 @ 2:31 PM
First off: This Framework is AWESOME. here is my question though: I am trying to call the "PlayEffect" method to fire a move event in C# and I am getting a null reference error on the following line :

mvQuestionNumbers.PlayEffect(EffectDirection.Forward);

Ned

Posted on 1/23/2009 @ 2:07 AM
Getting the same issue as Bill. Have you figured it out Bill?

Cheers, N.

PS This is truly an awesome bit of work.

Bill Puccio

Posted on 1/25/2009 @ 7:35 AM
No I have not gotten this figured out. Have you Ned. This is really giving me a problem.

Andrea

Posted on 1/29/2009 @ 1:04 AM
Useful!

james

Posted on 1/31/2009 @ 4:01 AM
CHECK THIS UNIQUE SITE
nodahej.com Matrimonial Site is the worlds only Matrimonial Site for peoples who don't want to take and give dowries for their marriages.its total free site.Add your Free Matrimonial Profile Now.
nodahej.com

Tom

Posted on 1/31/2009 @ 9:34 AM
It would be nice to extend the Crossfade effect to accept more than just 2 images.

Tom

Posted on 1/31/2009 @ 10:32 AM
A couple minor comments about the TimedEffect behaviour when used with a crossfade transition. I noticed that it plays the 1st transition in about 1 second, then the rest occur on my 5 second interval. Looking at the code in TimedEffect, I changed OnLoaded() to comment out PlayEffect(EffectDirection.Forward) as this was causing the immediate transition. I also swapped the lines of code in OnTick() as that needed to play the animation then negate the value otherwise the 1st transition took twice as long as it was supposed to (or you could change _forward to false, but this OnTick() swap seemed to make more sense). I'm not sure what effect this may have on other transitions, but this works for me.

Ketaki

Posted on 3/5/2009 @ 6:20 AM
Hi.
I want to filp a grid on the click of a button. This button happens to be complexly situated on the grid, (inside a usercontrol (inside a user control(inside a usercontrol)))
I wrote this code within the button tag
<fxui:Interaction.Behaviors>
<fxui:ClickEffect>
<fxtransitions:Flip TargetName = "xyz" Duration="00:00:1" />
</fxui:ClickEffect>
</fxui:Interaction.Behaviors>

But this "xyz" is the grid in the usercontrol, which is the outher most of those mentioned. :) yeah little complicated. cant put it in better words, sorry.
So, this guy cannot find the "xyz" name element. Very obious why. But the solution is not obvious to me.
Is there any other way to get handle to that grid, other than finding by name?
How do I otherwise go abt it?
Any ideas? suggestions?
Thanks.

Marcus Baffa

Posted on 3/16/2009 @ 6:18 AM
Hi,

I have just downloaded Silverlight FX and I would like Open a Popup with a Fade Effect. I could not find a Behaviour that could be applied to the Popup when it is opened.

How can I do it ???

Thanks in advance

Mia

Posted on 3/16/2009 @ 8:38 AM
Flip Effect - I want to flip more than two images. Can that be done and if so, how can I do so? thanks.

Scott

Posted on 3/20/2009 @ 5:40 AM
Great effort on the SilverlightFX controls!! How can I use the LoadEffect control, I can not see an example of it in the Samples. I did a search of the entire solution for "LoadEffect" but to no avail. I am not sure what control it is spost to be nested in. Is there any documentation availabe for the SilverlightFX?

Regards,

Scott.

Jon

Posted on 8/7/2009 @ 9:10 AM
Great stuff!
Any plans for a flashlight effect or shine effect.. When an object load a glare of light goes accross the control?
thanks

Jon

Posted on 8/7/2009 @ 3:56 PM
Something like this...

<Canvas Margin="0,-500,0,0" RenderTransformOrigin="0.5,0.5" UseLayoutRounding="False" d:LayoutRounding="Auto">
<Canvas.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform Angle="10"/>
<TranslateTransform/>
</TransformGroup>
</Canvas.RenderTransform>
<Rectangle x:Name="ScreenGlintRect" Width="600" Height="5000" Opacity="0.3" >
<Rectangle.Triggers>
<EventTrigger RoutedEvent="Rectangle.Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="ScreenGlintRect" Storyboard.TargetProperty="(Canvas.Left)" From="-500" To="1000" Duration="0:0:1" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Rectangle.Triggers>
<Rectangle.Fill>
<LinearGradientBrush StartPoint="0,1" EndPoint="1,1">
<GradientStop Color="Transparent" Offset="0.0" />
<GradientStop x:Name="GlintColor" Color="Silver" Offset="0.30" />
<GradientStop Color="#00141313" Offset="1" />
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
</Canvas>

Wilson

Posted on 5/25/2010 @ 8:26 AM
Hello,
I am trying to programmatically associate a timed, highlight effect to specific items in a collection.
When preparing the items, it may or may not add the effect, depending on a certain value. If the condition is met, I try adding the effect with the following code block:

SilverlightFX.UserInterface.Effects.Highlight highlight = new SilverlightFX.UserInterface.Effects.Highlight();
highlight.HighlightColor = Color.FromArgb(0, 0, 255, 0);
highlight.Duration = new TimeSpan(0, 0, 2);
highlight.Target = e.PreparedItem;

SilverlightFX.UserInterface.TimedEffect timedEffect = new SilverlightFX.UserInterface.TimedEffect() { Effect = highlight };

(based on your example in the Customizing Effects section of the post)

but I'm getting the error "Cannot implicitly convert type 'SilverlightFX.UserInterface.Effects.Highlight' to 'System.Windows.Media.Effects.Effect'" on the last line of code...

Can you tell me what I'm doing wrong here, and how to get this to work?
Thanks.
Post your comment and continue the discussion.