.NET Timer Control - c#

I have a windows app which is just a form with a timer control on. I've managed to track this down to the following situation:
private void timer1_Tick(object sender, EventArgs e)
{
MessageBox.Show("Test");
timer1.Enabled = false;
}
Will print Test again and again until I stop the program. However:
private void timer1_Tick(object sender, EventArgs e)
{
//MessageBox.Show("Test");
textBox1.Text += "t";
timer1.Enabled = false;
}
Just adds a single "t" to the textbox.
Can anyone tell me why MessageBox.Show is causing the function to return before the timer is disabled?

The call to MessageBox.Show blocks execution of timer1_Tick until you close the messsagebox, so the call to set timer1.Enabled = false; doesn't occur until after that. Because of this, the timer is still running and thus timer_Tick` still keeps getting called, every time the timer fires, until you hit OK on one of the message boxes.
What you need, if you want displaying the messagebox to stop the timer from firing again, is:
private void timer1_Tick(object sender, EventArgs e)
{
timer1.Enabled = false;
MessageBox.Show("Test");
}

You disable the timer after the user clicked the messagebox away.
MessageBox.Show shows a modal dialog. It will return (to the caller method) after the user responded to the messagebox. If you disable the timer first, the event will not be triggered again, and the user will have enough time to react.
Try this:
timer1.Enabled = false;
MessageBox.Show("Test");

Are you clicking OK on test, each timer click? If the message boxes keep stacking up one on top of the other, it's because MessageBox.Show doesn't return until you close the messagebox. In the meantime a message pump will continue to run, and process your timer messages.

Related

button queues its click events instead of disabling it

I change my button's "Enabled" property to "false" but my button still catches it's click event. I put Thread.Sleep() method to imitate some process. While my button is greyed out, i click on it, and after current process is done it begins work again (because i clicked on it while it was greyed out)
Here's my code:
int i = 0;
public Form1()
{
InitializeComponent();
label1.Text = "0";
}
private void button1_Click(object sender, EventArgs e)
{
//first click
button1.Enabled = false;
i++;
Thread.Sleep(3000); //if i click twice more while button is greyed-out the app will be non-responsive for 9 second and then prints "3" to my label
label1.Text = i.ToString();
button1.Enabled = true;
}
How can i disable my button completely (not allowing it's events to rise, but visible)?
You are freezing the UI thread which prevent anything from happening UI-wise. You should considering using the TPL to do such work.
private async void button1_Click(object sender, EventArgs e)
{
//first click
button1.Enabled = false;
i++;
await Task.Delay(TimeSpan.FromSeconds(3));
label1.Text = i.ToString();
button1.Enabled = true;
}
The Thread.Sleep(3000) call is blocking the function so the button doesn't get disabled. A quick and dirty fix to this is to call Application.DoEvents(); directly after button1.Enabled = false;. This forces the application to process any waiting events and should ensure that the button is disabled.
If you plan to replace Thread.Sleep(3000) with a long running process then you should use a BackgroundWorker. You'll find it under Components in the designer Toolbox.

System.Windows.Forms.Timer And MessageBox

can someone please let me know why the System.Windows.Forms.Timer continues to show multiple message boxes? I thought that it is on GUI thread ... and therefore after the first messagebox the GUI thread should block. But this is not the case
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
int nValue = 0;
void tmr_Tick(object sender, EventArgs e)
{
nValue++;
MessageBox.Show(nValue.ToString());
}
System.Windows.Forms.Timer tmr = new System.Windows.Forms.Timer();
private void btnStartTimer_Click(object sender, EventArgs e)
{
tmr.Interval = 500;
tmr.Enabled = true;
tmr.Tick += new EventHandler(tmr_Tick);
}
}
The MessageBox.Show() method includes (as all modal dialogs do) a message loop that continues to pump window messages.
Window messages are what allow a window to interact with the user (update itself, accept input, etc.), as well as what allows the Forms.Timer class to work.
If you want your Forms.Timer to stop ticking when the dialog is shown, you need to set the timer's Enabled property to false before you show the dialog.
In your Tick event stop the timer and then start again after MessageBox.Show like:
void tmr_Tick(object sender, EventArgs e)
{
tmr.Enabled = false;
nValue++;
MessageBox.Show(nValue.ToString());
tmr.Enabled = true;
}
The reason you are getting repeated MessgeBoxes is because your timer is continuing after showing the first MessageBox.
A message box does not block the GUI-Thread. It's as simple as that. You can interact with the message box, after all :)
Also: The internal workings of the timer are not clear, but I would guess that it runs on another thread and just returns on the GUI-Thread.

