DelayBinding and DelayMultiBinding with Source and Target delay

In a recent project I had the need for a Binding that supports specifying a delay for both source and target updates. This is what I ended up with (UpdateSourceDelay and/or UpdateTargetDelay can be specified)

<!-- Binding with Delay -->
<CheckBox IsChecked="{db:DelayBinding Path=BoolProperty,
                                      UpdateTargetDelay='00:00:00.50',
                                      UpdateSourceDelay='00:00:01.00'}"/>
<!-- MultiBinding with Delay -->
<cs:ColorSelector>
    <cs:ColorSelector.SelectedColor>
        <db:DelayMultiBinding Mode="TwoWay"
                              Converter="{StaticResource ColorConverter}"
                              UpdateSourceDelay="00:00:02">
            <Binding Path="Red" />
            <Binding Path="Green" />
            <Binding Path="Blue" />
        </db:DelayMultiBinding>
    </cs:ColorSelector.SelectedColor>
</cs:ColorSelector>

A project containing source code and demo usage can be downloaded in the link below
Download Project Here

Implemention details

Adding a delay option for source updates can be solved by setting UpdateSourceTrigger to Explicit combined with calling UpdateSource on the BindingExpression after the delay has passed as shown in this great article: DelayBinding: a custom WPF Binding.

However, an update to the target is always instant so I needed another Dependency Property to mirror the source and I actually ended up with two, one to mirror the source and one to mirror the target.
 
Extending Binding and MultiBinding

Binding can’t just be extended because ProvideValue is sealed. To workaround this I used a similar approach as described here: A base class for custom WPF binding markup extensions. I wanted a reusable solution that allowed extending both Binding and MultiBinding so I ended up with the following classes

• BindingBaseExtensionBase (BindingBase) – Extends MarkupExtension
• BindingExtensionBase (Binding) – Extends BindingBaseExtensionBase
• MultiBindingExtensionBase (MultiBinding) – Extends BindingBaseExtensionBase

And with those classes in place I could create the classes for DelayBinding and DelayMultiBinding

DelayBindingExtensionExtends BindingExtensionBase
DelayMultiBindingExtensionExtends MultiBindingExtensionBase
 
Dependency Properties and inheritance context for MarkupExtensions

Besides these custom Binding MarkupExtensions, dependency properties are needed so that means an object that derives from DependencyObject. But the Bindings above already derives from MarkupExtension so another class was created. I called this one DelayBindingController and it is used by both DelayBinding and DelayMultiBinding. However, for Bindings to resolve correctly within DelayBindingController an inheritance context is needed because it won’t be in the visual or logical tree. And how do we get an inheritance context? Just inherit from Freezable instead of DependencyObject.

public class DelayBindingController : Freezable
{
    // ...
}

So how does it know who what to inherit the context from? It needs to be connected to whatever DependencyObject this Binding was specified on. This was solved with an Attached Property of type FreezableCollection<DelayBindingController>. This property, in turn, was created in another class called DelayBindingManager. Everytime a DelayBinding is added to a DependencyObject, a corresponding DelayBindingController is added to the attached collection for that DependencyObject. And if it is removed, the DelayBindingController is removed from the collection as well.

public class DelayBindingControllerCollection :
    FreezableCollection<DelayBindingController { }

public class DelayBindingManager
{
    public static DependencyProperty DelayBindingControllersProperty =
        DependencyProperty.RegisterAttached(
            "DelayBindingControllers",
            typeof(DelayBindingControllerCollection),
            typeof(DelayBindingManager),
            new FrameworkPropertyMetadata(null));
    // ...
}

Delay Bindings in Code behind

At last, an option to add or remove DelayBindings through code behind was added in a static class called DelayBindingOperations.

public static class DelayBindingOperations
{
    public static void ClearBinding(DependencyObject targetObject,
                                    DependencyProperty targetProperty) // ...

    public static void SetBinding(DependencyObject targetObject,
                                  DependencyProperty targetProperty,
                                  BindingBaseExtensionBase delayBinding) // ...
}

A small class diagram for how this fits together can be viewed here
 
