Skip to content

WPF Behaviors and Attached Properties

In this article I will introduce you into the concept of WPF Behaviors and Attached Properties and how they can help you to create reusable modular code.

Introduction

In order to understand Behaviors you need to understand Attached Properties because from a technical point of view Behaviors base on Attached Properties.

In the following sections I will introduce you into the following concepts and how they are related to each other:

  • Attached Properties
  • Attached Behaviors
  • Behaviors

Preconditions

Before we can start to work with behaviors we need to download the nuget package Microsoft.Xaml.Behaviors.Wpf and add it to our project.

Attached Properties

Attached Properties are a feature provided by WPF. It allows us to declare an Attached Property in any class and attach it to a WPF element. It is comparable to an extension method in C# with the difference that it doesn’t extend something by a new functionality but instead adds new information. The probably most commonly used Attached Properties are Grid.Row and Grid.Column. Using these Attached Properties you can add row and column information to a WPF element which actually doesn’t know anything about rows and columns.

<Window x:Class="BehaviorsTutorial.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:Behaviors="http://schemas.microsoft.com/xaml/behaviors" xmlns:local="clr-namespace:BehaviorsTutorial" xmlns:scrollviewer="clr-namespace:ScrollViewer;assembly=ScrollViewer" mc:Ignorable="d" Title="MainWindow" Height="350" Width="525"> <Grid> <Button Content="Push Me" Grid.Row="0"/> </Grid> </Window>
Code language: HTML, XML (xml)

In the listing above the Attached Property is named ‘Row’ and is declared in the class ‘Grid’.

The definition of a Attached Property is very similar to the definition of a Dependency Property.

There are only two differences:

  • Instead of ‘Register’ we call ‘RegisterAttached’
  • Instead of a .Net Property wrapper we declare a static Set and Get Method.
public class AttachedProperties : DependencyObject { public static string GetTag(DependencyObject d) { return (string)d.GetValue(TagProperty); } public static void SetTag(DependencyObject d, string value) { d.SetValue(TagProperty, value); } public static readonly DependencyProperty TagProperty = DependencyProperty.RegisterAttached( "Tag", typeof(string), typeof(AttachedProperties), new PropertyMetadata(string.Empty)); }
Code language: C# (cs)

The names of the static Get/Set methods must follow the schema above. That means, Get/Set followed by the property name. If that schema is not followed you will get an error when you try to use the Attached Property in xaml.

The Attached Property ‘Tag’ as shown above is supposed to add any Tag to a XAML element. However, it doesn’t add any new functionality. It only adds the new information ‘Tag’ to the XAML element. This is in contrast to Behaviors and Attached Behaviors which add functionality to a XAML element.

Attached Behaviors

We can extend the Attached Property above by adding a callback handler which is called when the Attached Property is attached to a XAML element. This callback handler will get passed a reference to the object we have been attached to. This allows us to change the functionality of the object. We can add event handlers, set properties, animate properties, change bindings, and so on.

public class AttachedProperties : DependencyObject { public static string GetTag(DependencyObject d) { return (string)d.GetValue(TagProperty); } public static void SetTag(DependencyObject d, string value) { d.SetValue(TagProperty, value); } public static readonly DependencyProperty TagProperty = DependencyProperty.RegisterAttached( "Tag", typeof(string), typeof(AttachedProperties), new PropertyMetadata(string.Empty, TagCallback)); private static void TagCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) { var fe = d as FrameworkElement; if(fe != null) { fe.GotFocus += Fe_GotFocus; fe.LostFocus += Fe_LostFocus; } } private static void Fe_LostFocus(object sender, RoutedEventArgs e) { ((FrameworkElement)sender).Margin = new Thickness(2, 2, 2, 2); } private static void Fe_GotFocus(object sender, RoutedEventArgs e) { ((FrameworkElement)sender).Margin = new Thickness(10, 10, 10, 10); } }
Code language: C# (cs)

In the listing above I added a callback handler which is called when the Attached Property is attached. In the callback I register an event handler for the events GotFocus and LostFocus and change the margin in response to a focus change event.

As you can see I actually changed the behavior of the XAML element where the Attached Property is attached. That’s why many people call this special type of Attached Property an Attached Behavior. However, from technical point of view both is the same.

In my opinion you should never implement Behaviors like this. You should use the Microsoft.Xaml.Behaviors framework which features some classes to help you to implement clean Behaviors.

Behaviors

Behaviors are common classes which are supposed to extend the behavior of any XAML element. A Behavior can be attached to a XAML element using a special Attached Property contained in the namespace Microsoft.Xaml.Behaviors. This Attached Property manages your Behavior and informs you when the Behavior is getting attached or detached.

To implement a new Behavior you need to derive from Microsoft.Xaml.Behaviors.Behavior and pass a template parameter. This template parameter is the base class of your target where the Behavior can be applied. If you pass FrameworkElement as template parameter, then you can attach your Behavior to every XAML element that derives from FrameworkElement.

The listing below shows an Attached Behavior from my article ‘Smooth Scroll Viewer’. If you want a ScrollViewer to scroll smoothly you can attach this Behavior.

public class SmoothScrollViewerBehavior : Behavior<System.Windows.Controls.ScrollViewer> { protected override void OnAttached() { base.OnAttached(); AssociatedObject.Loaded += ScrollViewerLoaded; } private void ScrollViewerLoaded(object sender, System.Windows.RoutedEventArgs e) { var property = AssociatedObject.GetType().GetProperty("ScrollInfo", BindingFlags.NonPublic | BindingFlags.Instance); property.SetValue(AssociatedObject, new ScrollInfoAdapter((IScrollInfo)property.GetValue(AssociatedObject))); } }
Code language: C# (cs)

The Behavior can be applied using the Attached Property Interaction.Behaviors.

<Window x:Class="BehaviorsTutorial.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:Behaviors="http://schemas.microsoft.com/xaml/behaviors" xmlns:local="clr-namespace:BehaviorsTutorial" xmlns:scrollviewer="clr-namespace:ScrollViewer;assembly=ScrollViewer" mc:Ignorable="d" Title="MainWindow" Height="350" Width="525"> <Grid> <ScrollViewer> <Behaviors:Interaction.Behaviors> <scrollviewer:SmoothScrollViewerBehavior/> </Behaviors:Interaction.Behaviors> <ItemsControl> <Button Content="Monday" Height="70" Width="800" Margin="10"/> <Button Content="Tuesday" Height="70" Width="800" Margin="10"/> <Button Content="Wednesday" Height="70" Width="800" Margin="10"/> <Button Content="Thursday" Height="70" Width="800" Margin="10"/> <Button Content="Friday" Height="70" Width="800" Margin="10"/> <Button Content="Saturday" Height="70" Width="800" Margin="10"/> <Button Content="Sunday" Height="70" Width="800" Margin="10"/> </ItemsControl> </ScrollViewer> </Grid> </Window>
Code language: HTML, XML (xml)
Copyright (c) by Thomas Kemp, 2021