2009-06-04 14 views
6

Desidero mostrare all'utente quanti secondi sono trascorsi da quando si verifica un evento. Concettualmente, la mia vista del modello ha proprietà come questo:Come avere un aggiornamento del binding WPF ogni secondo?

public DateTime OccurredAtUtc { get; set; } 

public int SecondsSinceOccurrence 
{ 
    get { return (int)(DateTime.UtcNow - OccurredAtUtc).TotalSeconds; } 
} 

Se mi legano una proprietà TextBlock.Text-SecondsSinceOccurrence, appare il valore, ma è statico. Il passare del tempo non riflette l'età crescente di questo evento.

<!-- static value won't update as time passes --> 
<TextBlock Text="{Binding SecondsSinceOccurrence}" /> 

ho potuto creare un timer nel mio modello vista che spara PropertyChanged ogni secondo, ma ci sono probabilmente molti di tali elementi nell'interfaccia utente (suo un modello per gli oggetti in un ItemsControl) e io non voglio per creare tanti timer.

La mia conoscenza dell'animazione con gli storyboard non è eccezionale. In questo caso, il framework di animazione WPF può essere d'aiuto?

risposta

4

Avere un timer per attivare periodicamente PropertyChanged evento è un modo per andare. Ma se hai un sacco di articoli in un ContentControl e la proprietà che vuoi aggiornare è nel ItemTemplate di quello ContentControl, ciò significa creare inutilmente più di 100 timer e farli sollevare allo stesso tempo PropertyChanged allo stesso tempo. Tuttavia, questo comportamento verrà comunque creato per ogni articolo quando utilizzato in un ItemsControl come ListBox.

Per questo motivo ho creato questo comportamento che verrà creato una sola volta per ogni associazione nel modello. È puramente MVVM.

Uso

<Label xmlns:b="clr-namespace:Lloyd.Shared.Behaviors" 
     xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" 
     Content="{Binding MyContent}" Width="80" Foreground="{Binding MyColor}"> 
    <i:Interaction.Behaviors> 
     <b:PeriodicBindingUpdateBehavior Interval="0:00:01" Property="{x:Static ContentControl.ContentProperty}" Mode="UpdateTarget" /> 
     <b:PeriodicBindingUpdateBehavior Interval="0:00:01" Property="{x:Static Control.ForegroundProperty}" Mode="UpdateTarget" /> 
    </i:Interaction.Behaviors> 
</Label> 

Dipendenze

noti che http://schemas.microsoft.com/expression/2010/interactivity spazio dei nomi è disponibile sotto un package NuGet chiamato System.Windows.Interactivity.WPF. Sarà anche aggiunto automaticamente se si apre il progetto in blend.

copia e incolla codice

using System; 
using System.Threading; 
using System.Threading.Tasks; 
using System.Windows; 
using System.Windows.Data; 
using System.Windows.Interactivity; 

namespace Lloyd.Shared.Behaviors 
{ 
    public class PeriodicBindingUpdateBehavior : Behavior<DependencyObject> 
    { 
     public TimeSpan Interval { get; set; } 
     public DependencyProperty Property { get; set; } 
     public PeriodicBindingUpdateMode Mode { get; set; } = PeriodicBindingUpdateMode.UpdateTarget; 
     private WeakTimer timer; 
     private TimerCallback timerCallback; 
     protected override void OnAttached() 
     { 
      if (Interval == null) throw new ArgumentNullException(nameof(Interval)); 
      if (Property == null) throw new ArgumentNullException(nameof(Property)); 
      //Save a reference to the callback of the timer so this object will keep the timer alive but not vice versa. 
      timerCallback = s => 
      { 
       try 
       { 
        switch (Mode) 
        { 
         case PeriodicBindingUpdateMode.UpdateTarget: 
          Dispatcher.Invoke(() => BindingOperations.GetBindingExpression(AssociatedObject, Property)?.UpdateTarget()); 
          break; 
         case PeriodicBindingUpdateMode.UpdateSource: 
          Dispatcher.Invoke(() => BindingOperations.GetBindingExpression(AssociatedObject, Property)?.UpdateSource()); 
          break; 
        } 
       } 
       catch (TaskCanceledException) { }//This exception will be thrown when application is shutting down. 
      }; 
      timer = new WeakTimer(timerCallback, null, Interval, Interval); 

      base.OnAttached(); 
     } 

