Home
Search
 
What's New
Index
Books
Links
Q & A
Newsletter
Banners
 
Feedback
Tip Jar
 
C# Helper...
 
XML RSS Feed
Follow VBHelper on Twitter Follow VBHelper on Twitter
 
 
 
MSDN Visual Basic Community
 
 
 
 
 
TitleFlush click events to prevent the user from clicking a button while its code is still running in Visual Basic .NET
DescriptionThis example shows how to flush click events to prevent the user from clicking a button while its code is still running in Visual Basic .NET.
Keywordssyntax, API, threading, threads, BackgroundWorker, controls, events, flush events, flush mouse events, Visual Basic .NET, VB.NET
CategoriesControls, Miscellany, Software Engineering
 

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 Sub btnWaitNow_Click(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles btnWaitNow.Click
    ' None of these seem to work.
    'Me.Enabled = False
    'RemoveHandler btnWaitNow.Click, AddressOf
    ' btnWaitNow_Click
    btnWaitNow.Enabled = False
    Me.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())

    'Me.Enabled = True
    'AddHandler btnWaitNow.Click, AddressOf btnWaitNow_Click
    btnWaitNow.Enabled = True
    Me.Cursor = Cursors.Default
End Sub
 
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)> _
Private Structure NativeMessage
    Public handle As IntPtr
    Public msg As UInteger
    Public wParam As IntPtr
    Public lParam As IntPtr
    Public time As UInteger
    Public p As System.Drawing.Point
End Structure
Private Declare Auto Function PeekMessage Lib "user32.dll" ( _
    _
    ByRef lpMsg As NativeMessage, _
    ByVal hWnd As IntPtr, _
    ByVal wMsgFilterMin As UInteger, _
    ByVal wMsgFilterMax As UInteger, _
    ByVal flags As UInteger _
) As Boolean
Private Const WM_MOUSEFIRST As UInteger = &H200
Private Const WM_MOUSELAST As UInteger = &H20D
Private Const PM_REMOVE As Integer = &H1

' Flush all pending mouse events.
Private Sub FlushMouseMessages()
    Dim msg As NativeMessage
    ' Repeat until PeekMessage returns false.
    While (PeekMessage(msg, IntPtr.Zero, WM_MOUSEFIRST, _
        WM_MOUSELAST, PM_REMOVE))

    End While
End Sub
 
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 Sub btnWaitAndFlush_Click(ByVal sender As _
    System.Object, ByVal e As System.EventArgs) Handles _
    btnWaitAndFlush.Click
    Me.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()
    Me.Cursor = Cursors.Default
End Sub
 
 
 
 
Copyright © 1997-2010 Rocky Mountain Computer Consulting, Inc.   All rights reserved.
  Updated