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:
- Create one or more properties of type Effect.
- 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.
- 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.