Monday, September 14, 2009

[2009.09.14] WPF Actions and Behaviors: A closer look

There are two assemblies that house the classes necessary for extending interactivity in WPF. They are:

  • System.Windows.Interactivity.dll
  • Microsoft.Expression.Interactions.dll

I am using Windows Server 2008 Enterprise and for my setup, these assemblies are found in:
C:\Program Files (x86)\Microsoft SDKs\Expression\Blend 3\Interactivity\Libraries. In the Libraries folder, you will find two other folders: WPF and Silverlight and each has two assemblies listed above to be used for the particular project type. I mentioned these files now because you need to do an "Add Reference" to them in order to create your own behaviors, actions or triggers.

In the System.Windows.Interactivity.dll, you will find a set of classes which you will inherit from depending on what you are trying to do. Here are some of the classes you can inherit from if you want to create:

  1. Actions + Behavior: (with an associated trigger)
    TriggerAction,
    TriggerAction<T>,
    TargetedTriggerAction,
    TargetedTriggerAction<T>
  2. Behavior: (without an associated trigger)
    Behavior,
    Behavior<T>
  3. Triggers:
    TriggerBase,
    TriggerBase<T>

All of these classes share a common property in that they all inherit from Animatable. As such, they exhibit a similar behavioral pattern as related to each other.

