This question already has answers here:
What is a NullReferenceException, and how do I fix it?
(27 answers)
Closed 2 years ago.
So I am trying to set my countdown timer intervals, so that 1hr = 10minutes (in real time), 1min = 10secs (In real time) and 1s = 0.17s (In realtime) to help when testing my code. I can't seem to find what part of my code to change without causing an error. so I tried defining the interval in the initialise components section and received this:
System.NullReferenceException: 'Object reference not set to an instance of an object.' timer was null.
private void button1_Click(object sender, EventArgs e)
{
AddTimeToClock(TimeSpan.FromSeconds(10));
}
private void Form1_Load(object sender, EventArgs e)
{
timer = new System.Windows.Forms.Timer();
timer.Interval = (int)TimeSpan.FromSeconds(1).TotalMilliseconds;
timer.Tick += OnTimeEvent;
DisplayTime();
}
private void DisplayTime()
{
lblTime.Text = countdownClock.ToString(#"hh\:mm\:ss");
}
private void OnTimeEvent(object sender, EventArgs e)
{
// Subtract whatever our interval is from the countdownClock
countdownClock = countdownClock.Subtract(TimeSpan.FromMilliseconds(timer.Interval));
if (countdownClock.TotalMilliseconds <= 0)
{
// Countdown clock has run out, so set it to zero
// (in case it's negative), and stop our timer
countdownClock = TimeSpan.Zero;
timer.Stop();
}
// Display the current time
DisplayTime();
}
private void AddTimeToClock(TimeSpan timeToAdd)
{
// Add time to our clock
countdownClock += timeToAdd;
// Display the new time
DisplayTime();
// Start the timer if it's stopped
//if (!timer.Enabled) timer.Start();
}
private void button2_Click(object sender, EventArgs e)
{
AddTimeToClock(TimeSpan.FromMinutes(1));
}
private void button3_Click(object sender, EventArgs e)
{
AddTimeToClock(TimeSpan.FromMinutes(10));
}
private void checkBox1_CheckedChanged(object sender, EventArgs e)
{
if (checkBox1.Checked) timer.Start();
else
{
timer.Stop();
}
}
I would suggest you introduce a wrapper for your timer. That way you can expose a interval-method that is scaled however you want. For example:
public class ScaledTimer : IDisposable
{
private Timer timer;
public ScaledTimer() => this.timer = new Timer();
public void Dispose() => timer.Dispose();
public void Start() => timer.Start();
public void Stop() => timer.Stop();
public double Scale { get; set; } = 1;
public bool Enabled
{
get => timer.Enabled;
set => timer.Enabled = value;
}
public int Interval
{
get => (int)(timer.Interval * Scale);
set => timer.Interval = (int)(value / Scale);
}
public event EventHandler Tick
{
add => timer.Tick += value;
remove => timer.Tick -= value;
}
}
Set Scale to 10 to make the timer tick 10 times as fast. Note that this will not work well if you also use DateTime.Now since that will use the actual system time. It might also be a good idea to extract an interface for the timer. This can be useful when unit testing since it allows mocking of the timer. There is also a limited resolution for timers, so it may not work well if the interval is down to ~16ms.
If I'm understanding what you are trying to accomplish (ticking at the same speed, but logically running out your countdownClock at 6x speed), then I believe your best plan of action is to add a constant for time scale.
private static readonly int timeScale = 6
Then when you adjust your countdownClock in the OnTimeEvent, you can multiply by the scale.
countdownClock = countdownClock.Subtract(timeScale * TimeSpan.FromMilliseconds(timer.Interval));
Set your timeScale to 1 to run at normal speed, or to higher values to run faster.
This would give a general idea of how to accomplish your goal, but require hand-editing of the timeScale value when debugging to get your desired behavior. From here, you can decide how to best manage this - maybe you want to load your timeScale from a config file, or adjust it based on whether #DEBUG is defined. You may also want to make a countdownClock class that has a Scale property to further encapsulate this behavior. Final implementation is up to you.
Related
I have a little problem. There is something like chess timer. When i press button, current timer stops and second starts, but after 1 second. How can i start second one immediately?
using System;
using System.Windows.Forms;
namespace WindowsFormsApp1 {
public partial class Form1 : Form {
byte sec1;
byte sec2;
public Form1() {
InitializeComponent();
sec1 = 0;
sec2 = 0;
}
private void button1_Click(object sender , EventArgs e) {
timer1.Start();
timer2.Stop();
}
private void button2_Click(object sender , EventArgs e) {
timer2.Start();
timer1.Stop();
}
private void timer1_Tick(object sender , EventArgs e) {
label1.Text = sec1.ToString();
sec1++;
}
private void timer2_Tick(object sender , EventArgs e) {
label2.Text = sec2.ToString();
sec2++;
}
}
}
Edit
I know your question is "how to start the timers immediately", but in your code they are starting immediately. When you call start the timer starts. I believe the effect you are seeing is related to the delay associated with the tick event, which from the description I am assuming is set to a 1 second interval. Since you have said that you are trying to simulate something similar to a chess timer (although in your case counting up as opposed to down), then using something like a stop watch which can start, stop and show elapsed time would be a closer model. Since there is a Stopwatch class that provides exactly this behavior, I think it would be easier to implement it using two of those and just have a single background thread that updates the UI as frequently as needed. You could even add an update call into each button push to ensure the text boxes are up to date.
===============================
Maybe instead of the timers you should use two instances of the Stopwatch class. This will remove the need for your two variables that you are using to keep track of the seconds as the Stopwatch class will be holding the elapsed time for each counter.
Then in your button methods you could just do this:
private Stopwatch sw1 = new Stopwatch();
private Stopwatch sw2 = new Stopwatch();
private void button1_Click(object sender , EventArgs e) {
sw1.Start();
sw2.Stop();
}
private void button2_Click(object sender , EventArgs e) {
sw2.Start();
sw1.Stop();
}
And then you can use a Background worker or some other background thread that runs and updates your text boxes with the elapsed time from the timers you just need to grab the elapsed time.
// This will give you the total number of seconds elapsed.
var timer1Seconds = Math.Floor(sw1.Elapsed.TotalSeconds);
Here is an example of how you can make this update the UI:
private bool _stop = false;
public Form1()
{
InitializeComponent();
Task.Run(() =>
{
while(!_stop)
{
UpdateElapsedTimes();
Thread.Sleep(1000);
}
}
}
private void UpdateElapsedTimes()
{
if (InvokeRequired)
{
Invoke(UpdateElapsedTimes());
return;
}
label1.Text = Math.Floor(sw1.Elapsed.TotalSeconds).ToString();
label2.Text = Math.Floor(sw2.Elapsed.TotalSeconds).ToString();
}
Note - in a production program I would not use a boolean as my loop checker, you would use an event handle, and probably a couple of event handles if you wanted to allow pausing the updates, this is just to show an idea of how to do it. You could invoke directly from the thread method and drop the InvokeRequired check, but I added that for additional safety and since it was there I skipped it in the loop.
The timer does start immediately. The problem is that you are not reporting fractions of seconds, so the display will show 0 until a full second has elapsed, which is accurate, technically.
If you want to show 1 immediately, just initialize your variables that way.
public Form1() {
InitializeComponent();
sec1 = 1;
sec2 = 1;
}
I'm having trouble with the following code. I have some code that calls SetTimer() and expects the user to respond before interval is reached (in
millisecs). The calling code inherit these funtions. If the user responds, then StopTimer() is called, info is displayed, StartTimer() is called, and the user is expected to respond again within the interval time period. This continues until the user fails in an answer or takes too long (goes past the interval).
The problem is the timers don't stop. They keep repeating even after I've stopped them, set their Tick event to null (by the -= method), and left its scope. I even get new storage with a new DispatcherTimer (I've done this both using the old one and a new one each time). I can't get the old Timer to go away.
What am I doing wrong?
using Windows.UI.XAML;
public DispatcherTimer GameTimer;
internal void SetTimer(int interval)
{
GameTimer = new DispatcherTimer();
GameTimer.Tick += TimerCallback;
GameTimer.Interval = new TimeSpan(0,0,0,0,interval);
GameTimer.Start();
}
internal void StopTimer()
{
GameTimer.Stop();
try
{
GameTimer.Tick -= TimerCallback;
} catch {}
}
private void TimerCallback(object sender, object e)
{
StopTimer();
// Other code
}
Thanks in advance,
-justin
Try stopping the timer by using the sender object, not the actual public timer object:
private void TimerCallback(object sender, object e) {
(sender as DispatcherTimer).Stop();
// Other code
}
As a workaround, you could do something like:
// in your class
private bool _allowExecution = false;
Then whenever you start the time set _allowExecution = true; and when you stop the timer, simply add _allowExecution = false;
The last thing will be to add a simply boolean condition on your timer execute: if (_allowExecute) //do your stuff here
Because you initialize a new DispatcherTimer everytime call SetTimer(int interval). You must stop the old DispatcherTimer instance before initialize a new one.
internal void SetTimer(int interval)
{
StopTimer();
GameTimer = new DispatcherTimer();
GameTimer.Tick += TimerCallback;
GameTimer.Interval = new TimeSpan(0,0,0,0,interval);
GameTimer.Start();
}
internal void StopTimer()
{
if(GameTimer != null)
{
GameTimer.Stop();
GameTimer.Tick -= TimerCallback;
GameTimer = null;
}
}
I need to store the piano duration with Ticks as so then make the music note show according to that duration (Music players would know).
I'm using an interval of 100, but for some testing I used it at 1000.
The problem is this. When I'm invoking the method (I'm taking the 1000 millisecond interval one) the timer starts.. if I DO NOT manage to get the 1000 milliseconds it shows Duration 0: but then if I do for example 2 seconds, it shows 3 seconds, if I try to press it for another second (a different key) it would show 4 seconds instead of 1.
It's like it keeps on recurring. Same happened with the 100 interval one. It went mad. sometimes 40 sometimes 23 and so on. Any idea how to fix (resetting the timer)
N.B I'm using System.Windows.Forms.Timer as library
part of a method which invokes the methods further below
for (int i = 0; i < 15; i++)
{
WhiteKey wk = new WhiteKey(wKeys[i], wPos[i]-35,0); //create a new white Key with [i] Pitch, at that x position and at y =0 position
wk.MouseDown += onRightClick; //holds the Duration on Right Click
wk.MouseUp += onMouseUp;
wk.Click += new EventHandler(KeyClick); //Go to KeyClick Method whenever a key is pressed
this.panel1.Controls.Add(wk); //Give it control (to play and edit)
}
Methods controlling the time
private void onRightClick(object sender, MouseEventArgs e)
{
wk = sender as WhiteKey;
duration = 0;
t1.Enabled = true;
t1.Tick += timeTick;
t1.Interval = 100;
}
private void timeTick(object sender, EventArgs e)
{
duration++;
}
private void onMouseUp (object sender, MouseEventArgs e)
{
t1.Enabled = false;
String time = "Key: " + pitch + "\nDuration: " +duration ; //Test purposes to see if timer works
MessageBox.Show(time);
}
You are trying to measure time, don't use Timer, use Stopwatch.
You can find C# Stopwatch Exmples at dotnetpearls.com.
In abstract this is what you would want to do is something like this:
private void onRightClick(object sender, MouseEventArgs e)
{
stopwatch.Reset();
stopwatch.Start();
}
private void onMouseUp (object sender, MouseEventArgs e)
{
stopwatch.Stop();
String msg = "Duration in seconds: " + (stopwatch.ElapsedMilliseconds / 1000.0).ToString("0.00");
MessageBox.Show(msg);
}
Note: you may want to change the units or the string format.
Notes on using timer:
1) System.Windows.Forms.Timer uses the message loop of your window, this means that it may get delayed because the window is busy handling other events (such as click). For a better behaviour use System.Threading.Timer.
2) If using System.Windows.Forms.Timer don't set the Tick event handler each click. The event handler will execute once for each time you add it.
That is:
private void onRightClick(object sender, MouseEventArgs e)
{
wk = sender as WhiteKey;
duration = 0;
t1.Enabled = true;
//t1.Tick += timeTick; you should add this only once not each click
t1.Interval = 100;
}
3) If you use System.Threading.Timer you may want to make the variable duration volatile.
t1.Tick += timeTick;
By the way in your code sample you subscribe to the 'Tick' timer event each time on Right mouse click.
So if you click 2 times the
private void timeTick(object sender, EventArgs e)
method will be called twice, and 'duration++' will be executed twice. Your event subscription code should be executed only once for the timer.
P.S. If you need to measure duration, Timer is not the best way to do it.
As it currently stands, this question is not a good fit for our Q&A format. We expect answers to be supported by facts, references, or expertise, but this question will likely solicit debate, arguments, polling, or extended discussion. If you feel that this question can be improved and possibly reopened, visit the help center for guidance.
Closed 10 years ago.
Deleted post and have code:
namespace StackOverflowQuestion
{
class Program
{
static void Main (string[] args)
{
var cw = new ConsoleWriter ();
for (int i = 0; i <= 5000000; i++)
{
cw.Write (i);
}
Console.ReadKey ();
}
}
class ConsoleWriter
{
private Stopwatch sw = new Stopwatch ();
public ConsoleWriter ()
{
sw.Start ();
}
public void Write (int pNumber)
{
if (sw.ElapsedMilliseconds >= 50)
{
Console.WriteLine (pNumber);
sw.Restart ();
}
}
}
}
And the output:
305940
651171
1002965
1358665
1715740
2069602
2419054
2772833
3127880
3485054
3844335
4204016
4557912
4913494
So everything works fine. In this example ConsoleWriter display number on console, but it could be displayed in Control surface. Like you see, even if I call 5000000 times Write method, it only updates UI after minimum 50 ms. Great, but notice in many cases the last value 5000000 will not be displayed. How to fix it? Should I use a class (thread) which will call event each 50 ms and it will check the value to write is changed?
You could use a timer
Timer _timer;
public void StartTimer()
{
_timer = new Timer();
_timer.Interval = 100; // 100 ms = 0.1 s
_timer.Tick += new EventHandler(timer_Tick);
_timer.Start();
}
void timer_Tick(object sender, EventArgs e)
{
myControl.Number = i;
}
In the control there should be something like
private int _number;
public int Number
{
get { return _number; }
set
{
if (value != _number) {
_number = value;
Invalidate();
}
}
}
The call of Invalidate() will trigger the Paint event. Your painting logic should be in the OnPaint method:
protected override void OnPaint(PaintEventArgs e)
{
... paint here
}
But of cause the for loop itself will freeze the application. You could use a second timer that updates the counter at a faster intervall than your display counter. Every code running on the UI-tread (main thread if you prefer) will freeze you UI until it terminates. An easy way of doing a heavy work in the background in a separate thread is to use the BackgroundWorker. The background worker automatically switches between UI-thread and worker-thread and allows you to report progess to the UI-thread.
You can also start a thread manually in order to update the counter. If this will be the only thread changing the number, no synchronisation mechanisms will be required. But never access the UI (a form or a control) from another thread than the UI-thread.
Here is a complete non-blocking solution using another thread for the counting
Timer _timer;
int _counter;
System.Threading.Thread _worker;
public frmTimerCounter()
{
InitializeComponent();
_worker = new System.Threading.Thread(() =>
{
while (_counter < 10000000) {
_counter++;
System.Threading.Thread.Sleep(20);
}
});
_worker.Start();
StartTimer();
}
public void StartTimer()
{
_timer = new System.Windows.Forms.Timer();
_timer.Interval = 100; // 100 ms = 0.1 s
_timer.Tick += new EventHandler(timer_Tick);
_timer.Start();
}
void timer_Tick(object sender, EventArgs e)
{
// I used a Label for the test. Replace it by your control.
label1.Text = _counter.ToString();
}
You didn't post any code but I can guess at what it looks like. You are doing way too much work in the property setter. This for() loop should never take more than a millisecond, way too short to ever notice a GUI freeze.
You get this by following the standard way controls repaint themselves. Which is lazily. You get that by calling the Invalidate() method. Like this:
class MyControl : Control {
private int number;
public int Number {
get { return this.number; }
set {
if (value != this.number) this.Invalidate();
this.number = value;
}
}
protected override void OnPaint(PaintEventArgs e) {
// TODO: paint number
//...
base.OnPaint(e);
}
}
You'll now also discover something else, there's no point to using that for() loop anymore. There never was one in the first place, a human being cannot possibly see the incredible rate at which a modern processor can increment a number. So you'll now replace that with:
myControl.Number = 50000;
If you actually meant for a human eye to see the number increasing then you are going to have to do it a lot slower. No more than about once every 50 millisecond, about the point where the changes turn into a blur. That requires a Timer.
I have a statusbar label and I want to show a text on my StatusBar Label for 3 seconds only
How can I do it without using threads?
public void InfoLabel(string value)
{
if (InvokeRequired)
{
this.Invoke(new Action<string>(InfoLabel), new object[] { value });
return;
}
infoLabel.Text = value;
}
Simply add timer on the end of your method:
if (!string.IsNullOrWhiteSpace(value))
{
System.Timers.Timer timer = new System.Timers.Timer(3000) { Enabled = true };
timer.Elapsed += (sender, args) =>
{
this.InfoLabel(string.Empty);
timer.Dispose();
};
}
You need to define a function that you call each time you need to display your text, inside this function you define a timer, this timer is based on System.Windows.Forms.Timer, the only difference is that its modified to hold a stopTime parameter that represents the running duration, the only thing you need to do is to put your starting code(display text) inside the MyFunction function and to put the ending code(to stop displaying text) inside the Timer_Tick function, once you call MyFunction just specify how many seconds you want it to run in the function parameter.
private void MyFunction(int durationInSeconds)
{
MyTimer timer = new MyTimer();
timer.Tick += new EventHandler(Timer_Tick);
timer.Interval = (1000) * (1); // Timer will tick every second, you can change it if you want
timer.Enabled = true;
timer.stopTime = System.DateTime.Now.AddSeconds(durationInSeconds);
timer.Start();
//put your starting code here
}
private void Timer_Tick(object sender, EventArgs e)
{
MyTimer timer = (MyTimer)sender;
if (System.DateTime.Now >= timer.stopTime)
{
timer.Stop();
//put your ending code here
}
}
the modified timer class
public class MyTimer : System.Windows.Forms.Timer
{
public System.DateTime stopTime;
public MyTimer()
{
}
}
You can use Timer to create an instance of a timer that waits for n seconds before firing the Elapsed event. In the elapsed event, you clear the label's Content.
As the timer is executed in a separate thread, the UI thread is not locked while the timer is counting i.e. you are free to perform other operations in the UI.
private delegate void NoArgDelegate();
private void StartTimer(int durationInSeconds)
{
const int milliSecondsPerSecond = 1000;
var timer = new Timer(durationInSeconds * milliSecondsPerSecond);
timer.Start();
timer.Elapsed += timer_Elapsed;
}
private void timer_Elapsed(object sender, ElapsedEventArgs e)
{
var clearLabelTextDelegate = new NoArgDelegate(ClearLabelText);
this.Dispatcher.BeginInvoke(clearLabelTextDelegate);
}
private void ClearLabelText()
{
this.myLabel.Content = string.Empty;
}
As I do not the rest of your code, some suggestions would be to create a lock on the timer so as to prevent more than one UI event starting the timer. In addition, the delegate and the timer instance can be made as private members of the class.
You'll always be using at least the GUI thread. If you decide to wait on that thread, no other interaction with controls is possible (ie. no buttons will work, the window will not be repainted).
Alternatively you could use a System.Windows.Forms.Timer that gives control back to the OS, or another type of timer. Either way, the "countdown" will either block user interaction or happen on another thread (under the hood).