How to have a WPF binding update every second?

后端 未结 2 593
一生所求
一生所求 2020-12-17 22:24

I want to show the user how many seconds have passed since some event occurs. Conceptually, my view model has properties like this:

public DateTime Occurred         


        
相关标签:
2条回答
  • 2020-12-17 23:06

    A purely MVVM sulution

    Usage

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

    Dependencies

    Note that http://schemas.microsoft.com/expression/2010/interactivity namespace is available under a NuGet package called System.Windows.Interactivity.WPF. It will also be automatically added if you open the project in blend.

    Copy and paste code

    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)
  • 2020-12-17 23:20

    You could create a single DispatcherTimer statically for your view model, and then have all instances of that view model listen to the Tick event.

    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");
        }
    }
    
    0 讨论(0)
提交回复
热议问题