Add to Faves Add to MyAOL Add to Simpy Add to Delicious Add to Live Add to Digg Add to Newsvine Add to Reddit Add to Multiply Add to Blogmarks Add to Yahoo MyWeb Add to Slashdot Add to Mister Wong Add to Spurl Add to Furl Add to Link-a-Gogo Add to Yahoo Bookmarks Add to Twitter Add to Facebook Add to Diigo Add to Mixx Add to Segnalo Add to StumbleUpon Add to Ask Add to Backflip Add to Terchnorati Add to Google Bookmarks Add to MySpace
BlogProductsOpen SourceForumsAbout
Blog > July 2010 > Attaching Behaviors from the Expression Blend SDK using Styles

Attaching Behaviors from the Expression Blend SDK using Styles

The Expression SDK comes with a great feature called "Behaviors" which allows the designer to attach a unit of functionality (like drag-and-drop) to a visual element. Expression comes bundled with a set of general purpose behaviors, and developers can add more to give the designers functionality that is missing.

Behaviors are awesome, and it sure would be nice to be able to define a style that can be applied to any particular button -- for example, maybe your designer wants certain buttons to "pop" or "wiggle" when the mouse hovers over them.

The best way do that would be to define a style (perhaps in a shared resource dictionary) that looks something like this:

<Style TargetType="{x:Type Button}">
  <Setter Property="[some property here]">
    <Setter.Value>
      <MyBehaviors:PopOnHoverBehavior />
    </Setter.Value>
  </Setter>
</Style>

The problem is that there isn't an attached property included in the Expression SDK to put in the "[some property here]" blank... But not to fear! It is easy to create an attached property to give us the ability to make this style work.

First, we'll define a new class to contain the new attached property:

#region using System.Windows;
using System.Windows.Interactivity;
#endregion

namespace LivingAgile.Common.WPF
{
    public class StylizedBehaviors
    {
    }
}

Next we'll add the definition for the new attached property:

#region Fields (public)
public static readonly DependencyProperty BehaviorsProperty = DependencyProperty.RegisterAttached(
    @"Behaviors",
    typeof ( StylizedBehaviorCollection ),
    typeof ( StylizedBehaviors ),
    new FrameworkPropertyMetadatanull, OnPropertyChanged ) );
#endregion
#region Static Methods (public)
public static StylizedBehaviorCollection GetBehaviors ( DependencyObject uie )
{
    return ( StylizedBehaviorCollection )uie.GetValue( BehaviorsProperty );
}

public static void SetBehaviors ( DependencyObject uie, StylizedBehaviorCollection value )
{
    uie.SetValue( BehaviorsProperty, value );
}
#endregion

Then we will add the "OnPropertyChanged" method:

#region Static Methods (private)
private static void OnPropertyChanged ( DependencyObject dpo, DependencyPropertyChangedEventArgs e )
{
    var uie = dpo as UIElement;

    if ( uie == null )
    {
        return;
    }

    BehaviorCollection itemBehaviors = Interaction.GetBehaviors( uie );

    var newBehaviors = e.NewValue as StylizedBehaviorCollection;
    var oldBehaviors = e.OldValue as StylizedBehaviorCollection;

    if ( newBehaviors == oldBehaviors )
    {
        return;
    }

    if ( oldBehaviors != null )
    {
        foreach ( var behavior in oldBehaviors )
        {
            int index = itemBehaviors.IndexOf( behavior );

            if ( index >= 0 )
            {
                itemBehaviors.RemoveAt( index );
            }
        }
    }

    if ( newBehaviors != null )
    {
        foreach ( var behavior in newBehaviors )
        {
            int index = itemBehaviors.IndexOf( behavior );

            if ( index < 0 )
            {
                itemBehaviors.Add( behavior );
            }
        }
    }
}
#endregion

This method does the bulk of the work. When a new collection of behaviors is assigned to the attached property, this method will remove any old behaviors that are no longer required from the visual element (though it is doubtful whether this is useful), and it will add any new behaviors to the visual element.

