2009-06-04 3 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에 많은 그런 요소 (AN ItemsControl의 항목에 대한 템플릿)가 될 가능성이 있습니다 내가하지 않을 수 있습니다 그 많은 타이머를 만들 수 있습니다.

스토리 보드로 애니메이션에 대한 지식이 훌륭합니다. 이 경우 WPF 애니메이션 프레임 워크가 도움이 될 수 있습니까?

답변

4

가는 하나의 방법입니다. 그러나 ContentControl에 많은 항목이 있고 업데이트하려는 속성이 인 경우 ContentControl 일 때 불필요하게 100 개 이상의 타이머를 만들고 동시에 PropertyChanged을 모두 발생시키는 것을 의미합니다. 그러나 ItemsControl (ListBox)과 같이 사용하면 모든 항목에 대해이 동작이 계속 만들어집니다.

이 이유 때문에 템플릿에 바인딩마다 한 번만 생성되는이 동작을 만들었습니다. 순수한 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라는 NuGet 패키지에서 볼 수하는 것으로

. 또한 프로젝트를 블렌드로 열면 자동으로 추가됩니다.

복사 오히려 기본 데이터 소스 통지를하는 것보다, 주기적으로 당겨 것이다 나는 요소를 가질 수 있었을 것이다 희망 (또는 결합) 된

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 콜백))'을 사용하십시오. – monoceres

+0

@monoceres 오, 그래! C# 7입니다. 한번 시도해 본 후에는 그것을 사용할 수 없습니다. – fjch1997

8

뷰 모델에 대해 정적으로 DispatcherTimer을 만든 다음 해당 뷰 모델의 모든 인스턴스를 Tick 이벤트로 수신 할 수 있습니다. 주기적으로 PropertyChanged 이벤트를 트리거하기 위해 타이머를 갖는

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

관련 문제