Flush click events to prevent the user from clicking a button while its code is still running in C#

If a button starts a long task, you probably don't want the user to be able to click the button again (or perhaps not anything on the application) until the task finishes. The following code shows a straightforward attempt to prevent the user from clicking the button while its code is still executing.

// Wait for 5 seconds.
private void btnWaitNow_Click(object sender, EventArgs e)
{
// None of these seem to work.
//this.Enabled = false;
//btnWaitNow.Click -= btnWaitNow_Click;
btnWaitNow.Enabled = false;
this.Cursor = Cursors.WaitCursor;
Application.DoEvents();

lstMessages.Items.Add("Wait Now Start " + DateTime.Now.ToString());
Refresh();
System.Threading.Thread.Sleep(5000);
lstMessages.Items.Add("Wait Now Stop " + DateTime.Now.ToString());

//this.Enabled = true;
//btnWaitNow.Click += btnWaitNow_Click;
btnWaitNow.Enabled = true;
this.Cursor = Cursors.Default;
}

When the event handler starts, it disables the button. It then does its work and re-enables the button.

Unfortunately this approach doesn't work. Windows very helpfully queues up any pending mouse events including clicks while your program is busy and then delivers them when the event handler finishes so you can receive the second click. (I could have sworn this approach used to work.)

One way around this is to use a BackgroundWorker or other threading technique to perform the work on a separate thread. Disable the button and then start the thread. When the thread finishes, re-enable the button. This method works and may have other advantages (such as allowing the user to interact with other parts of the program while the button's task is still running), but it's a bit roundabout.

Another approach is to use the PeekMessage API function, as shown in the following code.

[StructLayout(LayoutKind.Sequential)]
public struct NativeMessage
{
public IntPtr handle;
public uint msg;
public IntPtr wParam;
public IntPtr lParam;
public uint time;
public System.Drawing.Point p;
}

[SuppressUnmanagedCodeSecurity]
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("User32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool PeekMessage(out NativeMessage message,
IntPtr handle, uint filterMin, uint filterMax, uint flags);
private const UInt32 WM_MOUSEFIRST = 0x0200;
private const UInt32 WM_MOUSELAST = 0x020D;
public const int PM_REMOVE = 0x0001;

// Flush all pending mouse events.
private void FlushMouseMessages()
{
NativeMessage msg;
// Repeat until PeekMessage returns false.
while (PeekMessage(out msg, IntPtr.Zero,
WM_MOUSEFIRST, WM_MOUSELAST, PM_REMOVE))
;
}

This code includes a bunch of declarations for the API function and its parameters. (You also need to add using statements for the System.Runtime.InteropServices and System.Security namespaces. Download the example for the details.)

The FlushMouseMessages method calls PeekMessage telling it to discard any message between WM_MOUSELAST and PM_REMOVE. It calls PeekMessage repeatedly until it returns false to indicate that there are no such messages.

The following button event handler calls FlushMouseMessage so you cannot click the button while its code is still running.

// Wait for 5 seconds and then flush the buffer.
private void btnWaitAndFlush_Click(object sender, EventArgs e)
{
this.Cursor = Cursors.WaitCursor;
lstMessages.Items.Add("Wait and Flush Start " + DateTime.Now.ToString());
Refresh();

System.Threading.Thread.Sleep(5000);

lstMessages.Items.Add("Wait and Flush Stop " + DateTime.Now.ToString());
FlushMouseMessages();
this.Cursor = Cursors.Default;
}

   

 

What did you think of this article?




Trackbacks
  • No trackbacks exist for this post.
Comments

  • 4/22/2011 12:47 AM ernesto wrote:
    Mmmm i don“t like this solution because it use a lot the winapi, which kind of lose the point to use .NET (what if i use mono to port this to other SO?); for this kind of problem i use a thread.
    Reply to this
    1. 4/23/2011 7:41 AM Rod Stephens wrote:
      The real point of this example is showing how to flush mouse events. I suspect something as closely tied to the system is going to require code that is pretty close to the OS.

      I used to have programs where impatient users might repeatedly click a button. When the first process finished, it would quickly do the same thing again and again.

      It would be much better if Windows ignored any events for a disabled button so the button would disable itself, perform its task, and re-enable itself. Unfortunately Windows seems to "helpfully" queue up the mouse events and deliver them to the button after it is re-enabled.

      Do you have another solution?
      Reply to this
Leave a comment

Submitted comments are subject to moderation before being displayed.

 Name

 Email (will not be published)

 Website

Your comment is 0 characters limited to 3000 characters.