8

I want to prevent a button click from queuing. In testing I have a Form, a Button and in the Code-Behind I have the event handler:

    private void button1_Click(object sender, EventArgs e)
    {
        if (_codeRunning)
            return;

        _codeRunning = true;

        //Application.DoEvents();

        //button1.Enabled = false;

        _click ++;

        Debug.WriteLine("Click Number: " + _click);

        Task.Delay(5000).Wait();

        //button1.Enabled = true;

        _codeRunning = false;
    }

When I run debug and click the button twice or three or four times rapidly, Debug Output shows each click about five seconds after the last one. What I would like it to show is a single Click and drop the rest until first Event is complete.

I have also tried to disable the button, as well as temporarily remove the Handler from the Button_click event. It is all the same results.

Randy
  • 1,137
  • 16
  • 49
  • You're tying up the UI thread with your 5 second delay, so it doesn't dequeue any further window messages until your code completes. You need to move the slow code off into (say) a background worker, and then manage knowing whether one is in progress. – Damien_The_Unbeliever Jun 17 '15 at 14:39
  • That behaviour occurs because windows queued all clicks in the message loop. Your main thread can't remove them because he waits for 5 seconds. Maybe you can set your local variable _codeRunning to true (ad you did) and after that start a new thread that does all the other work including setting _codeRunning to false. If you do that the main thread could remove all click messages (yes it will also call the event handler but you will return if the code is running and nothing will happen). Maybe you have to declare your _codeRunning as volatile. – Sebastian Schumann Jun 17 '15 at 14:39
  • @Damien_The_Unbeliever Deam 9 seconds too slow... :-( – Sebastian Schumann Jun 17 '15 at 14:40

2 Answers2

14

There are various amounts of trouble you'll get into when you hang-up the UI thread like this. This is certainly one of them, nothing pleasant happens when the user wildly bangs on the button to try to get something noticeable to happen. And sure, those clicks won't get lost, they stay stored in the message queue. To activate your Click event handler again when your event handler stops running.

Pretty important to learn how to use the BackgroundWorker or Task classes to avoid this kind of trouble. Just setting the button's Enabled property is then enough to solve this problem.

Purging the mouse clicks from the message queue is technically possible. But ugly to do, it requires pinvoke. I'll hesitantly post the alternative, don't assume that this is in general a good strategy. You'll need to read this post to have some insight into why DoEvents() is a dangerous method.

    private void button1_Click(object sender, EventArgs e) {
        button1.Enabled = false;
        button1.Update();
        '' long running code
        ''...
        Application.DoEvents();
        if (!button1.IsDisposed) button1.Enabled = true;
    }

The Update() call ensures that the user gets the feedback he needs to know that banging the button repeatedly isn't going to do anything useful. The DoEvents() call will dispatch all the queued-up mouse clicks, nothing happens with them since the button is still disabled. The IsDisposed test is essential to solve the problem with DoEvents(), it ensures your program won't crash when the user clicked the window's Close button while the code was running.

Use the HourGlass class in this post to provide more feedback.

Community
  • 1
  • 1
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
2

I had a button that on click event was going to run a method. Same issue happent and when the user clicked multiple times the method was triggered multiple times. So I made a boolean and changed it value when the method started.

private bool IsTaskRunning = false;

private void MyMethod()
{
    if ( IsTaskRunning==false )
    {
        IsTaskRunning=true;

        // My heavy duty code that takes a long time

        IsTaskRunning=false; // When method is finished
    }
}

So now the method runs only if it's done the last time.

Wai Ha Lee
  • 8,598
  • 83
  • 57
  • 92
  • Elegant and just works! How is this nice cheat not better than the accepted answer? – KMC Aug 29 '20 at 07:51
  • This is not as good as the accepted answer because the button is still enabled, so from a UI point of view the user may think that they keep pressing the button and it will do something - but it does not. Disabling the button is arguably preferable. – Wai Ha Lee Jun 17 '21 at 11:47