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.
Comments
Anonymous
August 25, 2006
Sorry it's taking me so long to get the posts out. The series turned out to be a little longer than I...Anonymous
September 15, 2006
In part 5, I talked about commands and how they are used for behavior. Now, I want to talk about a better...Anonymous
October 11, 2006
In part 5 , I talked about commands and how they are used for behavior. Now, I want to talk about a betterAnonymous
January 02, 2007
PingBack from http://www.orbifold.net/default/?p=595Anonymous
January 06, 2007
Sorry it's taking me so long to get the posts out. The series turned out to be a little longer than I