The DelayBindingController

Finally, a word on DelayBindingController. It has, like I said earlier, two dependency properties. SourcePropertyMirror and TargetPropertyMirror. TargetPropertyMirror binds TwoWay the target property and SourcePropertyMirror binds TwoWay to whatever was specified in the Binding. When the source value is updated a timer is started and when the time runs out the value is written to the target mirror property. The target property works the same way. It looks like this


public DelayBindingController(DependencyObject targetObject,
                              DependencyProperty targetProperty,
                              TimeSpan updateSourceDelay,
                              TimeSpan updateTargetDelay,
                              BindingBase binding,
                              BindingMode mode)
{
    TargetObject = targetObject;
    TargetProperty = targetProperty;
    if (mode == BindingMode.TwoWay || mode == BindingMode.OneWayToSource)
    {
        m_delaySource = true;
        m_updateSourceTimer = new DispatcherTimer();
        // ...
    }
    if (mode != BindingMode.OneWayToSource)
    {
        m_delayTarget = true;
        m_updateTargetTimer = new DispatcherTimer();
        // ...
    }

    Binding = binding;
}

public DependencyObject TargetObject { get; private set; }
public DependencyProperty TargetProperty { get; private set; }
public BindingBase Binding { get; private set; }

public void SetupBindingListeners()
{
    Binding targetBinding = new Binding()
    {
        Source = TargetObject,
        Path = new PropertyPath(TargetProperty),
        Mode = BindingMode.TwoWay
    };
    BindingOperations.SetBinding(this, TargetPropertyMirrorProperty, targetBinding);
    BindingOperations.SetBinding(this, SourcePropertyMirrorProperty, Binding);
}

private void SourcePropertyValueChanged()
{
    if (m_delayTarget == true)
    {
        m_updateSourceTimer.Stop();
        m_updateTargetTimer.Stop();
        m_updateTargetTimer.Start();
    }
}

private void TargetPropertyValueChanged()
{
    if (m_delaySource == true)
    {
        m_updateTargetTimer.Stop();
        m_updateSourceTimer.Stop();
        m_updateSourceTimer.Start();
    }
}

private void UpdateSourceTimer_Tick(object sender, EventArgs e)
{
    m_updateSourceTimer.Stop();
    object targetValue = GetValue(TargetPropertyMirrorProperty);
    this.SetValue(SourcePropertyMirrorProperty, targetValue);
}

private void UpdateTargetTimer_Tick(object sender, EventArgs e)
{
    m_updateTargetTimer.Stop();
    object sourceValue = GetValue(SourcePropertyMirrorProperty);
    this.SetValue(TargetPropertyMirrorProperty, sourceValue);
}

This became a pretty long post and I was probably to detailed about the implementation part. Anyway, there are many use cases for a Binding delay to the source. The use cases for a Binding delay to the target are fewer but it can be nice to have when needed so hopefully you’ll find this post useful. 😀

And for those of you who missed the download link to the demo project at the top, it can be downloaded here

Enyoj! 🙂
/Fredrik

Advertisements

5 Responses to DelayBinding and DelayMultiBinding with Source and Target delay

  1. James says:

    Fredrik,

    Thank you for your post. I have been looking for a memory leak free way to implement delay binding and this has been very helpful. I tried implementing your delayed binding approach and the delayed binding is working well; however, my validation code stopped working. Are you aware of something based on the delayed binding implementation that I need to do to get validation to work again? Thank you for your help.

  2. milasch says:

    Great… no, not great, awesome! Now we only need to implement binding validators… Is it easy to do?

  3. milasch says:

    Hm, it also doesn’t apparently work with a textbox in a listbox datatemplate.

    • XenoTheMorph says:

      if you want to use these within a style (without exceptions in .Net 4.0) you can add to the ProvideValue overrides in DelayBindingExtension & DelayMultiBindingExtension. e.g. return the MarkupExtension itself

      if (targetObject == null && service.TargetObject is Setter)
      return this;

  4. fatalbaboon says:

    Nice work, although it should update regardless of any delay when the focus is lost. Is there any easy way to do so?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: