2010-11-10 13 views
6

Ho un MultiBinding che sembra qualcosa di simile:WPF - Delayed MultiBinding

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

E, voglio essere in grado di aggiungere un ritardo tra IsMouseOver andando a false per entrambe le legature, e la visibilità che si imposta su crollato.

Ho trovato questa implementazione DelayBinding: http://www.paulstovell.com/wpf-delaybinding

Ma, questo non funziona per MultiBinding, e sono stato in grado di capire come fare quello che funziona con MultiBinding.

Ho la possibilità di apportare le modifiche a Visibility in eventi nel code-behind, e ciò funzionerebbe, ma sarebbe bello se ci fosse un modo per farlo attraverso il sistema di bind.

C'è un modo per aggiungere un ritardo a una Multibinding?

MODIFICA: Ray, per far funzionare la tua classe &, ho dovuto fare alcune correzioni. Tuttavia, qualcosa è ancora sbagliato, poiché gli aggiornamenti non vengono propagati. Sembra aggiornare solo la proprietà di destinazione una volta.

[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: Anche se non ho potuto ottenere il codice di Ray a lavorare, ho segnato come la risposta, perché portano a me un po 'di codice che fa il lavoro. Vedere la mia risposta qui sotto per il codice che ho usato.

+0

Questa domanda mi ha aiutato a risolvere il mio problema, grazie mille! – worldpart

risposta

6

La classe DelayBinding si è collegato al solo ritarda un aggiornamento sorgente, non un aggiornamento del bersaglio. Ritardare un aggiornamento del target, che è quello che stai chiedendo, è molto più semplice. Qualcosa del genere dovrebbe fare il trucco:

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

Come funziona: Un MultiBinding è costruito che ha uno in più vincolante, a una proprietà ChangeCount sulla estensione di markup in sé. Anche l'estensione markup si registra come il convertitore. Ogni volta che un valore di origine cambia, l'associazione valuta e viene chiamato il convertitore. Ciò a sua volta chiama il convertitore "reale" per calcolare il valore. Invece di aggiornare immediatamente il valore appena memorizza in _undelayedValue, e restituisce il valore precedente (_delayedValue). Inoltre, se il valore è cambiato, avvia (o riavvia) un timer. Quando il timer incendi il valore viene copiato in _delayedValue e ChangeCount viene incrementato, costringendo il legame di essere ri-valutati. Questa volta viene restituito il nuovo _delayedValue.

+0

Vedi modifica alla domanda. – Ashley

+0

Grazie, questo ha aiutato molto! – Ashley

3

Nota Questo risponde solo alla parte "Non sono riuscito a capire come realizzarne uno che funziona con MultiBinding" spiegando come farlo. Altri potrebbero trovare utile questa informazione, quindi la lascerò qui e aggiungerò un'altra risposta che risponda alla domanda principale.


è abbastanza banale per cambiare l'estensione di markup DelayBinding si è collegato al in una classe DelayMultiBinding che funziona allo stesso modo, ma con MultiBinding.

Nella estensione di markup:

  1. Rinomina per DelayMultiBindingExtension
  2. aggiungere la proprietà Bindings di tipo Collection<BindingBase>
  3. cambiare il tipo di proprietà Converter
  4. In ProvideValue, costruire una DelayMultiBinding invece di un DelayBinding, passando in tutti i binding.

In ritardo classe Binding:

  1. Rinomina per DelayMultiBinding
  2. Prendere serie di attacchi, invece di legare singolo
  3. a valore aggiunto cambiato gestori a ogni proprietà
  4. Costruire il MultiBinding proprio come hai costruito la rilegatura

Ora inste annuncio di scrivere MultiBinding, scrivere 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> 

Personalmente mi sarebbe anche pulirlo convertendo le due classi in una singola classe che è un MarkupExtension e gestisce anche il timer.

Nota che la classe DelayBinding e questa classe entrambi gli aggiornamenti indugio alle fonte, non gli aggiornamenti al bersaglio. Se si vuole ritardare gli aggiornamenti al bersaglio (che si fa), vedere la mia altra risposta.

+0

Ciò aiuta sicuramente a orientarmi nella giusta direzione, ma non vedo come ritardare la chiamata a UpdateTarget(). MultiBinding ha UpdateSourceTrigger per UpdateSource(), ma nulla di simile per UpdateTarget() AFAIK. – Ashley

+0

Inoltre, non sono del tutto sicuro di come aggiungere i gestori di valori modificati per la proprietà associata sull'origine. – Ashley

+0

Beh, penso di averlo più o meno implementato, ma sto ricevendo un'eccezione quando provo a chiamare UpdateTarget() nel timer. "Impossibile eseguire questa operazione quando l'associazione è staccata." – Ashley

2

Utilizzando il codice di Ray come punto di partenza, ho scritto un codice che funziona, ma non è del tutto elegante.

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

ho avuto un requisito analogo in un progetto un po 'indietro così ho creato due estensioni di markup chiamato DelayBindingExtension e DelayMultiBindingExtension.

Funzionano come normale Bindings con l'aggiunta che è possibile specificare UpdateSourceDelay e/o UpdateTargetDelay, entrambi i quali sono TimeSpan proprietà. Nel tuo caso si potrebbe usare in questo modo

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

Il codice sorgente e l'uso di esempio per DelayBinding e DelayMultiBinding può essere scaricato here.
Se sei interessato ai dettagli dell'implementazione, puoi controllare il mio post sul blog qui: DelayBinding and DelayMultiBinding with Source and Target delay

Problemi correlati