2009-06-04 18 views
6

イベントが発生してから経過した秒数をユーザーに表示します。概念的には、私の見解モデルは、このような性質を持っている:私はSecondsSinceOccurrenceTextBlock.Textプロパティをバインドする場合毎秒WPFバインディングを更新する方法はありますか?

public DateTime OccurredAtUtc { get; set; } 

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

、値が表示されますが、それは静的です。時間の経過はこの出来事の年齢を反映していません。

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

私はPropertyChanged秒ごとに発射する私の見解モデルでタイマーを作成し、そこUI(ItemsControl内の項目のそのテンプレート)には多くのそのような要素である可能性が高いと私は望んでいない可能性がありその多くのタイマーを作成します。

ストーリーボードを使用したアニメーションについての私の知識はあまり良くありません。 WPFアニメーションフレームワークはこの場合に役立ちますか?

答えて

4

タイマーを定期的に起動させると、PropertyChangedイベントが発生します。しかし、ContentControlに多数のアイテムがあり、更新するプロパティがContentControlItemTemplateにある場合、これは100以上のタイマーを不必要に作成し、同時にすべてPropertyChangedを発生させることを意味します。ただし、この動作はItemsControlListBox)のように使用すると、すべてのアイテムに対して作成されます。

このような理由から、テンプレート内の バインディングごとに1回作成されるこの動作を作成しました。純粋にMVVMです。 http://schemas.microsoft.com/expression/2010/interactivity名前空間がSystem.Windows.Interactivity.WPFと呼ばれるNuGetパッケージの下で利用可能であることを

使用

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

依存

注意。ブレンドしてプロジェクトを開くと、自動的に追加されます。

コピー&ペーストコード

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

これは素晴らしいようです。ありがとう。 –

+0

ありがとう!これを解決するためのきれいな方法を探していました。古いC#バージョンを使用しているのか、それとも 'if(weakCallback.TryGetTarget(out TimerCallback callback))'を 'TimerCallbackコールバックに変更しなければならなかったのか分かりません。 if(weakCallback.TryGetTarget(out callback)) 'を実行します。 – monoceres

+0

@monoceresああええ!それはC#7です。一度試してみると、それなしでは生きられません。 – fjch1997

8

ビューモデルに静的に1つのDispatcherTimerを作成し、そのビューモデルのすべてのインスタンスに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

私はむしろ基礎となるデータソースの通知を有するよりも、定期的にこれを引くであろうと、要素(または結合)を有することが可能であったであろう期待していました。カスタムバインディングを作成し、 'RefreshPeriod'プロパティを追加できますか?そうであれば、DispatcherTimerインスタンスもプールできます。 –

+0

確かに、私はXAMLから純粋にそれをやることにも興味があります。私はアニメーションatmについて十分な知識も持っていません。 – buckley

関連する問題