Updating the UI right before running the BackgroundWorker

I have this really little problem, but which can't be easily solved. Currently, my program has 2 buttons, a "Start" and a "Cancel". When the user clicks the start, the buttons should go instantly:
StartButton.IsEnabled = false;
CancelButton.IsEnabled = true;
But this occurs only when the BackgroundWorker has finished (all the code which will be ran after pressing the button), because the UI is always updated as last. There's no way I could add these commands to the "ProgressChanged" or "Completed" event of the backgroundworker. These events can take up to 10min to complete.
One easy way is to add these commands to the "ProgressChanged" part, and in the end "Complete" change their state again. But I'd like to avoid this, as the buttons should be showing their real state all the time, not after few "ProgressChanged" events. Of course there's always ways around, like not using the button's UI properties.
Is there any short solution for this?
It doesn't work to add the Button.Property changes to the ClickEvent. That's the main problem in this. I can easily use the "Completed" part of BGW to change the Button's back to match the starting state. The problem is to get them set right before all the events and BGW.
if you have a start button like:
this.StartButton = new System.Windows.Forms.Button();
then you can do
this.StartButton.Click += new System.EventHandler(this.button1_Click);
and then do
private void button1_Click(object sender, EventArgs e)
{
StartButton.IsEnabled = false;
CancelButton.IsEnabled = true;
Thread bg = new Thread(new ThreadStart( UpdateDatabase()));
bg.Start();
}
if you want the bg thread to send messages to the UI use the Invoke method like here
public delegate void UpdateUIHndler();
public void UpdateUI()
{
}
and do
if (InvokeRequired)
{
Invoke(new UpdateUIHndler(UpdateUI));
}
Take a look at a previous question of mine (quite similer). I should go for option 1.
ASP.NET Application log while code is running / progress bar
UI will only be delayed 5 seconds. Instead of text update the button styling using AJAX.
you can disable the start button in the click event of that button itself and enable it again it on RunWorkerCompleted event of BGW as shown below
BackgroundWorker _worker = new BackgroundWorker();
_worker.DoWork += new DoWorkEventHandler(_worker_DoWork);
private void StartButton_Click(object sender, RoutedEventArgs e)
{
startButton.IsEnabled = false;
}
void _worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
stratButton.IsEnabled = true;
}
void _worker_DoWork(object sender, DoWorkEventArgs e)
{
//Your processing code
}

how to lock the application GUI in C# Winform [duplicate]

This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
How to block Winforms UI while background thread is running
i am using a C# WinForm application
I have a Save button on the screen where the data of the screen is saved to database.
what happens, when user clicks to button application goes to database and saves data.
it takes some time.
but mean while if user again click on the Save button the Click event get catched and when the first Click return to main code (after saving database) the caught event get fired..
In short the click event get caught and fired when the thread returns from the first event
( I tried the scenario of enable/disable the button).
How can I stop this behavior.
Regards,
Akhil
#Jalal: I tried this code with some modification as
private readonly object _userActivityLocker = new object();
private void btnSave_Click(object sender, EventArgs e)
{
if (System.Threading.Monitor.TryEnter(_userActivityLocker))
{
//note that any sub clicks will be ignored while we are here
try
{
DateTime dt = DateTime.Now;
Thread.Sleep(2000);
Debug.Print("FirstClick {0} Second Click {1}",dt.ToLongTimeString(), DateTime.Now.ToLongTimeString());
//here it is safe to call the save and you can disable the btn
Application.DoEvents();
}
finally
{
System.Threading.Monitor.Exit(_userActivityLocker);
//re-enable the btn if you disable it.
}
}
}
but when i rapidly click on button (i checked with 5 times rapid clicks) 5 click events
has been fired and console window is showing
FirstClick 1:30:22 PM Second Click 1:30:24 PM
FirstClick 1:30:24 PM Second Click 1:30:26 PM
FirstClick 1:30:26 PM Second Click 1:30:28 PM
FirstClick 1:30:28 PM Second Click 1:30:30 PM
FirstClick 1:30:30 PM Second Click 1:30:32 PM
The problem is that your program is dead to the world while it is saving the data to the database. The user's mouse click is sitting in the message queue, waiting for your UI thread to come back to life. When it does, the button is no longer disabled so the Click event fires.
You can solve it by emptying the message queue before you re-enable the button so the click is processed while the button is disabled:
private void button1_Click(object sender, EventArgs e) {
button1.Enabled = false;
// Save data to database
//...
System.Threading.Thread.Sleep(2000);
Application.DoEvents(); // Empty the message queue
if (!button1.IsDisposed) button1.Enabled = true;
}
Do not skip the IsDisposed test, DoEvents is dangerous because it isn't selective about what events get processed. It will happily let the user close the main window while your code is still running.
But the better solution is to not let your UI thread go dead like this. Use a BackgroundWorker to perform the save on a worker thread. This will also avoid the ugly "Not Responding" ghost window that Windows puts up when your save takes more than a couple of seconds. It probably doesn't do this yet right now, but it will a year from now when the dbase has grown. You can re-enable the button in the BGW's RunWorkerCompleted event handler.
By enabling, then re-enabling again as you eluded to. What was the problem with this?
public void SaveButton_Click(..., ...)
{
this.SaveButton.Enabled = false;
Save();
this.SaveButton.Enabled = true;
}
using a System.Threading.Monitor class will do the trick like:
private readonly object _userActivityLocker = new object();
private void btnSave_Click(object sender, EventArgs e)
{
new Thread(delegate()
{
if (System.Threading.Monitor.TryEnter(_userActivityLocker))
{
//note that any sub clicks will be ignored while we are here
try
{
//here it is safe to call the save and you can disable the btn
}
finally
{
System.Threading.Monitor.Exit(_userActivityLocker);
//re-enable the btn if you disable it.
}
}
}) { IsBackground = true }.Start();
}
To prove that changing the button to enable or disable state is not enough here a simple test:
Add a new form and add a button1 to it and inside the button1 click event handler write the following code:
private void button1_Click(object sender, EventArgs e)
{
button1.Enabled = false;
Console.WriteLine("First Message");
Thread.Sleep(2000);
Console.WriteLine("second Message");
button1.Enabled = true;
}
and then build and run the application, double click on the button1 and the result int the output window will be:
First Message
second Message
First Message
second Message
so we have to make sure only one click is executed even when double click or so and that accomplished simply by using the System.Threading.Monitor
Update: Note that you can use a Task "if C# 4.0", a ThreadPool.QueueUserWorkItem or BackgroundWorker as a substitute of Thread.

Disabling Timer .. why I can't?

