views:

789

answers:

9

I'm working on a WinForms app and I have a user control in it. The buttons in the user control raise events up to the form to be handled by other code. One of the buttons starts some processses that will cause problems if they run simultaneously. I have logic in the code to manage the state so typically a user can't run the process if it's already running. However, if the user double-clicks the button it will start the process twice so quickly that it's tough for me to prevent it.

I'm wondering, what's the best way to handle this?

I started out by disabling the button in the click event but the second click comes in before the first click causes the button to be disabled. Setting other flags in the code didn't catch it either.

I'm considering adding some sort of sync lock on the code that raises the event but I'm wondering if any of you have a better idea.

Since this project is mostly complete I'm looking for answers that don't involve a radical rewrite of the app (like implementing the composite application block), however, feel free to post those ideas too since I can use them in my next projects.

A: 

Disable the button after the user first clicks it and before starting the task. When task completes, re-enable the button.

Piskvor
+1  A: 

The disable flag on that button would not be set until the button event handler completes, and any additional clicks will be queued in the windows message queue behind the first click. Make sure the button event handler completes quickly to free up the UI thread. There are several ways of doing this, but all involve eather spawning a thread or keeping a worker thread ready and waiting for an AutoResetEvent.

Nick
+5  A: 

Make sure that your button disabling or any other locking that you do is the /first/ thing that you do in your event handler. I would be extremely surprised if you could queue up two click events before even the first instruction fires, but I suppose that's possible if you're on a very slow computer that's bogged down with other apps.

In that event, use a flag.

private bool runningExclusiveProcess = false;

public void onClickHandler(object sender, EventArgs e)
{
    if (!runningExclusiveProcess)
    {
        runningExclusiveProcess = true;
        myButton.Enabled = false;

        // Do super secret stuff here

        // If your task is synchronous, then undo your flag here:
        runningExclusiveProcess = false;
        myButton.Enabled = true;
    }
}

// Otherwise, if your task is asynchronous with a callback, then undo your flag here:
public void taskCompletedCallback()
{
    runningExclusiveProcess = false;
    myButton.Enabled = true;
}

If you can still get in two clicks off of something like that, then make sure you're not accidentally subscribed to a doubleClick event, or anything else wonky is going on.

HanClinto
If your task is synchronous then this doesn't make sense. The second click in the double click will wait in the event queue until the first click exits. Thus, if you've unset your flag the second click will happily start the processes too.
rein
This solution doen't seem to work. I can still quickly click the button 5 times and it will process it 5 times, even using this code sample.
tzup
@tzup: This code doesn't prevent the user from clicking multiple times. It only prevents the processing code (the "super secret stuff") from trying to execute more than once at the same time in a multithreaded environment.
HanClinto
@tzup: It will happily let the processing stuff execute multiple times -- just so long as they all wait their turn and execute one after the other.
HanClinto
Ok, I get it. Thanks
tzup
+4  A: 

Are you handling the event twice?

I am very surprised to read that the second click is coming in before you can disable the button. I would look to make sure you aren't hooking up the event twice. I have done this by accident before. You will then get two events, almost instantaneously.

Jason Jackson
I did that on accident one also...took me a while to figure out why my event was firing twice..then I had a good chuckle at myself.
Harpua
I think that's happened to all of us. Cursed Auto Event Wiring!
John MacIntyre
A: 

By any chance, are you labeling the button with an icon? By all means use code to prevent running two processes at once, but you can reduce double-clicking on the human side if your button looks and acts like a conventional command button (rectangular, text label only, "raised" surface that depresses on a click). Just a thought.

Michael Zuschlag
A: 

Disable the button as soon as it is clicked. So, the event can't be handled twice. As soon as the event had been handled, you can re-enable the button.

A: 

I started out by disabling the button in the click event but the second click comes in before the first click causes the button to be disabled. Setting other flags in the code didn't catch it either.

Given WinForms is inherently single-threaded, it should not be possible for the second click to fire before the processing for the first has completed (unless you are using new threads or BackgroundWorkers etc).

Can you show us a sample illustrating your problem?

A: 

1 vote down

"The disable flag on that button would not be set until the button event handler completes, and any additional clicks will be queued in the windows message queue behind the first click." - tried all other methods of disabling and flags - didnt work cos this is the reason so must clear out the queue. In Delphi call procedure as first line in the button onclick event:--- procedure tform2.killdoubleclick(sender:tobject); var Msg: Windows.TMsg; { dummy value to receive each message from queue} begin while Windows.PeekMessage( Msg, 0, Messages.WM_mousefirst, Messages.WM_mouselast, Windows.PM_REMOVE ) do {nothing}; end;

A: 

The checked answer is close, if anecdotal at best. To the uninitated it's missing too much code to make sense. I've written an article about using Timers and Threads and the code example is REALLY easy to understand. Try reading through it and seeing if you can set the flags from the example above by running your task process in a new Thread:

http://www.robault.com/category/Threading.aspx

nbdeveloper