Dot-Net
如何每秒更新一次 WPF 綁定?
我想向使用者顯示自某些事件發生以來已經過去了多少秒。從概念上講,我的視圖模型具有如下屬性:
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(); } } }