When I use System.Windows.Forms.Timer class and finish using it then I can't disable it.. it ticks even if I set its property Enabled to false. What is wrong with the code? here is an example:
int counter = 0;
private void timer1_Tick(object sender, EventArgs e) {
MessageBox.Show("Hello");
counter++;
if (counter == 10){
timer1.Enabled = false;
}
}
This is a subtle bug that's induced by the MessageBox.Show() call. MessageBox pumps a message loop to keep the UI alive. Which allows the Tick event handler to run again, even though it is already active from the previous tick. The counter variable doesn't get incremented until you click the OK button. As a result, the screen fills with message boxes and that won't stop until you click the OK button ten times.
You need to increment the counter before showing the message box. Fix:
int counter = 0;
private void timer1_Tick(object sender, EventArgs e) {
counter++;
if (counter > 10) timer1.Enabled = false;
else MessageBox.Show("Hello");
}
This kind of problem is also the reason that DoEvents() got such a bad reputation. It is pretty difficult to write code that can properly deal with the re-entrancy induced by the message loop. You need to keep boolean flags around that indicate that code is already active. Which is another way to solve your problem:
int counter = 0;
bool showingBox = false;
private void timer1_Tick(object sender, EventArgs e) {
if (showingBox) return;
showingBox = true;
try {
MessageBox.Show("Hello");
counter++;
if (counter == 10) timer1.Enabled = false;
}
finally {
showingBox = false;
}
}
You now get only one message box at a time, probably what you are really looking for.
I should mention that this re-entrancy problem is pretty specific to timers. A dialog takes counter-measures to avoid re-entrancy problems, it disables all the windows in the application. That ensures that the user cannot do things like closing the main window or clicking a button that brings up the dialog again. Both rather disastrous mishaps. That takes care of most of the 'unexpected' Windows notifications, basically any of the messages that are generated by the user. The edge case is a timer (WM_TIMER is not disabled) whose event handler has a UI effect.
It is because the MessageBox.Show blocks until the user presses OK.
The code below the MessageBox will not execute until after 10 OK buttons are pressed.
But the timer continues to fire even if the execution is blocked.
Try this code
int counter = 0;
private void timer1_Tick(object sender, EventArgs e) {
counter++;
if (counter == 10){
timer1.Enabled = false;
}
MessageBox.Show("Hello");
}
(just moved the MessageBox)
What about timer1.Stop()? I am not too familiar with this class, but looked it up quickly: Timer Class
try this:
int counter = 0;
private void timer1_Tick(object sender, EventArgs e) {
MessageBox.Show("Hello");
counter++;
if (counter == 10){
timer1.Stop();
timer1.Dispose();
timer1 = null;
}
}
It is also working for me. I placed the timer in the form, on the button click, I am calling timer1.start() and I put the following code in the tick event and its working.
int i = 0;
private void timer1_Tick(object sender, EventArgs e)
{
i++;
this.Text = i.ToString();
if (i == 10)
{
timer1.Enabled = false;
}
}
You need to call Stop.
int i = 0;
private void timer1_Tick(object sender, EventArgs e)
{
i++;
if (i == 10)
{
timer1.Stop();
}
}
I'm pretty sure the MessageBox is the culprit here. Maybe if you use a short execution interval for the timer handler then this could potentially cause your code to function undesirably, if executions are overlapping.
The problem would be, in this case, that the handler executes and displays the MessageBox which in turn halts execution of the current scope until the the prompt is acknowledged by the user, meanwhile the handler has started again, showing another prompt, and another, and so on. At this point, we have multiple MessageBoxes waiting for input, yet counter hasn't even been incremented once. When we click 'OK' on the prompt, counter increments as desired, but at this point has a value of 1, rather than a value representing the number of prompts shown. This means yet another prompt will be displayed if more time elapses, until the user clicks 'OK' on at least 10 prompts.
You could try inhibiting execution if the process is already under way in order to prevent concurrent runs:
private readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim();
int counter = 0;
private void timer1_Tick(object sender, EventArgs e)
{
if (Locker.TryEnterWriteLock(0))
{
try
{
MessageBox.Show("Hello");
Counter++;
if (Counter == 10)
{
Timer.Enabled = false;
}
}
catch { }
finally
{
Locker.ExitWriteLock();
}
}
}

Categories