Custom Binding MarkupExtension

In my previous post MarkupExtension revisited I looked at creating a replacement “Binding” MarkupExtension.

As usual, after posting that I decided to make some changes.

Basically I wanted to create a base class which will allow me to easily create a Binding style MarkupExtension and then the subclass need only worry about the specifics for what it’s going to do. So I present the BindingBaseExtension class.

[MarkupExtensionReturnType(typeof(object))]
public abstract class BindingBaseExtension : MarkupExtension
{
   protected BindingBaseExtension()
   {			
   }

   protected BindingBaseExtension(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 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);

      PostBinding(targetObject, targetProperty, binding, expression);

      return targetObject.GetValue(targetProperty);
   }

   protected abstract void PostBinding(DependencyObject targetObject, 
      DependencyProperty targetProperty, Binding binding,
      BindingExpressionBase expression);
}

Now I’m not going to go over the code as you can read all about it in the MarkupExtension revisited post, but suffice to say we now inherit from this class and add our extension’s code to an implementation of the PostBinding method (I’m not mad on the method name but for now it’s the best I could think of).

So our DelayBindingExtension will now look like this

public class DelayBindingExtension : BindingBaseExtension
{
   private IDisposable disposable;

   public DelayBindingExtension()
   {			
   }

   public DelayBindingExtension(PropertyPath path) 
      : base(path)
   {
   }

   public int Delay { get; set; }

   protected override void PostBinding(DependencyObject targetObject, 
      DependencyProperty targetProperty, Binding binding,
      BindingExpressionBase expression)
   {
      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());
   }
}

Note: Please be aware that this implementation of the delay binding extension has a possible memory leak. Basically we’re rooting the object with the call to AddValueChanged and not calling RemoveValueChanged the remove this link.