WPF / C# (No MVVM) Timer with Countdown Clock - c#

Good day all,
I'm struggling with a coding problem and I need some help. My process requires a 300 second (5 min) timer that fires an event to refresh a Grid Control. That works just fine. The problem is, I need to countdown the 5 mins/300 seconds to the user so they know the next fresh happens in "X" seconds. The goal is to countdown, refresh, and show the user the next refresh.
All code samples below are examples of things I tried.
I have code that works for the refresh, but something strange happens after the first execution. The timer counts down to 0, refreshes, and then restarts at 300 seconds again (good), but each tick down flashes a second timer behind it. So I see 300, 299, 298, ... and then another 300, 299, 298, ...; therefore, it looks like 300, 299, 298, 300, 297, 299, 296, 298, etc. It's nauseating to watch. Let alone trying to watch 20 minutes in...
My 300-second timer is a System.Timers.Timer int eh below example (Reminder, this works):
public partial class MasterControl
{
private Timer _t;
public MasterControl()
{
InitializeComponent();
}
public void Dispose()
{
_t?.Dispose();
_handle?.Dispose();
}
private void Master_OnLoaded(object sender, RoutedEventArgs e)
{
var fd = new FillMaster();
GridMaster.ItemsSource = fd.GridPopulate("TblName", App.UserName);
_t = new Timer();
_t.Elapsed += OnTimedEvent;
_t.Interval = 300000;
_t.Enabled = true;
}
private void OnTimedEvent(object source, ElapsedEventArgs e)
{
Dispatcher.Invoke(() =>
{
try
{
var fd = new FillMaster();
GridMaster.ItemsSource = fd.GridPopulate("TblName", App.UserName);
}
catch (SqlException)
{
/* swallow */
}
});
}
}
What I tried to do was add a countdown that fills a label.
I added
private TimeSpan _time;
private DispatcherTimer _timer;
And I adjusted the OnTimedEvent code to the below example and it didn't work. This is where it started to double up on refresh. I tried GC to see if that would work. No dice.
private void OnTimedEvent(object source, ElapsedEventArgs e)
{
Dispatcher.Invoke(() =>
{
try
{
var fd = new FillMaster();
GridMaster.ItemsSource = fd.GridPopulate("IntakeCheckList", App.UserName);
TbCountDown.Content = "";
_time = TimeSpan.FromSeconds(60);
_timer = new DispatcherTimer(new TimeSpan(0, 0, 1), DispatcherPriority.Normal, delegate
{
TbCountDown.Content = _time.ToString("c");
if (_time == TimeSpan.Zero)
{
_timer.Stop();
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
}
_time = _time.Add(TimeSpan.FromSeconds(-1));
}, Application.Current.Dispatcher);
_timer.Start();
}
catch (SqlException)
{
/* swallow */
}
});
}
I also found some code that casued the same problem.
private void Countdown(int count, TimeSpan interval, Action<int> ts)
{
var dt = new DispatcherTimer {Interval = interval};
dt.Tick += (_, a) =>
{
if (count-- == 0)
dt.Stop();
else
ts(count);
};
ts(count);
dt.Start();
}
Then I added the following to the to OnTimedEvent
Countdown(30, TimeSpan.FromSeconds(5), cur => TbCountDown.Content = cur.ToString());
As seen here
private void OnTimedEvent(object source, ElapsedEventArgs e)
{
Dispatcher.Invoke(() =>
{
Countdown(30, TimeSpan.FromSeconds(5), cur => TbCountDown.Content = cur.ToString());
try
{
var fd = new FillMaster();
GridMaster.ItemsSource = fd.GridPopulate("IntakeCheckList", App.UserName);
}
catch (SqlException)
{
/* swallow */
}
});
}
This also failed with the exact same problem.
Ultimately, is there a way to get the countdown from the System.Timers.Timer or something else you can help me with?
Thank you!

