2010-11-10 80 views
6

我有一个multibinding看起来是这样的:WPF - 延迟Multibinding

<UserControl.Visibility> 
    <MultiBinding Converter="{StaticResource isMouseOverToVisibiltyConverter}"> 
     <Binding ElementName="otherElement" Path="IsMouseOver" /> 
     <Binding RelativeSource="{RelativeSource Self}" Path="IsMouseOver" /> 
    </MultiBinding> 
</UserControl.Visibility> 

而且,我希望能够IsMouseOver去假两个绑定之间添加延迟,能见度被设置为崩溃了。

我发现这个DelayBinding实现: http://www.paulstovell.com/wpf-delaybinding

但是,这并不对MultiBinding工作,我一直无法弄清楚如何使一个与MultiBinding工作。

我确实可以选择在代码隐藏的事件中对可见性进行更改,这样做会起作用,但如果有某种方法可以通过绑定系统执行此操作,那将会很不错。

有没有办法给MultiBinding添加延迟?

编辑:雷,为了让你的类编译&运行,我不得不做一些修复。但是,有些东西仍然是错误的,因为更新没有被传播。它似乎只更新一次目标属性。

[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:虽然我不能让雷的代码工作中,我将其标记为答案,因为它导致了我的一些代码,做的工作。查看我的答案,下面是我使用的代码。

+0

这个问题帮助我解决了我的问题,非常感谢! – worldpart 2017-10-17 20:30:59

回答

6

您链接到的DelayBinding类只会延迟源更新,而不是目标更新。推迟目标更新,这是你所要求的,更简单得多。这样的事情应该做的伎俩:

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; 
} 

工作原理:一个MultiBinding构成的,即有一个额外的约束力,对标记扩展本身ChangeCount属性。此外,标记扩展本身也被注册为转换器。无论何时源值改变,绑定评估和转换器被调用。这又调用“真实”转换器来计算该值。不是立即更新值,而是将其存储在_undelayedValue中,并返回以前的值(_delayedValue)。另外,如果值已更改,它将启动(或重新启动)计时器。当计时器触发时,将值复制到_delayedValue中并且增加ChangeCount,从而强制重新评估绑定。这次返回新的_delayedValue。

+0

请参阅编辑问题。 – Ashley 2010-11-15 20:23:24

+0

谢谢,这帮了很多! – Ashley 2010-11-15 20:45:08

3

注意这只回答“我一直无法弄清楚如何使一个与MultiBinding一起工作”的一部分,通过解释如何这样做。其他人可能会发现这些信息很有用,所以我会把它留在这里,并添加另一个答案来回答主要问题。


这是非常微不足道的改变你挂入DelayMultiBinding类的工作方式相同,但与MultiBinding的DelayBinding标记扩展。

在标记扩展:

  1. 重命名到DelayMultiBindingExtension
  2. 添加型Collection<BindingBase>
  3. 更改Converter属性的类型的Bindings财产
  4. ProvideValue,构建DelayMultiBinding代替传入所有Bindings。

在延迟绑定类:

  1. 重命名为DelayMultiBinding
  2. 绑定采取阵列,而不是单一的结合
  3. 增加价值的改变处理程序,每个属性
  4. 构建MultiBinding就像你建立的绑定

现在inste写MultiBinding的广告,写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> 

个人而言,我也将通过这两个类转换成一个单一的类,这是一个的MarkupExtension还负责定时清理。

注意,DelayBinding和这个类都延迟更新到,不更新到目标。如果你想延迟更新目标(你这样做),看看我的其他答案。

+0

这绝对有助于指向正确的方向,但我看不到如何延迟对UpdateTarget()的调用。 MultiBinding对UpdateSource()有UpdateSourceTrigger,但对于UpdateTarget()AFAIK没有任何相似之处。 – Ashley 2010-11-11 18:04:44

+0

另外,我不完全确定如何为源上的绑定属性添加值更改的处理程序。 – Ashley 2010-11-11 18:17:42

+0

嗯,我想我已经或多或少地实现了它,但是当我尝试在定时器中调用UpdateTarget()时发生异常。 “绑定分离时不能执行此操作。” – Ashley 2010-11-11 20:59:30

2

使用Ray的代码作为起点,我写了一些可行的代码,但并不完全优雅。

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

我在一个项目而回,所以我创建了两个标记扩展名为DelayBindingExtensionDelayMultiBindingExtension也有类似的要求。

它们像普通Bindings一样工作,除了可以指定UpdateSourceDelay和/或UpdateTargetDelay,它们都是TimeSpan属性。你的情况,你可以使用这样的

<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> 

的源代码和示例使用情况DelayBindingDelayMultiBinding可以下载here
如果你有兴趣的实施细节,你可以看看我的博客文章在这里:DelayBinding and DelayMultiBinding with Source and Target delay