Behaviors came into WPF via Expression Blend. They’re basically a standardized way of adding extra functionality to WPF classes using XAML. So why not use attached properties you might ask. I’ll discuss the differences as we go.
Let’s look at a real world example…
I’m using Infragistics XamDataGrid to display rows of data, but I would like a simple filtering mechanism so that when the user enters text into a text box, all columns are filtered to show data with the filter text in it. I also want it so that when the text box is cleared the filters are cleared. Then I want a Button to enable me to clear the filter text for me with the click of the button.
How might we implement this ? Well the title of the post is a giveaway, but let’s look at some other possibilities first
- We could write this in the code-behind but generally we try to stay clear of writing such code and instead would generally prefer use XAML to help make our code reusable across projects
- We could derive a new class from the XamDataGrid and add dependency properties, but this means the code will only be usable via our new type, so it’s not as useful across other projects as it requires anyone wanting to use a XamDataGrid to use our version
- We could use attached properties, which allow us to create “external” code, i.e. code not in code behind or in a derived class, which can work in conjunction with a XamDataGrid, but the problem here is that attached properties are written in static classes and we will want to store instance variables with the data (see my reasons for this requirement below). With a static class implementation we would have to handle the management of such data ourselves, not difficult, but not ideal.
The attached properties route looked promising – I’m going to need a property for the associated TextBox (acting as our filter text) and the Button (used to clear the filter) and ofcourse these may be different per instance of a XamDataGrid – I also need to handle the attachment and detachment of event handlers and any other possible state. As mentioned, we could implement such state management ourselves, but behaviors already give us this capability out of the box as they are created on a per instance basis.
So the best way for to think of a behavior is that it’s like attached properties but allows us to create multiple instances of the code and thus saves us a lot of the headaches that might occur managing multiple instance data.
Note: The code I’m about to show/discuss includes Reactive Extension code. I will not go into any depth on what it does or how it works but the main use here is to handle attachment and detachment of events and also to allow throttling of the input, this means as the user types in the filter text box, we do not update the filter until the user stops typing for half a second. This ensures we’re not continually updating the filters on the XamDataGrid as the user types
Creating a behavior
To create a behavior we simply create a class and derive it from the Behavior class which is part of the System.Windows.Interactivity namespace. The Behavior takes a generic argument which defines the type it can be used on. So to start off our code would look like this
public class XamDataGridFilterBehavior : Behavior<XamDataGrid> { protected override void OnAttached() { base.OnAttached(); } protected override void OnDetaching() { base.OnDetaching(); } }
So the key parts here (apart from the base class which has already been mentioned) are the OnAttached and OnDetaching overrides. So here we can attach and detach from events on the associated class (i.e. the XamDataGrid) and/or handle initialization/disposal of data/objects as required.
Before we look at a possible implementation of these methods, I wrote a simple list of requirements at the top of this post. One was the requirement for a TextBox to be associated with the XamDataGrid to act as the filter text and the other a Button to be associated to clear the filter. So let’s add the dependency properties to our class to implement these requirements.
public static readonly DependencyProperty FilterTextBoxProperty = DependencyProperty.Register( "FilterTextBox", typeof(TextBox), typeof(XamDataGridFilterBehavior)); public TextBox FilterTextBox { get { return (TextBox)GetValue(FilterTextBoxProperty); } set { SetValue(FilterTextBoxProperty, value); } } public static readonly DependencyProperty ResetButtonProperty = DependencyProperty.Register( "ResetButton", typeof(Button), typeof(XamDataGridFilterBehavior)); public Button ResetButton { get { return (Button)GetValue(ResetButtonProperty); } set { SetValue(ResetButtonProperty, value); } }
So nothing exciting there, just standard stuff.
Now to the more interesting stuff, let’s implement the OnAttached and OnDetaching code. As I’m using Reactive Extensions we’ll need to have two instance variables, both of type IDisposable to allow us to clean up/detach any event handling. Let’s see all the code
private IDisposable disposableFilter; private IDisposable disposableReset; protected override void OnAttached() { base.OnAttached(); var filter = FilterTextBox; if (filter != null) { disposableFilter = Observable.FromEventPattern<TextChangedEventHandler, TextChangedEventArgs>( x => filter.TextChanged += x, x => filter.TextChanged -= x). Throttle(TimeSpan.FromMilliseconds(500)). ObserveOn(SynchronizationContext.Current). Subscribe(_ => { var dp = AssociatedObject as DataPresenterBase; if (dp != null && dp.DefaultFieldLayout != null) { dp.DefaultFieldLayout.RecordFilters.Clear(); dp.DefaultFieldLayout.Settings.RecordFiltersLogicalOperator = LogicalOperator.Or; foreach (var field in dp.DefaultFieldLayout.Fields) { var recordFilter = new RecordFilter(field); recordFilter.Conditions.Add( new ComparisonCondition(ComparisonOperator.Contains, filter.Text)); dp.DefaultFieldLayout.RecordFilters.Add(recordFilter); } } }); } var reset = ResetButton; if (reset != null) { disposableReset = Observable.FromEventPattern<RoutedEventHandler, RoutedEventArgs>( x => reset.Click += x, x => reset.Click -= x). ObserveOn(SynchronizationContext.Current). Subscribe(_ => { FilterTextBox.Text = String.Empty; // whilst the above will clear the filter it's throttled so can // look delayed - better we clear the filter immediately var dp = AssociatedObject as DataPresenterBase; if (dp != null && dp.DefaultFieldLayout != null) { dp.DefaultFieldLayout.RecordFilters.Clear(); } }); } } protected override void OnDetaching() { base.OnDetaching(); if (disposableFilter != null) { disposableFilter.Dispose(); disposableFilter = null; } if (disposableReset != null) { disposableReset.Dispose(); disposableReset = null; } }
This post isn’t mean’t to be about using the RX library or Infragistics, but the basics are that when OnAttached is called we use the RX FromEventPattern method to create our event handler attachment/detachment points. In the case of the TextBox we attach to the KeyDown event on the TextBox, we throttle the Observable for half a second so (as previously mentioned) we don’t update the filters on every change of the TextBox, we delay for half a second to allow the user to pause and then we filter. We also ensure the Subscribe code is run on the UI thread (well as the code that call OnAttached will be on the UI thread we’re simply observing on the current thread, which should be the UI thread). In the Subscribe method we get the AssociatedObject, this is where our Behavior generic argument comes in. The AssociatedObject is the object this behavior is applied to (we’ll see a sample of the XAML code next). Now we clear any current filters and create new ones based upon the supplied TextBox Text property. Finally we connect to the Click event on the supplied ResetButton. When the button is pressed we clear the FilterText and clear the filters.
In the code above I felt that the UX of a delay between clearing the FilterText and the filters clearing (the half a second delay) didn’t feel right when the user presses a button, so in this instance we also clear the filters immediately.
The OnDetaching allows us cleanup, so we dispose of our Observables and that will detach our event handlers and nicely clean up after usage.
How do we use this code in XAML
Finally, we need to see how we use this code in our XAML, so here it is
<TextBox x:Name="filter"/> <Button Content="Reset" x:Name="reset" /> <XamDataGrid> <i:Interaction.Behaviors> <controls:XamDataGridFilterBehavior FilterTextBox="{Binding ElementName=filter}" ResetButton="{Binding ElementName=reset}" /> </i:Interaction.Behaviors> </XamDataGrid>
And that’s it, now we have a reusable class which a designer could use to add “behavior” to our XamDataGrid.