Hi,
in one project, we notice that when a click event is fired on a button, if the task is a bit long the user can click many times on that button and then fired many click event. We would like to prevent to fire another events as long as the first task is running.
We try to disable the button, the form, remove the eventhandlerinside the event but the click event are still queue.
The only way seems to put an Application.DoEvents at the end of the click event but : - I read that Application.DoEvents() Is Evil..( http://www.codinghorror.com/blog/archives/000159.html) - we have to implement this behaviour in many button which means that we will have to put an Application.DoEvents() in every events !!!
Here's a sample :
private void button1_Click(object sender, EventArgs e)
{ //this.Enabled = false; //form disable but event fired during long process
//this.button1.Enabled = false; //button disable but event fired during long process
//Application.DoEvents(); // don't solve the problem if here
try
{
//long process... for (int i = 0; i < 5; i++)
{
System.Threading.Thread.Sleep(500);
this.textBox1.Text += "A";
this.textBox1.Refresh();
}
//Application.DoEvents(); // seems to solve the problem only here why ???
}
finally
{
//this.Enabled = true; // the button must be enable when task is finished
//this.button1.Enabled = true; }
I'd like to know how and why Application.DoEventscan solveonly and hopethere's other way to solve that problem becauseusing Application.DoEvents in every event seems not a good idea...?
Thank you
- Edited bynikho Thursday, August 20, 2009 5:22 PMcode editor....
- Edited bynikho Thursday, August 20, 2009 5:19 PM
-
| | nikho Thursday, August 20, 2009 5:14 PM | I agree with you. Suppressing the click turned out to be unexpectedly hard though. I hacked for a while but had to reach pretty deep. Using Application.DoEvents() is still the best solution. It is benign in this case because there is no dangerous additional code running afterwards. You could use the commented section instead. Add a new class to your project and paste this code: using System; using System.Windows.Forms; using System.Reflection; public class ClickSuppressor : IDisposable { private Control mCtl; public ClickSuppressor(Control ctl) { mCtl = ctl; mCtl.Enabled = false; mCtl.Update(); } public void Dispose() { Application.DoEvents(); /* Or this: MethodInfo mi = typeof(Control).GetMethod("RemovePendingMessages", BindingFlags.Instance | BindingFlags.NonPublic); mi.Invoke(mCtl, new object[] { 0x201, 0x203 }); mi.Invoke(mCtl.Parent, new object[] { 0x201, 0x203 }); */ if (!mCtl.IsDisposed) mCtl.Enabled = true; } } Use it like this: private void button1_Click(object sender, EventArgs e) { using (new ClickSuppressor(button1)) { System.Threading.Thread.Sleep(2000); } }
Hans Passant.- Marked As Answer bynikho Friday, August 21, 2009 10:23 AM
-
| | nobugz Friday, August 21, 2009 2:55 AM | The better approach here would be to disable the button, and push your long running work into a background thread. BackgroundWorker is very handy for this. Once the work is done, the button can be reenabled. The advantage here is that the UI will be completely responsive while your "work" is processing (which will help prevent the multiple click events from queuing up). This allows you to present status information (such as a progress bar) to the user, as well.
Reed Copsey, Jr. - http://reedcopsey.com | | Reed Copsey, Jr. Thursday, August 20, 2009 5:20 PM | thanks but my task are not so long just a few seconds but it's enough for a user to click two or three times. It seems I can't use a backgroungworker in every click of every buttons in the whole application. | | nikho Thursday, August 20, 2009 5:29 PM | I would argue that any task that takes more than about 1 second should be put in a background worker. An application that "hangs" for a "few seconds" every time I click on a button is an application I would come to really hate working with. This is completely up to you, but the standard practice for handling this type of situation is to use a background worker. BackgroundWorker makes this fairly easy to do.
Reed Copsey, Jr. - http://reedcopsey.com | | Reed Copsey, Jr. Thursday, August 20, 2009 5:41 PM | I agree with you. Suppressing the click turned out to be unexpectedly hard though. I hacked for a while but had to reach pretty deep. Using Application.DoEvents() is still the best solution. It is benign in this case because there is no dangerous additional code running afterwards. You could use the commented section instead. Add a new class to your project and paste this code: using System; using System.Windows.Forms; using System.Reflection; public class ClickSuppressor : IDisposable { private Control mCtl; public ClickSuppressor(Control ctl) { mCtl = ctl; mCtl.Enabled = false; mCtl.Update(); } public void Dispose() { Application.DoEvents(); /* Or this: MethodInfo mi = typeof(Control).GetMethod("RemovePendingMessages", BindingFlags.Instance | BindingFlags.NonPublic); mi.Invoke(mCtl, new object[] { 0x201, 0x203 }); mi.Invoke(mCtl.Parent, new object[] { 0x201, 0x203 }); */ if (!mCtl.IsDisposed) mCtl.Enabled = true; } } Use it like this: private void button1_Click(object sender, EventArgs e) { using (new ClickSuppressor(button1)) { System.Threading.Thread.Sleep(2000); } }
Hans Passant.- Marked As Answer bynikho Friday, August 21, 2009 10:23 AM
-
| | nobugz Friday, August 21, 2009 2:55 AM | Hi nobugz and thanks a lot. This works great and need to code the Application.DoEvents in only one place which was exactly what I was looking for. The RemovePendingMessages do the job too. But where did you find infos about this method and about his arguments ?
Once again thank you.
| | nikho Friday, August 21, 2009 10:23 AM | I used Reflector. I knew I wanted to P/Invoke PeekMessage() and searched .NET with that tool for places where it was used. Hans Passant. | | nobugz Friday, August 21, 2009 11:30 AM |
|