WPF - Delayed Multibinding WPF - Delayed Multibinding wpf wpf

WPF - Delayed Multibinding


The DelayBinding class you linked to only delays a source update, not a target update. To delay a target update, which is what you are asking for, is much simpler. Something like this should do the trick:

public class DelayedMultiBindingExtension : MarkupExtension, IMultiValueConverter, INotifyPropertyChanged{  public Collection<Binding> Bindings { get; set; }  public IMultiValueConverter Converter { get; set; }  public object ConverterParameter { get; set; }  public CultureInfo ConverterCulture { get; set; }  public BindingMode Mode { get; set; }  public UpdateSourceTrigger UpdateSourceTrigger { get; set; }  public object CurrentValue { get { return _delayedValue; } set { _delayedValue = _undelayedValue = value; _timer.Stop(); } }  object _undelayedValue;  object _delayedValue;  DispatcherTimer _timer;  public int ChangeCount { get; set; }  // Public so Binding can bind to it  public DelayedMultiBindingExtension()  {    _timer = new DispatcherTimer();    _timer.Tick += _timer_Tick;  }  public override object ProvideValue(IServiceProvider serviceProvider)  {    var multi = new MultiBinding { Converter = this, Mode = Mode, UpdateSourceTrigger = UpdateSourceTrigger };    foreach(var binding in Bindings)      multi.Bindings.Add(binding);    multi.Bindings.Add(new Binding("ChangeCount") { Source = this });    return multi;  }  public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)  {    object newValue =      Converter.Convert(        values.Take(values.Length-1).ToArray(),        targetType,        ConverterParameter,        ConverterCulture ?? culture);    if(!object.Equals(newValue, _undelayedValue))    {      _undelayedValue = newValue;      _timer.Stop();      _timer.Start();    }    return _delayedValue;  }  public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)  {    return      Converter.ConvertBack(value, targetTypes, ConverterParameter, ConverterCulture ?? culture)      .Concat(new object[] { ChangeCount }).ToArray();  }  void _timer_Tick(object sender, EventArgs e)  {    _timer.Stop();    _delayedValue = _undelayedValue;    ChangeCount++;    if(PropertyChanged!=null)      PropertyChanged(this, new PropertyChangedEventArgs("ChangeCount"));  }  public event PropertyChangedEventHandler PropertyChanged;}

How it works: A MultiBinding is constructed that has one extra binding, to a ChangeCount property on the markup extension itself. Also the markup extension itself registers as the converter. Whenever a source value changes the, binding evaluates and the converter is called. This in turn calls the "real" converter to compute the value. Instead of updating the value immediately it just stores it in _undelayedValue, and returns the previous value (_delayedValue). Also, if the value has changed it starts (or restarts) a timer. When the timer fires the value is copied into _delayedValue and ChangeCount is incremented, forcing the binding to be re-evaluated. This time the new _delayedValue is returned.


Note This only answers the "I've been unable to figure out how to make one that works with MultiBinding" part of the question by explaining how to do so. Others may find this information useful, so I will leave it here and add another answer that answers the main question.


It is pretty trivial to change the DelayBinding markup extension you linked to into a DelayMultiBinding class that works the same way but with MultiBinding.

In the markup extension:

  1. Rename to DelayMultiBindingExtension
  2. Add the Bindings property of type Collection<BindingBase>
  3. Change the type of the Converter property
  4. In ProvideValue, construct a DelayMultiBinding instead of a DelayBinding, passing in all Bindings.

In the delay binding class:

  1. Rename to DelayMultiBinding
  2. Take array of bindings instead of single binding
  3. Add value changed handlers to each property
  4. Build the MultiBinding just like you built the Binding

Now instead of writing MultiBinding, write DelayMultiBindingExtension:

<UserControl.Visibility>   <my:DelayMultiBindingExtension Delay="0:0:1" Converter="{StaticResource isMouseOverToVisibiltyConverter}">    <Binding ElementName="otherElement" Path="IsMouseOver" />     <Binding RelativeSource="{RelativeSource Self}" Path="IsMouseOver" />   </my:DelayMultiBindingExtension> </UserControl.Visibility> 

Personally I would also clean it up by converting the two classes into a single class which is a MarkupExtension and also handles the timer.

Note that the DelayBinding class and this class both delay updates to the source, not updates to the target. If you want to delay updates to the target (which you do), see my other answer.


I had a similar requirement in a project a while back so I created two markup extensions called DelayBindingExtension and DelayMultiBindingExtension.

They work like normal Bindings with the addition that you can specify UpdateSourceDelay and/or UpdateTargetDelay, both of which are TimeSpan properties. In your case you could use it like this

<UserControl.Visibility>    <db:DelayMultiBinding Converter="{StaticResource yourConverter}"                          UpdateTargetDelay="00:00:01">        <Binding ElementName="otherElement" Path="IsMouseOver" />        <Binding RelativeSource="{RelativeSource Self}" Path="IsMouseOver" />    </db:DelayMultiBinding></UserControl.Visibility>

The source code and sample usage for DelayBinding and DelayMultiBinding can be downloaded here.
If you're interested in the implementation details, you can check out my blog post about it here: DelayBinding and DelayMultiBinding with Source and Target delay