Cover

Writing Behaviors for Silverlight 3 - Part 2

May 16, 2009
No Comments.

Url: http://wilderminds.blob.core.windows.net/downloads/BehaviorDemo.zip

In the first part of this article, I showed you how to build a very simple behavior using the Behavior<T> class. That class is useful for creating simple behaviors that just require that the behavior know about attaching and detaching of the item.

In this 2nd part of the series, I’ll show off the TriggerAction<T> class which expands on this by supporting an Invoke method which is called once an event trigger happens. This is a much more common case than the simple behavior.

For example, let’s create a behavior that shivers an object when an event is fired.  I start out with a behavior that derives from TriggerAction<T> like so:

public class ShiverBehavior : TriggerAction<UIElement>{}

Note that I use UIElement again as the generic parameter.  This is because I should be able to make any UIElement shiver with my behavior. This parameter determines what types of objects I can attach the behavior to.

I decided to implement the behavior by using a DoubleAnimation with a ElasticEase (easing function). To make the shiver happen, I am going to animate a TranslateTransform so I can move it around vertically or horizontally. So my behavior is going to need to reference and create the storyboard (and related objects) to do the actual shivering:

TranslateTransform shiverTransform = new TranslateTransform();
Storyboard shiverStory = new Storyboard();
ElasticEase ease = new ElasticEase();
DoubleAnimation shiverAnimation = new DoubleAnimation();

public ShiverBehavior()
  : base()
{
  // Set up ease
  ease.Springiness = 2;

  // Create the Animation
  shiverAnimation.EasingFunction = ease;
  Storyboard.SetTarget(shiverAnimation, shiverTransform);

  // Setup Variable Properties
  SetupProperties();

  // Attach it to the story
  shiverStory.Children.Add(shiverAnimation);
}

The trick to making this work is to use the OnAttached to insert our TranslateTransform into the object itself. (We won’t work on objects that already have a TranslateTransform but that’s a small price for getting the behavior):

protected override void OnAttached()
{
  base.OnAttached();

  // Attach the transform
  if (this.AssociatedObject.RenderTransform == null)
  {
    this.AssociatedObject.RenderTransform = shiverTransform;
  }
  else if (this.AssociatedObject.RenderTransform is TransformGroup)
  {
    TransformGroup group = (TransformGroup)AssociatedObject.RenderTransform);
    group.Children.Add(shiverTransform);
  }
  else
  {
    var tx = AssociatedObject.RenderTransform;
    var group = new TransformGroup();
    group.Children.Add(tx);
    group.Children.Add(shiverTransform);
    AssociatedObject.RenderTransform = group;
  }
}

The OnAttached adds the transformation to the object (or inside a TransformGroup if it exists). In the OnDetaching we need to reverse this as well (you can see that code by downloading the sample since its not especially relevant to our explanation).

At this point we have a Storyboard that contains our animation of the TranslateTransform.  The TranslateTransform is now added to the object. We’re all ready for the last part of the code.  When the Invoke method is called, we simply need to start the animation:

protected override void Invoke(object parameter)
{
  shiverStory.Begin();
}

This Invoke method is the only required override that you need in your behavior, but in practice (as you’ve seen here), you will override several other parts of the class.

In order to make our shivering configurable, we can just add dependency properties and they show up in Blend automatically.  For example, the Oscillations property was created like so:

public int Oscillations
{
  get { return (int)GetValue(OscillationsProperty); }
  set { SetValue(OscillationsProperty, value); }
}
public static readonly DependencyProperty OscillationsProperty =
    DependencyProperty.Register("Oscillations", 
                                typeof(int), 
                                typeof(ShiverBehavior),
    new PropertyMetadata(25, 
      new PropertyChangedCallback(OnOscillationsChanged)));

private static void OnOscillationsChanged(DependencyObject d, 
                                          DependencyPropertyChangedEventArgs e)
{
  ((ShiverBehavior)d).OnOscillationsChanged(e);
}

protected virtual void OnOscillationsChanged(DependencyPropertyChangedEventArgs e)
{
  SetupProperties();
}

Creating the dependency properties is fairly simple (especially since I am using the dependency property snippets from the Silverlight Contrib project).

Using a TriggerAction based behavior is similar to using a simple Behavior. You can see the behaviors in the Asset Library as shown below:

Asset Library in Blend

Once added, the behavior becomes nested inside the object in the “Objects and Timeline”:

Objects and Timeline Behavior

In the Property pane, you can see the event that will trigger this behavior and change it (as well as behavior specific properties in the Miscellaneous section):

Behavior Properties

If you are using behaviors for your own projects, you just need to reference the assembly in your Silverlight project and they’ll show up in Blend 3’s Asset Library.  But if you want to build behaviors that are listed in all Blend 3’s projects (and make it add the assembly automatically), you will need to register your assembly in the registry:

HKEY_CURRENT_USER\Software\Microsoft\Expression\Blend\v3.0\Toolbox\Silverlight\v3.0{Friendly Name

With the default key pointing to the directory with behaviors.

Grab the source code and take a look at the behaviors:

http://wilderminds.blob.core.windows.net/downloads/BehaviorDemo.zip