The above list is a bit confusing when you first look at it but basically you can create a Behavior by inheriting from the exact same classes as you would use when creating an Action. The things that really separates them (and sometimes doesn't) boils down to which of the virtual methods defined in the base class you override and implement.

To create just an Action, you derive from all the classes listed in 1 above but you need to only override the Invoke() method. Here is where you write code to do what you want when the Action is invoked.

To create a Behavior that uses Triggers, you can derive from classes both in 1 and 2 but instead you override the OnAttached() and OnDetaching() methods. However, depending on what your behavior entails, you may choose to override Invoke() as well. For a Behavior without any Triggers, derive from classes in 2 and override the methods mentioned above. Of course with this approach, once you attach the behavior, it will do do what the code written tells it to do without need for triggers. Note that you can also use Commands with Behaviors.

Blend 3 and Visual Studio 2008 SP1 comes with templates to get you started in creating actions, behaviors and triggers as in Fig. 1 and 2.

[2009.09.13].01.Blend.new.item
Fig. 1. Blend 3 interactivity templates

[2009.09.13].02.VS.new.item
Fig. 2. Visual Studio interactivity templates

Each time you add one of these items to your project, a skeleton implementation is created. For example, if you are creating a new Behavior, your class will already derive from Behavior<T>, and OnAttached() and OnDetaching() methods will be overridden. In addition, each comes commented to point you in the right direction as shown in Listing 1.

  1: using System;
  2: using System.Collections.Generic;
  3: using System.Text;
  4: using System.Windows;
  5: using System.Windows.Controls;
  6: using System.Windows.Data;
  7: using System.Windows.Documents;
  8: using System.Windows.Input;
  9: using System.Windows.Media;
 10: using System.Windows.Media.Imaging;
 11: using System.Windows.Shapes;
 12: using System.Windows.Interactivity;
 13: //using Microsoft.Expression.Interactivity.Core;
 14: 
 15: namespace ActionBehaviorBlogDemo01
 16: {
 17:   public class Behavior2 : Behavior<DependencyObject>
 18:   {
 19:     public Behavior2()
 20:     {
 21:       // Insert code required on object creation below this point.
 22: 
 23:       //
 24:       // The line of code below sets up the relationship between the command and the function
 25:       // to call. Uncomment the below line and add a reference to Microsoft.Expression.Interactions
 26:       // if you choose to use the commented out version of MyFunction and MyCommand instead of
 27:       // creating your own implementation.
 28:       //
 29:       // The documentation will provide you with an example of a simple command implementation
 30:       // you can use instead of using ActionCommand and referencing the Interactions assembly.
 31:       //
 32:       //this.MyCommand = new ActionCommand(this.MyFunction);
 33:     }
 34: 
 35:     protected override void OnAttached()
 36:     {
 37:       base.OnAttached();
 38: 
 39:       // Insert code that you would want run when the Behavior is attached to an object.
 40:     }
 41: 
 42:     protected override void OnDetaching()
 43:     {
 44:       base.OnDetaching();
 45: 
 46:       // Insert code that you would want run when the Behavior is removed from an object.
 47:     }
 48: 
 49:     /*
 50:     public ICommand MyCommand
 51:     
 52:       get;
 53:       private set;
 54:     }
 55:      
 56:     private void MyFunction()
 57:     {
 58:       // Insert code that defines what the behavior will do when invoked.
 59:     }
 60:     /*
 61:   }
 62: }

Listing 1: Generated template to implement a Behavior

Examples of Actions and Behaviors

In all the samples, I am basically running the same animation, which changes the Opacity of the targeted or associated element. The main areas to look at is the base classes being derived from in each case as well as the comments in each class.

Example 1: Action which derives from TriggerAction

  1: using System;
  2: using System.Windows;
  3: using System.Windows.Interactivity;
  4: using System.Windows.Media.Animation;
  5: 
  6: namespace ActionBehaviorBlogDemo02
  7: {
  8: 
  9:     /// <summary>
 10:     /// Class showing a simple action that can be invoked by a trigger
 11:     /// </summary>
 12:     // Change the type parameter to be a FrameworkElement to access
 13:     //  the AssociatedObject's Opacity property
 14:     public class OpacityAction : TriggerAction<FrameworkElement>
 15:     {
 16:         // variables to hold Storyboard and
 17:         //  DoubleAnimation objects
 18:         Storyboard myStoryboard = new Storyboard();
 19:         DoubleAnimation myDoubleAnimation = new DoubleAnimation();
 20:  
 21:         public OpacityAction(){ }
 22: 
 23:         protected override void Invoke(object o)
 24:         {
 25:             // Insert code that defines what the Action 
 26:             //  will do when triggered/invoked.
 27:             this.Animate();
 28:         }
 29: 
 30:         // method to be called by Invoke()
 31:         public void Animate()
 32:         {
 33:             // set up the animation properties 
 34:             myDoubleAnimation.To = 0.2;
 35:             myDoubleAnimation.Duration = new Duration(TimeSpan.FromSeconds(1));
 36:             myDoubleAnimation.AutoReverse = true;
 37:             myDoubleAnimation.RepeatBehavior = RepeatBehavior.Forever;
 38: 
 39:             // the target of the animation is the AssociatedObject
 40:             //  i.e the object the Action is attached to
 41:             Storyboard.SetTarget(myDoubleAnimation, AssociatedObject);
 42:             // animate the Opacity property
 43:             Storyboard.SetTargetProperty(myDoubleAnimation,
 44:                 new PropertyPath(FrameworkElement.OpacityProperty));
 45:             myStoryboard.Children.Add(myDoubleAnimation);
 46:             myStoryboard.Begin();
 47:         }
 48:     }
 49: }

Listing 2: A simple Action

Example 2: Action which derives from TargetedTriggerAction

  1: using System;
  2: using System.Windows;
  3: using System.Windows.Interactivity;
  4: using System.Windows.Media.Animation;
  5: 
  6: namespace ActionBehaviorBlogDemo02
  7: {
  8: 
  9:     /// <summary>
 10:     /// Class used to set the target of the action other to the element it is attached to
 11:     /// </summary>
 12:     // If you want your Action to target elements other than its parent, extend your class
 13:     // from TargetedTriggerAction instead of from TriggerAction
 14:     public class OpacityTargetedTriggerAction : TargetedTriggerAction<FrameworkElement>
 15:     {
 16:         // variables to hold Storyboard and
 17:         //  DoubleAnimation objects
 18:         Storyboard myStoryboard = new Storyboard();
 19:         DoubleAnimation myDoubleAnimation = new DoubleAnimation();
 20:         public OpacityTargetedTriggerAction()
 21:         {
 22:             // Insert code required on object creation below this point.
 23:         }
 24: 
 25:         protected override void Invoke(object o)
 26:         {
 27:             if (Target == null)
 28:             {
 29:                 return;
 30:             }
 31: 
 32:             // Insert code that defines what the Action 
 33:             //  will do when triggered/invoked.
 34:             this.Animate();
 35:         }
 36: 
 37:         // method to be called by Invoke()
 38:         public void Animate()
 39:         {
 40:             // set up the animation properties 
 41:             myDoubleAnimation.To = 0.2;
 42:             myDoubleAnimation.Duration = new Duration(TimeSpan.FromSeconds(1));
 43:             myDoubleAnimation.AutoReverse = true;
 44:             myDoubleAnimation.RepeatBehavior = RepeatBehavior.Forever;
 45: 
 46:             // The action is meant to be executed on the Target
 47:             //  element, which can be 
 48:             //  or another element. By using the Target property here,
 49:             //  both cases are covered.
 50:             Storyboard.SetTarget(myDoubleAnimation, base.Target);
 51:             // animate the Opacity property
 52:             Storyboard.SetTargetProperty(myDoubleAnimation,
 53:                 new PropertyPath(FrameworkElement.OpacityProperty));
 54:             myStoryboard.Children.Add(myDoubleAnimation);
 55:             myStoryboard.Begin();
 56:         }
 57:     }
 58: }

Listing 3: An Action that can execute on the attached object or on another Target

Example 3: A simple Behavior

  1: using System;
  2: using System.Windows;
  3: using System.Windows.Interactivity;
  4: using System.Windows.Media.Animation;
  5: 
  6: namespace ActionBehaviorBlogDemo02
  7: {
  8:     /// <summary>
  9:     /// Basic independent behavior
 10:     /// </summary>
 11:     public class OpacityTriggerlessBehavior : Behavior<FrameworkElement>
 12:     {
 13:         Storyboard myStoryboard = new Storyboard();
 14:         DoubleAnimation myDoubleAnimation = new DoubleAnimation();
 15:         public OpacityTriggerlessBehavior()
 16:         {
 17:         }
 18: 
 19:         protected override void OnAttached()
 20:         {
 21:             base.OnAttached();
 22: 
 23:             // Since this behavior depicts an animation,
 24:             //  the animation will run as soon as the app
 25:             //  is up and running
 26:             // Here, the behavior is doing something without
 27:             //  being asked/triggered
 28:             this.Animate();
 29:         }
 30: 
 31:         protected override void OnDetaching()
 32:         {
 33:             base.OnDetaching();
 34:         }
 35: 
 36:         public void Animate()
 37:         {
 38:             // set up the animation properties 
 39:             myDoubleAnimation.To = 0.2;
 40:             myDoubleAnimation.Duration = new Duration(TimeSpan.FromSeconds(1));
 41:             myDoubleAnimation.AutoReverse = true;
 42:             myDoubleAnimation.RepeatBehavior = RepeatBehavior.Forever;
 43: 
 44:             // the target of the animation is the AssociatedObject
 45:             //  i.e the object the behavior is attached to
 46:             Storyboard.SetTarget(myDoubleAnimation, AssociatedObject);
 47:             // animate the Opacity property
 48:             Storyboard.SetTargetProperty(myDoubleAnimation,
 49:                 new PropertyPath(FrameworkElement.OpacityProperty));
 50:             myStoryboard.Children.Add(myDoubleAnimation);
 51:             myStoryboard.Begin();
 52:         }
 53:     }
 54: }

Listing 4: A simple Behavior

Example 4: A Behavior that can be triggered

  1: using System;
  2: using System.Windows;
  3: using System.Windows.Input;
  4: using System.Windows.Interactivity;
  5: using System.Windows.Media.Animation;
  6: 
  7: namespace ActionBehaviorBlogDemo02
  8: {
  9:     /// <summary>
 10:     /// Showing how behaviors can be infulenced by a trigger
 11:     /// </summary>
 12:     public class OpacityTriggeredBehavior : Behavior<FrameworkElement>
 13:     {
 14:         Storyboard myStoryboard = new Storyboard();
 15:         DoubleAnimation myDoubleAnimation = new DoubleAnimation();
 16:         public OpacityTriggeredBehavior()
 17:         {
 18: 
 19:             // set up the animation properties 
 20:             myDoubleAnimation.To = 0.2;
 21:             myDoubleAnimation.Duration = new Duration(TimeSpan.FromSeconds(1));
 22:             myDoubleAnimation.AutoReverse = true;
 23:             myDoubleAnimation.RepeatBehavior = RepeatBehavior.Forever;
 24: 
 25:             // the target of the animation is the AssociatedObject
 26:             //  i.e the object the behavior is attached to
 27:             Storyboard.SetTargetProperty(myDoubleAnimation,
 28:                 new PropertyPath(FrameworkElement.OpacityProperty));
 29:             myStoryboard.Children.Add(myDoubleAnimation);
 30:         }
 31: 
 32: 
 33:         protected override void OnAttached()
 34:         {
 35:             base.OnAttached();
 36:             // respond to mouse events to control the behavior
 37:             this.AssociatedObject.MouseEnter += new MouseEventHandler(AssociatedObject_MouseEnter);
 38:             this.AssociatedObject.MouseLeave += new MouseEventHandler(AssociatedObject_MouseLeave);
 39:         }
 40:         protected override void OnDetaching()
 41:         {
 42:             base.OnDetaching();
 43:             this.AssociatedObject.MouseEnter -= new MouseEventHandler(AssociatedObject_MouseEnter);
 44:             this.AssociatedObject.MouseLeave -= new MouseEventHandler(AssociatedObject_MouseLeave);
 45:         }
 46: 
 47:         void AssociatedObject_MouseEnter(object sender, MouseEventArgs e)
 48:         {
 49:             FrameworkElement fe = sender as FrameworkElement;
 50:             Storyboard.SetTarget(myDoubleAnimation, fe);
 51:             myStoryboard.Begin();
 52:         }
 53:         void AssociatedObject_MouseLeave(object sender, MouseEventArgs e)
 54:         {
 55:             myStoryboard.Stop();
 56:         }
 57:     }
 58: }

Listing 5: Behavior with triggers

[2009.09.13].03.Sample.of.actions.and.behaviors

Fig. 3. Sample of actions and behaviors

Download the Code!




No comments: