Dot-Net

如何每秒更新一次 WPF 綁定?

  • March 16, 2019

我想向使用者顯示自某些事件發生以來已經過去了多少秒。從概念上講,我的視圖模型具有如下屬性:

public DateTime OccurredAtUtc { get; set; }

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

如果我將TextBlock.Text屬性綁定到SecondsSinceOccurrence,則會出現該值,但它是靜態的。時間的流逝並不反映這一事件的年齡越來越大。

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

PropertyChanged我可以在我的視圖模型中創建一個每秒觸發一次的計時器,但是 UI 中可能有很多這樣的元素(它是 an 中項目的模板ItemsControl),我不想創建那麼多計時器。

我對故事板動畫的了解不是很好。WPF 動畫框架可以在這種情況下提供幫助嗎?

純粹的 MVVM 解決方案

用法

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

依賴項

請注意, http: //schemas.microsoft.com/expression/2010/interactivity命名空間在名為System.Windows.Interactivity.WPF. 如果您在 blend 中打開項目,它也會自動添加。

複製和粘貼程式碼

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

引用自:https://stackoverflow.com/questions/949458