I have an application that displays various alarms and statuses. Some alarms when tripped are supposed to display a timer counting up from the time in which it was activated. I have read through several implementations and even questions here on SO but nothing seems to work 100%.
The following solution comes the closest. The timer displays and it does update, but it only updates 1 second about every 30 to 45 seconds. The UI Property is bound to the TimeElapsed Property, setting the TimeStarted property is what starts everything.
public class Alarm : INotifyPropertyChanged
{
DispatcherTimer timer = null;
Stopwatch stopWatch = new Stopwatch();
public Alarm()
{
Application.Current.Dispatcher.Invoke(() =>
{
timer = new DispatcherTimer();
timer.Tick += timer_Tick;
timer.Interval = new TimeSpan(0, 0, 1);
}, DispatcherPriority.Normal);
}
private TimeSpan initialDifference;
private DateTime? timeStarted;
public DateTime? TimeStarted
{
get { return timeStarted; }
set
{
// If the value is new
if (timeStarted != value)
{
// If timeStarted was previously null then start the new timer
if (!timeStarted.HasValue)
{
timeStarted = value;
// Get the initial difference between Now and TimeStarted
initialDifference = DateTime.Now.Subtract(TimeStarted.Value);
//irolTimer = new System.Threading.Timer(TickTick, null, 1000, 1000);
Application.Current.Dispatcher.Invoke(() =>
{
stopWatch.Start();
timer.Start();
}, DispatcherPriority.Normal);
}
// If the timeStarted had a value but now its gone (stop the timer)
else if (timeStarted.HasValue && value == null)
{
if (stopWatch.IsRunning)
stopWatch.Stop();
timeStarted = value;
}
// If we already have a timer going but for some reason we just received a different start time
else if (timeStarted.HasValue && value != null)
{
timeStarted = value;
// Change the initial difference
initialDifference = DateTime.Now.Subtract(TimeStarted.Value);
}
OnPropertyChanged("TimeStarted");
}
}
}
private string timeElapsed = string.Empty;
public string TimeElapsed
{
get
{
return timeElapsed;
}
set
{
timeElapsed = value;
OnPropertyChanged("TimeElapsed");
}
}
void timer_Tick(object sender, EventArgs e)
{
if (stopWatch.IsRunning)
{
TimeSpan elapsed = stopWatch.Elapsed;
TimeSpan total = initialDifference + elapsed;
TimeElapsed = String.Format("{0:00}:{1:00}:{2:00}", total.Hours, total.Minutes, total.Seconds / 10);
}
}
}
Adding Dispatcher.Invoke to the Tick event causes the timer not to show at all. I've tried a few different implementations including using a regular System.Threading.Timer with no luck. The StopWatch seemed like overkill and I thought I could accomplish the same thing with the following but it caused it to stop working:
TimeElapsed = DateTime.Now.Subtract(TimeStarted.Value).ToString("hh:mm:ss");
Here is a screenshot, I had to edit it due to security concerns but I tried to add Text back for context. The item circled in Red is the text for the timer. The Xaml is straight forward as follows:
<StackPanel DockPanel.Dock="Left">
<TextBlock FontSize="18" Text="{Binding Path=DisplayName, IsAsync=True}" Style="{StaticResource Alarm}" Foreground="White" FontWeight="Bold" Padding="2,2,2,2" />
<TextBlock Text="{Binding Path=TimeElapsed, IsAsync=True}" Foreground="White" FontSize="12" FontWeight="Bold" />
</StackPanel>

Your code should work OK. To make sure I dropped it into an empty WPF project and created a simple list of alarms with a simple data template, and it worked as expected.
One thing I did notice though is that you performed an integer division on the number of elapsed seconds, as seen here:
TimeElapsed = String.Format("{0:00}:{1:00}:{2:00}", total.Hours, total.Minutes, total.Seconds / 10);
This causes the TimeElapsed property only update once every 10 seconds, as the string doesn't change until the total seconds reaches the next factor of 10.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With