     protected override void OnDetaching() 
     { 
      timer.Dispose(); 
      timerCallback = null; 
      base.OnDetaching(); 
     } 
    } 

    public enum PeriodicBindingUpdateMode 
    { 
     UpdateTarget, UpdateSource 
    } 

    /// <summary> 
    /// Wraps up a <see cref="System.Threading.Timer"/> with only a <see cref="WeakReference"/> to the callback so that the timer does not prevent GC from collecting the object that uses this timer. 
    /// Your object must hold a reference to the callback passed into this timer. 
    /// </summary> 
    public class WeakTimer : IDisposable 
    { 
     private Timer timer; 
     private WeakReference<TimerCallback> weakCallback; 
     public WeakTimer(TimerCallback callback) 
     { 
      timer = new Timer(OnTimerCallback); 
      weakCallback = new WeakReference<TimerCallback>(callback); 
     } 

     public WeakTimer(TimerCallback callback, object state, int dueTime, int period) 
     { 
      timer = new Timer(OnTimerCallback, state, dueTime, period); 
      weakCallback = new WeakReference<TimerCallback>(callback); 
     } 

     public WeakTimer(TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period) 
     { 
      timer = new Timer(OnTimerCallback, state, dueTime, period); 
      weakCallback = new WeakReference<TimerCallback>(callback); 
     } 

     public WeakTimer(TimerCallback callback, object state, uint dueTime, uint period) 
     { 
      timer = new Timer(OnTimerCallback, state, dueTime, period); 
      weakCallback = new WeakReference<TimerCallback>(callback); 
     } 

     public WeakTimer(TimerCallback callback, object state, long dueTime, long period) 
     { 
      timer = new Timer(OnTimerCallback, state, dueTime, period); 
      weakCallback = new WeakReference<TimerCallback>(callback); 
     } 

     private void OnTimerCallback(object state) 
     { 
      if (weakCallback.TryGetTarget(out TimerCallback callback)) 
       callback(state); 
      else 
       timer.Dispose(); 
     } 

     public bool Change(int dueTime, int period) 
     { 
      return timer.Change(dueTime, period); 
     } 
     public bool Change(TimeSpan dueTime, TimeSpan period) 
     { 
      return timer.Change(dueTime, period); 
     } 

     public bool Change(uint dueTime, uint period) 
     { 
      return timer.Change(dueTime, period); 
     } 

     public bool Change(long dueTime, long period) 
     { 
      return timer.Change(dueTime, period); 
     } 

     public bool Dispose(WaitHandle notifyObject) 
     { 
      return timer.Dispose(notifyObject); 
     } 
     public void Dispose() 
     { 
      timer.Dispose(); 
     } 
    } 
} 
+0

Questo sembra fantastico. Grazie. –

+0

Grazie mille! State cercando un modo pulito per risolvere questo. Non so se sto usando una vecchia versione di C# o cosa, ma ho dovuto cambiare 'if (weakCallback.TryGetTarget (out callback TimerCallback))' a 'Richiamata callback TimerCallback; if (weakCallback.TryGetTarget (out callback)) 'per farlo funzionare. – monoceres

+0

@monoceres Oh sì! è C# 7. Una volta provato, non puoi vivere senza di esso – fjch1997

8

È possibile creare un singolo DispatcherTimer in modo statico per il modello di visualizzazione, quindi disporre tutte le istanze di tale modello di visualizzazione per ascoltare l'evento Tick.

public class YourViewModel 
{ 
    private static readonly DispatcherTimer _timer; 

    static YourViewModel() 
    { 
     //create and configure timer here to tick every second 
    } 

    public YourViewModel() 
    { 
     _timer.Tick += (s, e) => OnPropertyChanged("SecondsSinceOccurence"); 
    } 
} 
+1

Speravo che sarebbe stato possibile avere un elemento (o associazione) che tirare questo periodicamente, piuttosto che avere sottostante notifica sorgente dati. Si può creare un binding personalizzato e aggiungere una proprietà 'RefreshPeriod'? In tal caso, anche le istanze di DispatcherTimer potrebbero essere raggruppate. –

+0

In effetti, sono anche interessato a farlo esclusivamente da XAML. Inoltre non ho abbastanza conoscenza dell'animazione. – buckley