Attaching behavior to an Avalon element in XAML

Sometimes you want to attach some behavior to an Avalon element by adding some event handlers, but without subclassing the element. An example of this is my most recent version of the layout animation code. In my original post, I had code in my code behind file that added this behavior:

            new PanelLayoutAnimator(_tilePanel);

I suggested that one way to allow this to be specified in Xaml would be to write a new element you could wrap the other element in. This would let you specify the behavior in Xaml, but you are left with a more complex visual tree, which hurts performance among other things. A couple of people alerted me to another technique that solves this problem. I think this technique is useful in many similar circumstances, so I thought I’d blog in more detail about it.

The technique makes use of attached property functionality. The Avalon property system allows you to register a property that can be attached to any element. This is what lets you put something like DockPanel.Dock=”Top” on a Button inside a DockPanel, even though Dock isn’t a property that buttons know about. It also makes use of the ability to register for a callback when a property changes. Here are the changes step by step…

First, we register a new property and provide a set function that allows it to be set in XAML. I’ve also set up a function to be called when the property changes on an item:

public static readonly DependencyProperty IsAnimationEnabledProperty

   = DependencyProperty.RegisterAttached("IsAnimationEnabled", typeof(bool), typeof(PanelLayoutAnimator),

        new FrameworkPropertyMetadata(new PropertyChangedCallback(OnIsAnimationEnabledInvalidated)));

public static void SetIsAnimationEnabled(DependencyObject dependencyObject, bool enabled)

{

    dependencyObject.SetValue(IsAnimationEnabledProperty, enabled);

}

We also need an attached property that we’ll use to see if an animator is attached to a panel. This is isn’t something that people should be setting in XAML, so we keep it private.

private static readonly DependencyProperty AttachedAnimatorProperty

   = DependencyProperty.RegisterAttached("AttachedAnimator", typeof(PanelLayoutAnimator), typeof(PanelLayoutAnimator));

And, we’ll support detaching an animator from a panel, so we need a Detach function.

public void Detach()

{

    if (_panel != null)

    {

        _panel.LayoutUpdated -= PanelLayoutUpdated;

        _panel = null;

    }

}

Finally, we’ll use the invalidation callback to add or remove animations depending on what it’s set to.

private static void OnIsAnimationEnabledInvalidated(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)

{

    Panel panel = dependencyObject as Panel;

    if (panel != null)

    {

        if ((bool)e.NewValue)

        {

            // Turn animations on for the panel if there's not already a PanelAnimator attached

            if (panel.ReadLocalValue(AttachedAnimatorProperty) == DependencyProperty.UnsetValue)

            {

                PanelLayoutAnimator animator = new PanelLayoutAnimator(panel);

                panel.SetValue(AttachedAnimatorProperty, animator);

            }

        }

        else

        {

            // clear animations

            if (panel.ReadLocalValue(AttachedAnimatorProperty) != DependencyProperty.UnsetValue)

            {

                PanelLayoutAnimator animator = (PanelLayoutAnimator)panel.ReadLocalValue(AttachedAnimatorProperty);

                animator.Detach();

                panel.SetValue(AttachedAnimatorProperty, DependencyProperty.UnsetValue);

            }

        }

    }

}

That’s it! Now, you can animate a panel with something as simple as:

<WrapPanel myapp:PanelLayoutAnimator.IsAnimationEnabled="true">

Isn’t that nice? This leaves the animation decision to the style and keeps it cleanly separated from the behavior. I hope this technique helps you solve a similar problem!

I’ve attached the latest version of PanelLayoutAnimator.cs.

Note: I tried binding the IsAnimationEnabled value to a checkbox, but it didn’t work for some reason. It may be because I am using an old Avalon, or I could be doing something else wrong. If anyone knows what’s going on, let me know.

PanelLayoutAnimator.cs