2009-06-04 18 views
6

Quiero mostrar al usuario cuántos segundos han pasado desde que se produce algún evento. Conceptualmente, mi modelo de vista tiene propiedades similares:¿Cómo tener una actualización obligatoria de WPF cada segundo?

public DateTime OccurredAtUtc { get; set; } 

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

Si Ato una propiedad TextBlock.Text-SecondsSinceOccurrence, aparece el valor pero es estática. El paso del tiempo no refleja la edad creciente de este evento.

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

que podría crear un temporizador en mi modelo de vista que dispara PropertyChanged cada segundo, pero no es probable que haya muchos de estos elementos en la interfaz de usuario (su plantilla para artículos en una ItemsControl) y yo no quiero para crear tantos temporizadores.

Mi conocimiento de la animación con guiones gráficos no es muy bueno. ¿Puede el marco de animación de WPF ayudar en este caso?

Respuesta

4

Tener un temporizador para activar periódicamente PropertyChanged evento es una manera de ir. Pero si tiene muchos elementos en un ContentControl y la propiedad que desea actualizar está en el ItemTemplate de ese ContentControl, eso significa crear innecesariamente más de 100 temporizadores y hacer que eleven PropertyChanged todo al mismo tiempo. Sin embargo, este comportamiento se seguirá creando para cada elemento cuando se use en un ItemsControl como ListBox.

Por esta razón, creé este Comportamiento que solo se creará una vez por cada enlace en su Plantilla. También es 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> 

Dependencias

Tenga en cuenta que http://schemas.microsoft.com/expression/2010/interactivity espacio de nombres está disponible en un paquete denominado NuGet System.Windows.Interactivity.WPF. También se agregará automáticamente si abre el proyecto en combinación.

Copiar y pegar el código

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

Esto se ve fantástico. Gracias. –

+0

¡Muchas gracias! He estado buscando una manera limpia de resolver esto. No sé si estoy usando una versión anterior de C# o qué, pero tuve que cambiar 'if (weakCallback.TryGetTarget (out callback TimerCallback))' a 'TimerCallback callback; if (weakCallback.TryGetTarget (out callback)) 'para que funcione. – monoceres

+0

@monoceres ¡Oh sí! eso es C# 7. Una vez que lo has probado, no puedes vivir sin él – fjch1997

8

Puede crear un solo DispatcherTimer estáticamente para su modelo de vista, y luego tener todas las instancias de ese modelo de vista para escuchar el 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

Tenía la esperanza de que hubiera sido posible tener un elemento (o unión) que sacar esto periódicamente, en lugar de tener la notifica fuente de datos subyacente. ¿Se puede crear un enlace personalizado y agregar una propiedad 'RefreshPeriod'? Si es así, las instancias de DispatcherTimer también podrían agruparse. –

+0

De hecho, también estoy interesado en hacerlo exclusivamente de XAML. Tampoco tengo suficiente conocimiento sobre la animación atm. – buckley

Cuestiones relacionadas