2010-11-10 69 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