I'm creating a countdown timer that allows the user to choose a valid time, and once the countdown reaches zero, it plays a sound.
I've instantiated a timer from the System.Timers namespace and set its Interval to 1 second. I convert the user-specified time to seconds, and decrement that value by 1 every time the Timer.Elapsed function hits. Once it reaches zero, that means the countdown has reached zero which means it's time to play the sound.
However, whenever it doesn't reach zero, I decrement the time value by 1 and I also want to increment the ProgressBar by using the progressbar.Increment function.
Unfortunately, whenever I do this, it gives me an exception having to do with multithreading issues. I know what is wrong, but I am not sure how to fix it. Do I need to start the timer.Elapsed function on a new thread?
The error is:
Cross-thread operation not valid: Control 'CountdownProgress' accessed
from a thread other than the thread it was created on.
Also, any tips on better programming habits are welcome as well.
Many thanks!
Here is the code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Timers;
using System.Media;
using System.Diagnostics;
using System.Threading;
namespace WindowsFormsApplication1
{
public partial class Form_CDA : Form
{
public Form_CDA()
{
InitializeComponent();
}
private bool m_CountdownActive; // declare global variables and instantiate timer.
private bool m_Cancelled;
public decimal seconds;
public decimal minutes;
public decimal hours;
public decimal time;
public System.Timers.Timer timer = new System.Timers.Timer();
private void Form_CDA_Load(object sender, EventArgs e)
{
m_Cancelled = false;
timer.AutoReset = false;
timer.Interval = 0;
m_CountdownActive = false;
richTextBox1.Text = "Hello, please select an hour between 0 and 100, a minute between 0 and 59, and a second between 0 and 59. The countdown timer will play a sound when finished.";
btn_Cancel.Enabled = false;
seconds = 0;
minutes = 0;
hours = 0;
time = 0;
m_StatusBar.Text = "Program properly loaded, waiting for user input"; // initialize variables.
}
private void btn_SetCountdown_Click(object sender, EventArgs e)
{
seconds = numUpDown_Seconds.Value;
minutes = numUpDown_Minutes.Value;
hours = numUpDown_Hours.Value;
time = (hours * 3600) + (minutes * 60) + seconds; // calculate the total time in seconds.
if (time != 0) // if time is not zero, start timer and set up event handler timer.elapsed.
{
timer.Interval = 1000;
timer.AutoReset = true;
timer.Start();
timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
CountdownProgress.Maximum = (int)time;
CountdownProgress.Minimum = 0;
CountdownProgress.Value = 0;
}
else
{
m_StatusBar.Text = "Invalid selection of times. Try again.";
return;
}
DateTime dt = DateTime.Now;
dt.AddSeconds((double)time);
Label_Countdown.Text = "Finishing time: " + dt.ToString(); // display end time to user.
m_CountdownActive = true;
btn_Cancel.Enabled = true;
btn_SetCountdown.Enabled = false;
numUpDown_Hours.Enabled = false;
numUpDown_Minutes.Enabled = false;
numUpDown_Seconds.Enabled = false; // disable controls.
m_Cancelled = true;
m_StatusBar.Text = "Timer set to " + numUpDown_Hours.Value.ToString() + " hours, " + numUpDown_Minutes.Value.ToString() + " minutes, " + numUpDown_Seconds.Value.ToString() + " seconds.";
}
void timer_Elapsed(object sender, ElapsedEventArgs e)
{
if (time == 0)
{
m_StatusBar.Text = "Countdown Finished";
SoundPlayer soundPlayer = new SoundPlayer(#"C:\Users\marupakuuu\Desktop\New stuff\doorbell.wav");
soundPlayer.Play(); // play sound.
timer.Stop();
return;
}
else
{
time = time - 1;
CountdownProgress.Increment(1); // exception occurs here; multithreading issues.
}
}
private void btn_Cancel_Click(object sender, EventArgs e)
{
// if user wishes to stop the countdown to start a new one.
m_Cancelled = true;
m_CountdownActive = false;
btn_SetCountdown.Enabled = true;
numUpDown_Seconds.Value = 0;
numUpDown_Minutes.Value = 0;
numUpDown_Hours.Value = 0;
numUpDown_Hours.Enabled = true;
numUpDown_Minutes.Enabled = true;
numUpDown_Seconds.Enabled = true;
btn_Cancel.Enabled = false;
m_StatusBar.Text = "Countdown cancelled";
}
}
}
A control can only be accessed within the thread it was created. Therefore use Invoke to execute the given code as a delegate on the main thread, where the control (progressbar) was created.
void timer_Elapsed(object sender, ElapsedEventArgs e)
{
if (time == 0)
{
m_StatusBar.Text = "Countdown Finished";
SoundPlayer soundPlayer = new SoundPlayer(#"C:\Users\marupakuuu\Desktop\New stuff\doorbell.wav");
soundPlayer.Play(); // play sound.
timer.Stop();
return;
}
else
{
time = time - 1;
Invoke(new Action(() => CountdownProgress.Increment(1)));
}
}
You may want to read http://msdn.microsoft.com/en-us/library/zyzhdc6b.aspx
Related
Dispose isn't working in my code for some reason. When i hit the reset button, and then start again, it won't start the timer over. I've tried disabling and reenablinig enabled(), but it still didn't work.
Here's my code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
public partial class StopWatch : Form
{
bool reSet = false;
bool stopped = true;
public StopWatch()
{
InitializeComponent();
}
private void startStop_Click(object sender, EventArgs e)
{
timer1.Enabled = true;
if (stopped == true)
{
timer1.Start();
stopped = false;
}
else
{
timer1.Stop();
stopped = true;
}
}
private void reset_Click(object sender, EventArgs e)
{
timer1.Dispose();
txtBox.Text = "";
reSet = true;
stopped = true;
}
int i = 0;
private void timer1_Tick(object sender, EventArgs e)
{
txtBox.Text = i.ToString();
i++;
}
}
}
Forget the timer for now, just make a stopwatch:
DateTime startTime = DateTime.MinValue; //it's reset
StartButton_Click(...){
startTime = DateTime.Now;
}
StopButton_Click(...){
MessageBox.Show("counted secs: " + (DateTime.Now - startTime).TotalSeconds);
}
That's a stopwatch
Now let's make it look like it runs. Add a label to the Form. Add a timer to the form, set it's interval to 100, enable it and put a tick event:
Timer_Tick(...){
StopWatchLabel.Text = startTime == DateTime.MinValue ? "00:00:00.000" : (DateTime.Now - startTime).ToString();
}
Now you can get into the minutiae of adding a reset button (set startTime to MinValue) starting and stopping the timer (no point updating a label to 00:00 ten times a second, but no harm in it either) but hopefully this proves that the timer is not (and should not) be part of the stopwatch function of measuring the passage of time. The timer doesn't need messing with/disposing etc. It's purely to update a label with the period that has passed since your start time
I'm new to C# and I'm trying to make a custom chronometer that is composed of two labels (label1 and label2) that display time strings (time and time0/time1) and one button (pause/play) that changes its text from pause to play and viceversa on each click. Label1 shows time that is a string var maked by datetime.now (hhmmss), label2 shows time0 and after clicking on the button "pause" and again on "play" it will shows time1 (time 1 is calculated by the formula below).
It does the following:
get system datetime.now (hhmmss), saves it in time string and shows it in label1
pushing the button pause, saves the value of time in another string time0 and shows it stopped in label2
pushing the button play, starts the time of label2 (time1) that's not synchronized with the time of label1
To calculate time1 I would like to use this formula:
time1 = DateTime.Now - ((difference between DateTime.Now and time0) - 1 second)
I'm stucked on the 3rd point because I don't know how to do the time difference between two strings and use the new time time1 as text for label2 and next clicks.
This is my actual code, any help to complete it is appreciate, thank you.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
//time0
public int hh = 0;
public int mm = 0;
public int ss = 0;
//time
public string time = "";
public string time0 = "";
public bool IsPause = true;
public Timer t = new Timer();
public Form1()
{
InitializeComponent();
}
private void label1_Click(object sender, EventArgs e)
{
}
private void Form1_Load(object sender, EventArgs e)
{
//timer interval
t.Interval = 1000; //in millisecondi
t.Tick += new EventHandler(this.t_Tick);
//start timer form loads
t.Start(); //questo userà il metodo t_Tick()
}
//timer eventhandler
private void t_Tick(object sender, EventArgs e)
{
//get current time
hh = DateTime.Now.Hour;
mm = DateTime.Now.Minute;
ss = DateTime.Now.Second;
//padding leading zero
if(hh < 10)
{
time += "0" + hh;
}
else
{
time += hh;
}
time += ":";
if(mm < 10)
{
time += "0" + mm;
}
else
{
time += mm;
}
time += ":";
if (ss < 10)
{
time += "0" + ss;
}
else
{
time += ss;
}
//update labels
label1.Text = time;
if (IsPause == false) label2.Text = time0;
else label2.Text = time;
}
private void button1_Click(object sender, EventArgs e)
{
if (button1.Text == "pause")
{
IsPause = false;
button1.Text = "play";
time0 = label1.Text;
}
else
{
IsPause = true;
button1.Text = "pause";
}
}
}
}
It sounds as if you are best saving the time in the controls as well as the time as a string. The Tag property is there for that purpose. See https://msdn.microsoft.com/en-us/library/system.windows.forms.control.tag%28v=vs.110%29.aspx
So, for example, if you set the DateTime you are using into label2.Tag to the same time as you format it as text in label2.Text then you can refer to it as a DateTime. Then when you need to calculate from it you can use
DateTime.Subtract - see https://msdn.microsoft.com/en-us/library/8ysw4sby%28v=vs.110%29.aspx
to determine the elapsed time.
So, to refer this to your code, wherever you have code like this, where time is the string you have created from a DateTime instance:
label1.Text = time;
you also need to set the time like this (DateTime.Now is an example, you should chose whatever you used to format the time string):
label1.Tag = DateTime.Now
Then later, when you want to know the time in label1, do this:
DateTime t = (DateTime)label1.Tag
I'm a C# newbie_and in programming in general_ and Ι'm trying to build a math quiz app with a countdown timer.
I generate an equation each time the user clicks the start button and I give him a max 60 seconds to answer. The user answers -whether his answer is wrong or right doesn't matter_ and can he/she can click again for a new equation. So I want the timer to reset each time the user is shown a new random equation. So far I've only managed to reset this when the 60sec timespan elapses but even that is not working properly, sometimes it displays 59 or 58 secs instead of 60.
So far reading other questions has't helped me much and the timer confuses me. I also accept suggestions to make my code simpler and more elegant.
Here is my code:
EquationView.xaml.cs
public sealed partial class EquationView : Page
{
DispatcherTimer timer = new DispatcherTimer();
int tick = 60;
int result;
public EquationView()
{
this.NavigationCacheMode = NavigationCacheMode.Enabled;
this.InitializeComponent();
}
private void startButton_Click(object sender, RoutedEventArgs e)
{
// Once clicked then disabled
startButton.IsEnabled = false;
// Enable buttons required for answering
resultTextBox.IsEnabled = true;
submitButton.IsEnabled = true;
var viewModel = App.equation.GenerateEquation();
this.DataContext = viewModel;
result = App.equation.GetResult(viewModel);
timer.Interval = new TimeSpan(0, 0, 0, 1);
//timer.Tick += new EventHandler(timer_Tick);
timer.Tick += timer_Tick;
timer.Start();
DateTime startTime = DateTime.Now;
// Reset message label
if (message.Text.Length > 0)
{
message.Text = "";
}
// Reset result text box
if (resultTextBox.Text.Length > 0)
{
resultTextBox.Text = "";
}
}
private void timer_Tick(object sender, object e)
{
Countdown.Text = tick + " second(s) ";
if (tick > 0)
tick--;
else
{
Countdown.Text = "Times Up";
timer.Stop();
submitButton.IsEnabled = false;
resultTextBox.IsEnabled = false;
startButton.IsEnabled = true;
tick = 60;
}
}
private void submitButton_Click(object sender, RoutedEventArgs e)
{
timer.Stop();
submitButton.IsEnabled = false;
resultTextBox.IsEnabled = false;
if (System.Text.RegularExpressions.Regex.IsMatch(resultTextBox.Text, "[^0-9]"))
{
MessageDialog msgDialog = new MessageDialog("Please enter only numbers.");
msgDialog.ShowAsync();
resultTextBox.Text.Remove(resultTextBox.Text.Length - 1);
//Reset buttons to answer again
submitButton.IsEnabled = true;
resultTextBox.IsEnabled = true;
timer.Start();
}
else
{
try
{
int userinput = Int32.Parse(resultTextBox.Text);
if (userinput == result)
{
message.Text = "Bingo!";
App.player.UpdateScore();
startButton.IsEnabled = true;
}
else
{
message.Text = "Wrong, sorry...";
startButton.IsEnabled = true;
}
}
catch (Exception ex)
{
MessageDialog msgDialog = new MessageDialog(ex.Message);
msgDialog.ShowAsync();
submitButton.IsEnabled = true;
resultTextBox.IsEnabled = true;
timer.Start();
}
}
}
It seems to me that you have at least two significant problems here. One is that your timer will likely give the user more than 60 seconds, due to the inaccuracy in the Windows thread scheduler (i.e. each tick will occur at slightly more than 1 second intervals). The other (and more relevant to your question) is that you don't reset the tick value to 60 except when the timer has elapsed.
For the latter issue, it would be better to simply reset your countdown value when you start the timer, rather than trying to remember everywhere that you stop it.
To fix that and the first issue, get rid of the tick field altogether and change your code to look more like this:
static readonly TimeSpan duration = TimeSpan.FromSeconds(60);
System.Diagnostics.Stopwatch sw;
private void startButton_Click(object sender, RoutedEventArgs e)
{
// Once clicked then disabled
startButton.IsEnabled = false;
// Enable buttons required for answering
resultTextBox.IsEnabled = true;
submitButton.IsEnabled = true;
var viewModel = App.equation.GenerateEquation();
this.DataContext = viewModel;
result = App.equation.GetResult(viewModel);
sw = System.Diagnostics.Stopwatch.StartNew();
timer.Interval = new TimeSpan(0, 0, 0, 1);
timer.Tick += timer_Tick;
timer.Start();
// Reset message label
if (message.Text.Length > 0)
{
message.Text = "";
}
// Reset result text box
if (resultTextBox.Text.Length > 0)
{
resultTextBox.Text = "";
}
}
private void timer_Tick(object sender, object e)
{
if (sw.Elapsed < duration)
{
Countdown.Text = (int)(duration - sw.Elapsed).TotalSeconds + " second(s) ";
}
else
{
Countdown.Text = "Times Up";
timer.Stop();
submitButton.IsEnabled = false;
resultTextBox.IsEnabled = false;
startButton.IsEnabled = true;
}
}
This way, it won't matter exactly when the tick event happens, the code will still correctly compute the actual time remaining and use that for the display and to tell whether the time is up.
I have a button click event that start an operation :
private void Diagnose_Click(object sender, EventArgs e)
{
processfinish = false;
timer2.Enabled = true;
timerCount = 0;
count = 0;
countBack = 5;
CreateZip.Enabled = false;
DriverVerifier.Enabled = false;
Diagnose.Enabled = false;
Diagnose.Text = "PROCESSING PLEASE WAIT";
if (this.backgroundWorker1.IsBusy == false)
{
this.backgroundWorker1.RunWorkerAsync();
}
Logger.Write("***** OPERATION STARTED *****");
}
And the completed event of the backgroundworker :
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
processfinish = true;
Logger.Write("***** OPERATION ENDED *****");
}
And the timer1 tick event that start to work when its getting to the completed event:
private void timer1_Tick(object sender, EventArgs e)
{
count++;
Diagnose.Text = "PROCESS HAS FINISHED" + " " + countBack--;
if (count == 6)
{
Diagnose.Text = "COLLECT INFORMATION";
Diagnose.Enabled = true;
CreateZip.Enabled = true;
ViewLogFile.Enabled = true;
DriverVerifier.Enabled = true;
timer1.Enabled = false;
}
}
I want in my Logger text file to see something like:
Logger.Write("Operation Time Was: " + timepassed);
And timepassed will show minutes and second for example:
Operation Times Was: 05:21
Just use a StopWatch. Start() it to begin timing, Stop() it when you are done, and then get the Elapsed time and format it.
The simplest way (not the best since it isn't thread safe at all, so it won't work if you have 2 operations working at the same time)
Declare a private variable:
DateTime _start;
Then in your Diagnose_Click method, assign a value to it:
_start = DateTime.Now;
And in your backgroundWorker1_RunWorkerCompleted you can have the time elapsed like that:
TimeSpan elapsed = DateTime.Now - _start;
And you can write it directly in your log file using something like:
Logger.Write("Operation time was: " + elapsed.Minutes + "mn " + elapsed.Seconds + "s");
// Create new stopwatch
Stopwatch stopwatch = new Stopwatch();
// Begin timing
stopwatch.Start();
// Do something
for (int i = 0; i < 1000; i++)
{
Thread.Sleep(1);
}
// Stop timing
stopwatch.Stop();
// Write result
Console.WriteLine("Time elapsed: {0}",
stopwatch.Elapsed);
You can do
DateTime startTime = DateTime.Now;
Then after you are done processing do
DateTime endTime = DateTime.Now;
//This will give you something like 2hrs 15min or 15 days 11 hours 10 min ect
TimeSpan timePassed = endTime.Subtract(startTime);
Here is the MSDN Documentation http://msdn.microsoft.com/en-us/library/8ysw4sby.aspx
The advantage to using this method (other than it specifically uses DateTime like you specified above) is that there is no timer running. You are not using any resources really here. You just grab the DateTime at the start of your process and at the end. Then subtract them to get the difference. To easy.
I'm trying to create a Windows Form application that searches for a string and has three possible scenarios:
String 1 found - wait
String 2 found - stop
Else - Perform action and wait 1 minute
I am encountering my problem only on the times when it is expected to wait. When this happens, the newTimer_Tick starts to tick every second. I have tried disabling the timer when it ticks and a few other things but none appeared to work. Below is the code:
public void Action(string result)
{
if (result.Contains("string1"))
{
// Check again in 10 + x seconds
int n = new Random().Next(0, 5000);
int newtime = 10000 + n;
newTimer.Tick += new EventHandler(newTimer_Tick);
newTimer.Interval = newtime;
newTimer.Enabled = true;
}
else if (result.Contains("string2"))
{
// Turn off
newTimer.Enabled = false;
}
else
{
// Perform action and tick again in 1min + x seconds
action1();
int n = new Random().Next(0, 5000);
int newtime = 600000 + n;
newTimer.Tick += new EventHandler(newTimer_Tick);
newTimer.Interval = newtime;
newTimer.Enabled = true;
}
}
private void newTimer_Tick(object sender, EventArgs e)
{
Action( result );
}
What have I done wrong?
Each time the following line is called, an new instance of the event handler newTimerTick is added to the invocation list for the Tick event:
newTimer.Tick += new System.EventHandler(newTimer_Tick);
So every time the time tick goes off newTimerTick is going to be called multiple times, which is going to give you unexpected results.
Configure your event handler once only. In the constructor would be a sensible place.
Have you tried to stop the timer with the Timer.Stop method?
Btw: I don't think you need to reassign the Tick event from the newTimer unless you don't create a new Timer everytime.
I think what you were missing is that you have to stop your timer since you don't actually want it to keep for more than one interval. You seem to want to run it once, check on the result and then decide if you want to keep running it or not. Here's the code:
public void action(string result)
{
int n = new Random().Next(0, 5000);
Boolean blActivateTimer = true;
Timer timer = new Timer();
timer.Tick += timer_Tick;
if (!result.Contains("string1") && !result.Contains("string2"))
{
n += 600000;
action1();
}
else
{
if (result.Contains("string1"))
{
n += 10000;
}
else
{
blActivateTimer = false;
}
}
if (blActivateTimer)
{
timer.Start();
}
}
void action1()
{
}
void timer_Tick(object sender, EventArgs e)
{
Timer t = (Timer)sender;
t.Stop();
action(result);
}