【问题标题】:WPF - Delayed MultibindingWPF - 延迟多重绑定
【发布时间】:2011-05-08 01:43:54
【问题描述】:

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

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

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

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

但是,这不适用于 MultiBinding,我一直无法弄清楚如何制作一个适用于 MultiBinding 的产品。

我确实可以选择在代码隐藏中的事件中更改可见性,这会起作用,但如果有某种方法可以通过绑定系统来做到这一点,那就太好了。

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

编辑:Ray,为了让您的课程能够编译和运行,我必须进行一些修复。但是,仍然有问题,因为更新没有被传播。似乎只更新一次目标属性。

[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 的代码工作,但我已将其标记为答案,因为它会引导我找到一些可以工作的代码。有关我使用的代码,请参阅下面的答案。

【问题讨论】:

  • 这个问题帮助我解决了我的问题,非常感谢!
  • 看起来他们现在将延迟属性添加到多重绑定中。

标签: wpf multibinding


【解决方案1】:

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

【讨论】:

    【解决方案2】:

    注意这仅通过解释如何做来回答问题的“我一直无法弄清楚如何制作一个与 MultiBinding 一起工作的”部分。其他人可能会发现此信息很有用,因此我将其保留在此处并添加另一个回答主要问题的答案。


    将您链接到的DelayBinding 标记扩展更改为一个工作方式相同但使用MultiBinding 的DelayMultiBinding 类非常简单。

    在标记扩展中:

    1. 重命名为 DelayMultiBindingExtension
    2. 添加Collection&lt;BindingBase&gt;类型的Bindings属性
    3. 更改Converter 属性的类型
    4. ProvideValue 中,构造一个DelayMultiBinding 而不是DelayBinding,传入所有绑定。

    在延迟绑定类中:

    1. 重命名为 DelayMultiBinding
    2. 采用绑定数组而不是单个绑定
    3. 为每个属性添加值更改处理程序
    4. 像构建绑定一样构建 MultiBinding

    现在不要写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 类和此类都延迟对的更新,而不是对目标的更新。如果您想延迟对 target 的更新(您会这样做),请参阅我的其他答案。

    【讨论】:

    • 这绝对有助于为我指明正确的方向,但我不知道如何延迟对 UpdateTarget() 的调用。 MultiBinding 对 UpdateSource() 有 UpdateSourceTrigger,但对 UpdateTarget() AFAIK 没有类似的。
    • 另外,我不完全确定如何为源上的绑定属性添加值更改处理程序。
    • 好吧,我想我或多或少地实现了它,但是当我尝试在计时器中调用 UpdateTarget() 时出现异常。 "绑定分离后无法执行此操作。"
    • 好的,这样就是MultiBinding OneWay的时候出现的异常。当 MultiBinding 为 TwoWay 时,它就会消失。但是,随后它会立即开始使用 TwoWay 进行更新。无论如何,TwoWay 在这种情况下没有任何意义,因为 IsMouseOver 是只读的。
    • 我查看了您链接到的 DelayBinding 类,并将其用作您想要做什么的指南。我应该更仔细地阅读这个问题。您链接到的 DelayBinding 类不会延迟目标更新,它会延迟源更新。我上面给出的答案非常适合源更新,但不处理目标更新。我添加了另一个答案,解释了如何做你所要求的。
    【解决方案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>
    

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

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-05-24
        • 2012-05-12
        • 1970-01-01
        • 1970-01-01
        • 2010-11-13
        相关资源
        最近更新 更多