I have a multibinding that looks something like this:
<UserControl.Visibility>
<MultiBinding Converter="{StaticResource isMouseOverToVisibiltyConverter}">
<Binding ElementName="otherElement" Path="IsMouseOver" />
<Binding RelativeSource="{RelativeSource Self}" Path="IsMouseOver" />
</MultiBinding>
</UserControl.Visibility>
And, I want to be able to add a delay between IsMouseOver going to false for both bindings, and the Visibility being set to Collapsed.
I found this DelayBinding implementation: http://www.paulstovell.com/wpf-delaybinding
But, that doesn't work for MultiBinding, and I've been unable to figure out how to make one that works with MultiBinding.
I do have the option of doing the changes to Visibility in events in the code-behind, and that would work, but it would be nice if there was some way to do this through the binding system.
Is there some way to add a delay to a MultiBinding?
EDIT: Ray, in order to get your class to compile & run, I had to make some fixes. However, something is still wrong, as the updates aren't being propagated. It seems to only update the target property once.
[ContentProperty("Bindings")]
public class DelayedMultiBindingExtension : MarkupExtension, IMultiValueConverter, INotifyPropertyChanged
{
public Collection<BindingBase> Bindings { get; private 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(); } }
private object _undelayedValue;
private object _delayedValue;
private DispatcherTimer _timer;
public int ChangeCount { get; private set; } // Public so Binding can bind to it
public DelayedMultiBindingExtension()
{
this.Bindings = new Collection<BindingBase>();
_timer = new DispatcherTimer();
_timer.Tick += _timer_Tick;
_timer.Interval = TimeSpan.FromMilliseconds(500);
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
var valueProvider = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
if (valueProvider != null)
{
var bindingTarget = valueProvider.TargetObject as DependencyObject;
var bindingProperty = valueProvider.TargetProperty as DependencyProperty;
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, Mode = BindingMode.OneWay });
var bindingExpression = BindingOperations.SetBinding(bindingTarget, bindingProperty, multi);
return bindingTarget.GetValue(bindingProperty);
}
return null;
}
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();
}
private void _timer_Tick(object sender, EventArgs e)
{
_timer.Stop();
_delayedValue = _undelayedValue;
ChangeCount++;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("ChangeCount"));
}
public event PropertyChangedEventHandler PropertyChanged;
}
EDIT2: Even though I couldn't get Ray's code to work, I've marked it as the answer because it lead to me some code that does work. See my answer below for the code I used.
Using Ray's code as a starting point, I've written some code that works, but isn't entirely elegant.
XAML:
DelayingMultiConverter:
I had a similar requirement in a project a while back so I created two markup extensions called
DelayBindingExtension
andDelayMultiBindingExtension
.They work like normal
Bindings
with the addition that you can specifyUpdateSourceDelay
and/orUpdateTargetDelay
, both of which areTimeSpan
properties. In your case you could use it like thisThe source code and sample usage for
DelayBinding
andDelayMultiBinding
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
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:
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:
Bindings
property of typeCollection<BindingBase>
Converter
propertyProvideValue
, construct aDelayMultiBinding
instead of aDelayBinding
, passing in all Bindings.In the delay binding class:
Now instead of writing
MultiBinding
, writeDelayMultiBindingExtension
: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.