Title: Flush click events 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 in 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 in the range WM_MOUSELAST to PM_REMOVE. The code 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;
}
Download the example to experiment with it and to see additional details.
|