WPF - Delayed Multibinding

I have multitasking 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 for the Visibility parameter set to Collapsed.

I found this implementation of DelayBinding: http://www.paulstovell.com/wpf-delaybinding

But this does not work for MultiBinding, and I could not figure out how to create one that works with MultiBinding.

I have the ability to make changes to Visibility in events in the code, and this will work, but it would be nice if there was some way to do this through the binding system.

Is there any way to add delay to MultiBinding?

EDIT: Ray, in order to get my class to compile and run, I had to make some corrections. However, something is still wrong as updates are not distributed. It seems that only once updates the target property.

 [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: Despite the fact that I could not get the Ray code to work, I marked it as an answer because it brought me code that really works. See my answer below for the code I used.

+6
wpf multibinding
source share
4 answers

The DelayBinding class that you contacted only delays the original update, not the target update. Delaying the targeted update you are asking for is much easier. 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: It creates a MultiBinding, which has one additional binding, to the ChangeCount property in the markup extension itself. Also, the markup extension itself is registered as a converter. Whenever the original value is changed, the binding estimate is called and the converter is called. This, in turn, calls the β€œreal” converter to calculate the value. Instead of updating the value immediately, it simply stores it in _undelayedValue and returns the previous value (_delayedValue). In addition, if the value has changed, it starts (or restarts) the timer. When the timer starts, the value is copied to _delayedValue, and ChangeCount is incremented, causing repackaging to be re-read. This time a new _delayedValue is back.

+6
source share

Note This only answers the question "I could not understand how to make work with MultiBinding" part of the question, explaining how to do it. Others may find this information useful, so I will leave it here and add another answer that answers the main question.


It is very trivial to change the markup extension of the DelayBinding you are associated with to the DelayMultiBinding class, which works the same way, but with MultiBinding.

In the markup extension:

  • Rename to DelayMultiBindingExtension
  • Add a Bindings property of type Collection<BindingBase>
  • Change the type of property Converter
  • In ProvideValue build a DelayMultiBinding instead of DelayBinding , passing all the Bindings.

In the delay binding class:

  • Rename to DelayMultiBinding
  • Take an array of bindings instead of a single binding
  • Add value of modified handlers to each property
  • Create a MultiBinding just as you created the binding

Now, instead of writing a 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 clear it by converting two classes into one class, which is MarkupExtension, and also handles a timer.

Note that the DelayBinding class and this class delay updating the source, rather than updating it. If you want to delay updates to the goal (what you are doing), see my other answer.

+3
source share

Using Ray code as a starting point, I wrote code that works, but not quite elegant.

XAML:

 <local:IsMouseOverToVisibilityConverter x:Key="isMouseOverToVisibiltyConverter" /> <local:DelayingMultiConverter x:Key="delayedIsMouseOverToVisibiltyConverter" Delay="00:00:00.500" Converter="{StaticResource isMouseOverToVisibiltyConverter}" /> ... <UserControl.Visibility> <MultiBinding Converter="{StaticResource delayedIsMouseOverToVisibiltyConverter}"> <Binding ElementName="otherElement" Path="IsMouseOver" /> <Binding RelativeSource="{RelativeSource Self}" Path="IsMouseOver" /> <Binding Source="{StaticResource delayedIsMouseOverToVisibiltyConverter}" Path="ChangeCount" /> </MultiBinding> </UserControl.Visibility> 

DelayingMultiConverter:

 internal class DelayingMultiConverter : IMultiValueConverter, INotifyPropertyChanged { private object undelayedValue; private object delayedValue; private DispatcherTimer timer; private int changeCount; public int ChangeCount { get { return this.changeCount; } private set { this.changeCount = value; this.NotifyPropertyChanged("ChangeCount"); } } public IMultiValueConverter Converter { get; set; } public CultureInfo ConverterCulture { get; set; } public object ConverterParameter { get; set; } public TimeSpan Delay { get { return this.timer.Interval; } set { this.timer.Interval = value; } } public DelayingMultiConverter() { this.timer = new DispatcherTimer(); this.timer.Tick += Timer_Tick; } 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(); } public event PropertyChangedEventHandler PropertyChanged; private void NotifyPropertyChanged(string info) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(info)); } } private void Timer_Tick(object sender, EventArgs e) { timer.Stop(); delayedValue = undelayedValue; ChangeCount++; } } 
+2
source share

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

They work like regular Bindings with the addition that you can specify UpdateSourceDelay and / or UpdateTargetDelay , both of which are TimeSpan properties. In your case, you can 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 use of the example for DelayBinding and DelayMultiBinding can be downloaded here . If you are interested in implementation details, you can find out about this blog post of mine : DelayBinding and DelayMultiBinding with source and target delay

+2
source share

All Articles