7

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

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

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

我发现了这个延迟绑定实现: 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:尽管我无法让 Ray 的代码正常工作,但我已将其标记为答案,因为它会引导我找到一些可以工作的代码。有关我使用的代码,请参阅下面的答案。

4

4 回答 4

7

您链接到的 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。

于 2010-11-12T22:16:11.810 回答
3

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.

于 2010-11-10T21:10:34.170 回答
3

不久前我在一个项目中有一个类似的要求,所以我创建了两个标记扩展名为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>

和 的源代码和示例用法DelayBinding可以DelayMultiBinding此处下载。
如果您对实现细节感兴趣,可以在这里查看我的博客文章:DelayBinding and DelayMultiBinding with Source and Target delay

于 2012-05-02T23:32:08.890 回答
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>

延迟多转换器:

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++;
    }
}
于 2010-11-15T20:41:17.660 回答