WPF - 延迟多重绑定

时间:2010-11-10 18:55:58

标签: wpf multibinding

我有一个看起来像这样的多绑定:

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

而且,我希望能够在两个绑定的IsMouseOver之间添加一个延迟,并将Visibility设置为Collapsed。

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

但是,这对MultiBinding不起作用,而且我一直无法弄清楚如何使用MultiBinding。

我可以选择在代码隐藏中的事件中对Visibility进行更改,这样就行了,但是如果通过绑定系统有某种方法可以做到这一点会很好。

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

编辑:雷,为了让你的课程编译&amp;跑,我不得不做一些修复。但是,由于更新未被传播,因此仍然存在问题。它似乎只更新了一次目标属性。

[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:虽然我无法让Ray的代码工作,但我已将其标记为答案,因为它会导致我使用一些可行的代码。请参阅下面的答案,了解我使用的代码。

4 个答案:

答案 0 :(得分: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。

答案 1 :(得分:3)

注意这只回答了“我一直无法弄清楚如何制作一个与MultiBinding配合使用”的部分问题,并解释了如何解决问题。其他人可能会觉得这些信息很有用,所以我会把它留在这里并添加另一个回答主要问题的答案。


将链接到的DelayBinding标记扩展更改为与MultiBinding工作方式相同的DelayMultiBinding类非常简单。

在标记扩展名中:

  1. 重命名为DelayMultiBindingExtension
  2. 添加Bindings
  3. 类型的Collection<BindingBase>属性
  4. 更改Converter属性
  5. 的类型
  6. ProvideValue中,构建一个DelayMultiBinding而不是DelayBinding,传入所有绑定。
  7. 在延迟绑定类中:

    1. 重命名为DelayMultiBinding
    2. 取一系列绑定而不是单一绑定
    3. 为每个属性添加值更改处理程序
    4. 构建MultiBinding就像构建Binding一样
    5. 现在不写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类和此类都会延迟对的更新,而不是对目标的更新。如果您想延迟目标的更新(您这样做),请参阅我的其他答案。

答案 2 :(得分: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++;
    }
}

答案 3 :(得分: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