You don't actually need more than a simple DispatcherTimer and a DateTime that is cyclically reset to the current time + 300 seconds.
public partial class MainWindow : Window
{
private readonly DispatcherTimer timer = new DispatcherTimer();
private DateTime endTime;
public MainWindow()
{
InitializeComponent();
timer.Interval = TimeSpan.FromSeconds(1);
timer.Tick += new EventHandler(OnTimerTick);
timer.Start();
}
private void OnTimerTick(object sender, EventArgs e)
{
var now = DateTime.Now;
if (endTime < now)
{
endTime = now.AddSeconds(300);
}
label.Content = (endTime - now).ToString(#"mm\:ss");
}
}

Related

dispatcherTimer not restarting

I'm implementing a countdown in my app
private async void Window_Activated(object sender, WindowActivatedEventArgs args)
{
dispatcherTimer = new DispatcherTimer();
dispatcherTimer.Tick += dispatcherTimer_Tick;
dispatcherTimer.Interval = new TimeSpan(0,1,0);
dispatcherTimer.Start();
}
private void dispatcherTimer_Tick(object sender, object e)
{
TimeSpan timeSpan = new TimeSpan(blockTime.Hours, blockTime.Minutes, blockTime.Seconds);
if (timeSpan != TimeSpan.Zero)
{
timeSpan = timeSpan.Subtract(TimeSpan.FromMinutes(1));
Countdown_TexBlock.Text = String.Format("{0}:{1}", timeSpan.Hours, timeSpan.Minutes);
dispatcherTimer.Start();
}
else
{
dispatcherTimer.Stop();
}
}
The code works but only one time
For example I put 15 minutes in the blocktime (the time that the countdown will be running)
after a minute the countdown.text would be 0:14.
So only works after the first minute
Is not supposed to be restarted with dispatcher.start()
In the code that you posted, I don't see the blockTime variable being changed to any other value than it has in the beginning. This means that on every tick of dispatchTimer the value of the timeSpan.Subtract expression will always evaluate to the same 14 minutes. In your code, that 14 minutes is assigned to a local vaiable that is disposed when the tick is over. This gives the appearance that the dispatchTimer has stopped issuing Tick when it hasn't.
Here's what I ran that works as expected (for testing, I changed the minutes to seconds to make it observable in a reasonable time).
public sealed partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
// Create the dispatch timer ONCE
dispatcherTimer = new DispatcherTimer();
dispatcherTimer.Tick += DispatcherTimer_Tick;
dispatcherTimer.Interval = TimeSpan.FromSeconds(1);
// This will restart the timer every
// time the window is activated
this.Activated += (sender, e) =>
{
startOrRestartDispatchTimer();
};
}
private void startOrRestartDispatchTimer()
{
dispatcherTimer.Stop(); // If already running
blockTime = TimeSpan.FromSeconds(15);
Countdown_TexBlock.Text = blockTime.ToString();
dispatcherTimer.Start();
}
private void DispatcherTimer_Tick(object sender, object e)
{
if (blockTime > TimeSpan.Zero)
{
blockTime = blockTime.Subtract(TimeSpan.FromSeconds(1));
Countdown_TexBlock.Text = blockTime.ToString();
if (blockTime == TimeSpan.Zero)
{
Countdown_TexBlock.Text = "Done";
dispatcherTimer.Stop();
}
}
}
TimeSpan blockTime = TimeSpan.FromSeconds(15);
private DispatcherTimer dispatcherTimer;
// This will restart the timer when the button is clicked.
private void buttonRestart_Click(object sender, RoutedEventArgs e) =>
startOrRestartDispatchTimer();
}

label disappears if the countdown timer gets zero

i have a Textblock (tbTime) which shows the countdown timer. when it gets zero, the Textblock (tbTime) shows still the zero values. But i wanna make this Textblock (tbTime) disappear, after the countdown timer reaches to zero.
Could anyone help me, please?
C# Code
public partial class InfoScreen : Window
{
DispatcherTimer timer;
TimeSpan time;
public InfoScreen()
{
InitializeComponent();
AppearingNext();
time = TimeSpan.FromSeconds(10);
timer = new DispatcherTimer(new TimeSpan(0, 0, 1), DispatcherPriority.Normal, delegate
{
tbTime.Text = time.ToString("ss");
if (time == TimeSpan.Zero) timer.Stop();
time = time.Add(TimeSpan.FromSeconds(-1));
}, Application.Current.Dispatcher);
if (tbTime.Text == "00") //My code doesn't work!
{
tbTime.Visibility = Visibility.Hidden;
}
}
private async void AppearingNext()
{
await Task.Delay(TimeSpan.FromSeconds(10));
VisbilityPanel.Visibility = Visibility.Visible;
}
private void AgreementClick(object sender, RoutedEventArgs e)
{
var registration = new Reset_Register();
registration.Show();
Close();
}
}
One solution is put your code that hide the textblock inside the callback of the timer:
timer = new DispatcherTimer(new TimeSpan(0, 0, 1), DispatcherPriority.Normal, delegate {
tbTime.Text = time.ToString("ss");
if (tbTime.Text == "00") {
tbTime.Visibility = Visibility.Hidden;
}
if (time == TimeSpan.Zero) timer.Stop();
time = time.Add(TimeSpan.FromSeconds(-1));
}, Application.Current.Dispatcher);