The last thing we need is to define the StylizedBehaviorCollection class:

#region
using System.Windows;
using System.Windows.Interactivity;
#endregion

namespace LivingAgile.Common.WPF
{
    public class StylizedBehaviorCollection : FreezableCollection<Behavior>
    {
        #region Methods (protected)
        protected override Freezable CreateInstanceCore ( )
        {
            return new StylizedBehaviorCollection( );
        }
        #endregion
    }
}

Now we are ready to go! The style from above can now be written as:

<Style TargetType="{x:Type Button}">
  <Setter Property="WPF:StylizedBehaviors.Behaviors">
    <Setter.Value>
      <WPF:StylizedBehaviorCollection>
        <MyBehaviors:PopOnHoverBehavior />
      </WPF:StylizedBehaviorCollection>
    </Setter.Value>
  </Setter>
</Style>

For this example, assuming you use the same namespaces that I did, you would need to define the WPF namespace in your XAML file as:

xmlns:WPF="clr-namespace:LivingAgile.Common.WPF"
And that is all there is to it! Enjoy.

What about Blend's Attached Triggers?

The Expression Blend SDK also includes a concept of attached triggers, and you may want to extend this design to add another attached property for assigning these triggers from styles. I leave this as an exercise for the reader. The code above can be used as a template and it would be very easy to add this feature!

Why it works...

You might be thinking that this solution won't work if the same style is applied to several visual elements since an instance of a behavior can only be attached to one visual element at a time. The answer is that anything that derives from Freezable, and stores all of its state using dependency properties, can be cloned (i.e. deep copied).

When WPF applies a style to a visual element, it inspects the setter's values to see whether they are freezables and whether they are frozen. If they are frozen, the same instance will be shared across all visual elements that implement the style, which is fine because frozen means it cannot change or vary. This really only applies to things like visual brushes that don't have any internal state that will vary based on the inheritance context, etc.

When WPF sees that the setter's value derives from Freezable and isn't frozen, it clones the instance defined in the style for each visual element that implements the style and assigns the cloned values to the visual elements. That is why it is critical that the collection class derives from FreezableCollection<>. If the collection class was just an ObservableCollection<>, we would not get this cloning behavior and the solution would break.

Posted: 7/27/2010 4:56:04 PM by Mark Smeltzer | with 1 comments
Filed under: .NET4.0, Behaviors, Expression, OpenSource, Styles, WPF, XAML, .NET


Comments
Blog - Attaching Behaviors from the Expression Blend SDK using Styles (Part 2)
Pingback from Blog - Attaching Behaviors from the Expression Blend SDK using Styles (Part 2).
In the previous post in this series, I introduce the reader to a new attached property that gives the ability to attach a collection of behaviors to an item through a style. Since that post, I have discovered a bug in the original implementation that prevented databindings inside the behavior to work correctly....
7/31/2010 3:46:30 AM

Recent Comments

10/25/2010
Mark Smeltzer
Beej, I received similar feedback from Antoine. Based on...

10/25/2010
Beej
In my WPF 4 project I was getting error: "Cannot set a...

10/25/2010
Saheer
Hi Mark, Will this work in Silverlight? I think Freezable...

8/24/2010
Mark Smeltzer
When you say that you are getting errors, are they compilat...

8/24/2010
Antoine
Thanks for your quick reply! I renamed the class to Inte...

8/23/2010
Mark Smeltzer
I have not had any problems with that. Could you post your ...

8/23/2010
Antoine
Hi, Thanks a lot for this, I found it interesting and tr...

7/31/2010
Blog - Attaching Behaviors from the Expression Blend SDK using Styles (Part 2)
In the previous post in this series, I introduce the reader...

6/17/2010
Mark Smeltzer
Werner, The latest release, 0.9.7.4, should fix the prob...

5/31/2010
Mark Smeltzer
Werner, Are you using AnkhSVN? I have created a patch...

|< <  1   2   > >| Results 1 - 10 of 11

Syndication

RSS
This web site uses Kentico CMS, the content management system for ASP.NET developers.