In a previous post I looked at using the MarkupExtension to remove the need for adding Converters as resources, see MarkupExtension. For this post I’m going to look at implement a MarkupExtension to in place of a Binding, i.e.
Disclaimer: All the code listed works, but I’ve not tested in all scenarios
<TextBox Text="{Binding SomeProperty}"/>
What are we going to implement ?
WPF in .NET 4.5 has the concept of Delay property on a Binding which is used as follows
<TextBox Text="{Binding SomeProperty, Delay=500}"/>
This is used on a Binding to only update the source (after a change on the target) after a user-specified delay. The most obvious use of such a mechanism is when the user types into a search or filter text box and you don’t want to be searching and filtering for every change. Preferably code will wait for the user to pause for a short time then carry out the search or filter functionality.
Now .NET 4.0 doesn’t have such a property on the Binding class, so we’re going to implement a very basic approximation of the Binding class in .NET 4.0 (mainly because the current project I’m working on targets .NET 4.0).
Why can’t be simply inherit from the Binding class ?
Unfortunately we cannot simply inherit from the Binding class to add our new code. The ProvideValue method of the MarkupExtension is marked as sealed and not override-able. But hey, where would the fun be if it was all that easy…
Let’s write some code
By convention, as with .NET Attributes, we suffix our extension class name with “Extension” but when it’s used, in XAML, the “Extension” part can be ignored. So first up let’s create the barest of bare bones for our DelayBindingExtension.
[MarkupExtensionReturnType(typeof(object))] public class DelayBindingExtension : MarkupExtension { public override object ProvideValue(IServiceProvider serviceProvider) { // To be implemented return null; } }
Note: The MarkupExtensionReturnType simply gives WPF information on what to expect as the return type from the ProvideValue method.
Let’s take a quick look at how we expect to use this extension in XAML (which should look pretty similar to the .NET 4.5 implementation show earlier)
<TextBox Text="{markupExtensionTest:DelayBinding Path=MyProperty, Delay=500}"/>
Before we implement the ProvideValue method, let’s add the other binding properties which most people will expect to see
[MarkupExtensionReturnType(typeof(object))] public class DelayBindingExtension : MarkupExtension { public DelayBindingExtension() { } public DelayBindingExtension(PropertyPath path) { Path = path; } [ConstructorArgument("path")] public PropertyPath Path { get; set; } public IValueConverter Converter { get; set; } public object ConverterParameter { get; set; } public string ElementName { get; set; } public RelativeSource RelativeSource { get; set; } public object Source { get; set; } public bool ValidatesOnDataErrors { get; set; } public bool ValidatesOnExceptions { get; set; } [TypeConverter(typeof(CultureInfoIetfLanguageTagConverter))] public CultureInfo ConverterCulture { get; set; } public int Delay { get; set; } public override object ProvideValue(IServiceProvider serviceProvider) { // To be implemented return null; } }
Right, those properties will make our extension appear like a Binding object, obviously with our addition of the Delay property.
Now it’s time to implement the ProvideValue method. I’m going to use Reactive Extensions to handle the actual delay (throttling) of the property change events. I’ll list the code and then discuss it afterwards
public override object ProvideValue(IServiceProvider serviceProvider) { var pvt = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget; if (pvt == null) return null; var targetObject = pvt.TargetObject as DependencyObject; if (targetObject == null) return null; var targetProperty = pvt.TargetProperty as DependencyProperty; if (targetProperty == null) return null; var binding = new Binding { Path = Path, Converter = Converter, ConverterCulture = ConverterCulture, ConverterParameter = ConverterParameter, ValidatesOnDataErrors = ValidatesOnDataErrors, ValidatesOnExceptions = ValidatesOnExceptions, Mode = BindingMode.TwoWay, UpdateSourceTrigger = UpdateSourceTrigger.Explicit }; if (ElementName != null) binding.ElementName = ElementName; if (RelativeSource != null) binding.RelativeSource = RelativeSource; if (Source != null) binding.Source = Source; var expression = BindingOperations.SetBinding(targetObject, targetProperty, binding); var subject = new Subject<EventArgs>(); var descriptor = DependencyPropertyDescriptor.FromProperty( targetProperty, targetObject.GetType()); descriptor.AddValueChanged(targetObject, (sender, args) => subject.OnNext(args)); subject.Throttle(TimeSpan.FromMilliseconds(Delay)). ObserveOn(SynchronizationContext.Current). Subscribe(_ => expression.UpdateSource()); return targetObject.GetValue(targetProperty); }
Note: The first line where we see if the IServiceProvider supports the IProvideValueTarget interface uses the GetService method. If you use the serviceProvider as IProvideValueTarget way of obtaining the interface you’ll find the MarkupExtension will not be able to cast from an InstanceBuilderServiceProvider to the IProvideValueTarget meaning the code will return null and not the actual bound property at design time. At runtine all will work.
So the first few lines of code are all about getting the interfaces and objects we want before proceeding to the interesting part of the method. This is the point where we create a binding object and supply all the information it expects.
Notice also that for ElementName, RelativeSource and Source we check whether the user actually supplied these values before overwriting the default values. This is important. If we set one of these properties to null we might appear to be supplying the properties with the same value they already have (and ordinarily we’d expect nothing to change) but in fact we’ll be causing the Binding to change from an unset state to set and with the value null. This will stop the property using it’s default behaviour.
So for example setting Source to null (even though it will say it’s null), will stop it using any parent DataContext and ofcourse not bind to anything.
Note: we can return a property to it’s default state using DependencyProperty.UnsetValue
After creating the Binding and applying the properties we call BindingOperations.SetBinding to associate the binding with the target object and target property.
Next we’re going to basically connect to the target property value change events on it’s dependency property. This allows us to handle a dependency property’s value changed event in a generic way, so for a TextBox Text property we’ll respond to the TextChanged event whilst if we use this code on a CheckBox we’ll handle the IsChecked change event and so on.
I’m using the Reactive Extensions in this example as it makes things so much simpler. We’re going to create a Subject and then from this we’ll be using Throttle so we can implement the delay, i.e. Throttle will not call the Action associated with the Subscribe method until a period of inactivity on the property change event – the inactivity timeout obviously being the Delay we set, in milliseconds.
When the Subscribe method’s Action method is called we simply tell the binding expression to Update the source and the data is written to the view model.
You’ll notice we return the property value for the target object at the end of the method. Obviously if the property has data when the binding is first created, this will now be displayed when the control is instantiated.
References