Making an alarm with C# to ring on specific constant times, throughout a day

I'm trying to make an alarm to ring on specific times ( for example; every five minutes after 9:30 am till 4 pm). So, I want to write a code that rings in 9:30 and 9:35 and ... . but with every approach ultimately I get an error. in my code I have a string that includes the times, but I cannot use that string or group in a if(...) to make the alarm. it's fine with just one number with var..., where am I wrong?
{
public partial class Form1 : Form
{
System.Timers.Timer timer;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
timer = new System.Timers.Timer();
timer.Interval = 1000;
timer.Elapsed += Timer_Elapsed;
}
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
DateTime currentTime = DateTime.Now;
string[] DailyTime = { 093000, 093500 };
if (((currentTime.Hour * 10000) + (currentTime.Minute *100) + currentTime.Second) == DailyTime)
{
timer.Stop();
try
{
SoundPlayer player = new SoundPlayer();
player.SoundLocation = #"C:\Windows\Media\Time interval alarm\FiveH.wav";
player.Play();
private void Form1_Load(object sender, EventArgs e)
{
timer = new System.Timers.Timer();
timer.Interval = 1000 * 60 * 5; //5 minutes
timer.Elapsed += Timer_Elapsed;
InitializeTimer(); //this method makes sure the timer starts at the correct time
}
The timer initialization method:
private async void InitializeTimer()
{
while (!timer.Enabled) //keep looping until timer is initialized
{
//if the minute is a multiple of 5 (:00, :05, ...) start the timer
if (DateTime.Now.Minute % 5 == 0 && DateTime.Now.Second == 0)
{
timer.Start();
TriggerAlarm(); //trigger the alarm initially instead of having to wait 5min
}
else
{
await Task.Delay(100);
}
}
}
You could store the times and alarm paths in a dictionary:
Dictionary<TimeSpan, string> dict = new Dictionary<TimeSpan, string>()
{
{ new TimeSpan(9, 30, 0), #"C:\Windows\Media\Time interval alarm\FiveH.wav" },
{ new TimeSpan(9, 35, 0), #"C:\Windows\Media\Time interval alarm\Whatever.wav" },
{ new TimeSpan(9, 40, 0), #"C:\Windows\Media\Time interval alarm\Whatever1.wav" },
{ new TimeSpan(9, 45, 0), #"C:\Windows\Media\Time interval alarm\Whatever2.wav" },
//...
{ new TimeSpan(16, 0, 0), #"C:\Windows\Media\Time interval alarm\Whatever3.wav" }
};
The Timer_Elapsed event, which will trigger every 5 min since the alarm is started
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
TriggerAlarm();
}
The method that plays the sound
private static void TriggerAlarm()
{
TimeSpan alarmTime = new TimeSpan(DateTime.Now.Hour, DateTime.Now.Minute, 0);
if (dict.TryGetValue(alarmTime, out string alarmFile))
{
using (SoundPlayer player = new SoundPlayer(alarmFile))
{
player.Play();
}
}
else
{
//this alarm time is not found in the dictionary,
//therefore, no alarm should be played at this time (e.g. 16:05:00)
}
}

Using ThreadPoolTimer with Background Audio UWP

I am making a UWP app using the BackgroundAudioTask. My app is working very well. Now I want to add in a TextBlock the Current position of the Audio played.
I was doing this method before implementing the audio Task:
private TimeSpan TotalTime;
private DispatcherTimer timerRadioTime;
private void radioPlayer_MediaOpened(object sender, RoutedEventArgs e)
{
TotalTime = radioPlayer.NaturalDuration.TimeSpan;
// Create a timer that will update the counters and the time slider
timerRadioTime = new DispatcherTimer();
timerRadioTime.Interval = TimeSpan.FromSeconds(1);
timerRadioTime.Tick += TimerRadioTime_Tick;
timerRadioTime.Start();
}
private void TimerRadioTime_Tick(object sender, object e)
{
//Check if the audio finished calculate it's total time
if (radioPlayer.NaturalDuration.TimeSpan.TotalSeconds > 0)
{
if (TotalTime.TotalSeconds > 0)
{
// Updating timer
TimeSpan currentPos = radioPlayer.Position;
var currentTime = string.Format("{0:00}:{1:00}", (currentPos.Hours * 60) + currentPos.Minutes, currentPos.Seconds);
radioTimerBlock.Text = currentTime;
}
}
}
When I implemented the Background Task it gave me an Exception. After researching I saw a suggestion of using ThreadPoolTimer instead of dispatcherTimer.
I tried writing this code (following this solution: Clock program employing ThreadPoolTimer C# uwp)
ThreadPoolTimer timer;
// for displaying time only
private void CurrentPlayer_MediaOpened(MediaPlayer sender, object args)
{
_clockTimer_Tick(timer);
}
private async void _clockTimer_Tick(ThreadPoolTimer timer)
{
var dispatcher = Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher;
await dispatcher.RunAsync(
CoreDispatcherPriority.Normal, () =>
{
// Your UI update code goes here!
if (CurrentPlayer.NaturalDuration.TotalSeconds > 0)
{
TimeSpan currentPos = CurrentPlayer.Position;
var currentTime = string.Format("{0:00}:{1:00}", (currentPos.Hours * 60) + currentPos.Minutes, currentPos.Seconds);
CurrentPosition.Text = currentTime;
}
});
}
This is obviously not working. The app enters the method without updating my UI. I really don't understand what timer should be. Any Idea on how to make it run?
Solution:
ThreadPoolTimer timer;
// for displaying time only
private void CurrentPlayer_MediaOpened(MediaPlayer sender, object args)
{
timer = ThreadPoolTimer.CreatePeriodicTimer(_clockTimer_Tick, TimeSpan.FromSeconds(1));
}
private async void _clockTimer_Tick(ThreadPoolTimer timer)
{
var dispatcher = Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher;
await dispatcher.RunAsync(
CoreDispatcherPriority.Normal, () =>
{
// Your UI update code goes here!
if (CurrentPlayer.NaturalDuration.TotalSeconds < 0)
{
TimeSpan currentPos = CurrentPlayer.Position;
var currentTime = string.Format("{0:00}:{1:00}", (currentPos.Hours * 60) + currentPos.Minutes, currentPos.Seconds);
CurrentPosition.Text = currentTime;
}
});
}

How to calculate that how many times tick event occur?

What I really want to do, I want to calculate that how many times tick event occur. Actually I want to make check on it that if this event occurs 5 time.
Then messagebox should displayed.
Here is my code :
public partial class MainWindow : Window
{
int i = 0;
int points = 0;
int counter = 0;
public MainWindow()
{
System.Windows.Threading.DispatcherTimer dispatcherTimer = new System.Windows.Threading.DispatcherTimer();
dispatcherTimer.Tick += new EventHandler(this.playMyAudioFile);
TimeSpan ts = dispatcherTimer.Interval = new TimeSpan(0, 0, 2);
dispatcherTimer.Start();
if (counter == 5)
{
dispatcherTimer.Stop();
}
InitializeComponent();
}
private void textBox1_TextChanged(object sender, TextChangedEventArgs e)
{
// some code
label1.Content = points;
}
}
private void playMyAudioFile(object sender, EventArgs e)
{
Random rd = new Random();
i = rd.Next(1, 26);
mediaElement1.Source = new Uri(#"D:\Project C#\A-Z\" + i + ".mp3");
mediaElement1.Play();
}
}
Using await, instead of a timer, makes this particular task much easier:
public static async Task makeMusic(TimeSpan timespan)
{
for (int i = 0; i < 5; i++)
{
//this assumes you can remove the parameters from this method
playMyAudioFile();
await Task.Delay(timespan);
}
MessageBox.Show("All done!");
}
You can make the count a parameter, if it needs to be configurable, or remove the timespan as a parameter if it need never change.
Servy's solution is a lot cleaner than using a timer. But if you insist on using a timer, I would suggest this:
private int counter = 0;
private Random rd = new Random();
private void playMyAudioFile(object sender, EventArgs e)
{
i = rd.Next(1, 26);
mediaElement1.Source = new Uri(#"D:\Project C#\A-Z\" + i + ".mp3");
mediaElement1.Play();
++counter;
if (counter == 5)
{
dispatcherTimer.Stop();
}
}
I think that the sender is the dispatcher timer, so you can probably write:
var timer = (DispatcherTimer)sender;
timer.Stop();
And, please, replace this:
TimeSpan ts = dispatcherTimer.Interval = new TimeSpan(0, 0, 2);
With:
TimeSpan ts = dispatcherTimer.Interval = TimeSpan.FromSeconds(2);
When I see new TimeSpan(0, 0, 2), I have to think about what that signifies. Is it minutes, seconds, and milliseconds? Days, hours, and minutes? Hours, minutes, and seconds?
TimeSpan.FromSeconds(2), though, is explicit. There is absolutely no ambiguity.

Categories