Flush click events in C#

[flush]

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 Example   Follow me on Twitter   RSS feed   Donate




This entry was posted in API, controls, events, syntax, system, threading and tagged , , , , , , , , , , , , , . Bookmark the permalink.

12 Responses to Flush click events in C#

  1. Rod Stephens says:

    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?

  2. Dandre says:

    Why not just disable the button and run the long operation in a background thread and when done re-enable the button? No need to use OS APIs directly and even if the events queue up on the button it will be discarded because the button is disabled plus you will have a responsive UI. There are many examples on how to do this in WinForms and especially WPF.

  3. Rod Stephens says:

    Unfortunately I don’t think that works. As the post above says:

    Unfortunately Windows seems to “helpfully” queue up the mouse events and deliver them to the button after it is re-enabled.

    When I try this approach, the button fires and disables itself. If you click on the button while it is disabled, the click is saved and when you re-enable the button, it launches another Click event. All this example does is flush out those “saved” Click events.

  4. Loops says:

    Awesome !!
    It’s really helpful.
    Thanks for sharing this.

  5. Bindumol says:

    When i used this method i feel the Mouse control is not to be set on any of the control in new screen. So the first click on the new screen will select the control (button) and the second event will only be fired.

    • RodStephens says:

      I don’t see that behavior on my system. On mine the first click on the button makes the button fire, even if some other program such as Windows Explorer has the focus. I don’t know why it wouldn’t work on your system.

  6. Bindumol says:

    Hi Rod,
    Do you know how i can remove only all the mouse click events at run time.For eg. LeftUp, Leftdown etc, but not all like mouse enter and Mouse leave, hover messages/events

    • RodStephens says:

      PeekMessage’s 3rd and 4th parameters let you tell it the values of the first and last messages that you want to examine. This example uses WM_MOUSEFIRST and WM_MOUSELAST to check all messages, but you can change that to pick out only the messages you want to remove.

  7. Bindumol says:

    Thanks.
    My issue is fixed when i use the current window handle as the Peekmessage parameter
    while (PeekMessage(out msg, windowHandle,
    WM_MOUSEFIRST, WM_MOUSELAST, PM_REMOVE))
    ;

    • RodStephens says:

      It sounds like it’s filtering all of the messages but only on the current window. I don’t know why it wouldn’t also remove the MouseEnter and MouseLeave events, unless those are somehow fired on a parent window.

      But if it’s working, that’s great!

Leave a Reply

Your email address will not be published. Required fields are marked *