Intercept single or double mouse click - only execute double click code on double click
Asked Answered
F

2

7

I have a situation where I am handling both single & double mouse click events on a form. In both cases something has to be loaded, however when a double click occurs, I do not wish to execute the code attached to the single click event.

Is there a way to intercept the mouse click's and check if double or single and then execute the right event appropriately?

Perhaps by intercepting the WndProc of the window or something?

Fiftyfifty answered 27/1, 2011 at 9:31 Comment(1)
Is this WinForms or WPF?Worldweary
C
6

No, that's pretty much impossible unless you have a time machine. And it doesn't really even make sense once you understand how Windows distinguishes double-clicks from single-clicks.

It turns out that Raymond Chen (a developer on the Windows Shell team) explains exactly that in a blog entry titled "Logical consequences of the way Windows converts single-clicks into double-clicks".

Specifically, Windows only knows to interpret something as a double-click because a second click has occurred within the interval specified by the GetDoubleClickTime function. Because it would require clairvoyance (as Raymond so eloquently puts it) to determine ahead of time if something is going to be a single or double click, the window manager goes ahead and sends a WM_LBUTTONDOWN message as soon as the first click is received. The WM_LBUTTONDBLCLK message is only sent later, after the second click is confirmed to actually represent a double-click. The upshot is that your application will always receive two WM_LBUTTONDOWN messages for each WM_LBUTTONDBLCLK message that is received.

Now, the .NET Framework designers understood how this process works and designed the events that are raised accordingly. Of course, they can't do anything about a single click always occurring before a double-click, but they were able to suppress the second click message if it is determined that the user intended that to be part of a double-click event. As the documentation for the Control.MouseClick event (which roughly corresponds to the WM_LBUTTONDOWN message) tells us:

Two single clicks that occur close enough in time, as determined by the mouse settings of the user's operating system, will generate a MouseDoubleClick event instead of the second MouseClick event.

Raymond's blog article that I linked to above does, however, propose a possible workaround for apps and developers who insist on a design where the double-click action is unrelated to the single-click action (although neither of us recommend that you actually do this):

Now suppose you're a program that nevertheless wants to continue with the dubious design of having the double-click action be unrelated to the single-click action. What do you do?

Well, one thing you could do is to do nothing on receipt of the WM_LBUTTONDOWN message aside from set a timer to fire in GetDoubleClickTime() milliseconds. [Corrected 10am.] If you get a WM_LBUTTONDBLCLK message within that time, then it was a double-click after all. If you don't, then it must have been a single-click, so you can do your single-click action (although a bit late).

Chrystel answered 27/1, 2011 at 9:41 Comment(2)
I've deleted my answer as I completely missed the point of the question.Worldweary
This might help: #9554095Blemish
G
4

Here is code to do what you requested.

Public Class Form1
    Const WM_LBUTTONDOWN As Integer = &H201
    Const WM_LBUTTONDBLCLK As Integer = &H203

    Private WithEvents tmrDoubleClicks As Timer

    Dim isDblClk As Boolean
    Dim firstClickTime As Date
    Dim doubleClickInterval As Integer

    Sub New()

        ' This call is required by the designer.
        InitializeComponent()
        tmrDoubleClicks = New Timer

        ' Add any initialization after the InitializeComponent() call.
        tmrDoubleClicks.Interval = 50
        doubleClickInterval = CInt(Val(Microsoft.Win32.Registry.CurrentUser.
                                       OpenSubKey("Control Panel\Mouse").
                                       GetValue("DoubleClickSpeed", 1000)))
    End Sub

    Protected Overrides Sub Dispose(ByVal disposing As Boolean)
        Try
            If disposing AndAlso components IsNot Nothing Then
                components.Dispose()
            End If
            If disposing AndAlso tmrDoubleClicks IsNot Nothing Then
                tmrDoubleClicks.Dispose()
            End If
        Finally
            MyBase.Dispose(disposing)
        End Try
    End Sub

    Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
        Select Case m.Msg
            Case WM_LBUTTONDOWN
                If Not isDblClk Then
                    firstClickTime = Now
                    tmrDoubleClicks.Start()
                End If

            Case WM_LBUTTONDBLCLK
                isDblClk = True
                tmrDoubleClicks.Stop()
                DoubleClickActivity()
                isDblClk = False

            Case Else
                MyBase.WndProc(m)
        End Select
    End Sub

    Private Sub DoubleClickActivity()
        'implement double click activity here
        Dim r As New Random(Now.TimeOfDay.Seconds)
        Me.BackColor = Color.FromArgb(r.Next(0, 255), 
                                      r.Next(0, 255), 
                                      r.Next(0, 255))
    End Sub

    Private Sub SingleClickActivity()
        'implement single click activity here
        Beep()
    End Sub

    Private Sub tmrDoubleClicks_Tick(ByVal sender As Object, 
                                     ByVal e As System.EventArgs 
                                    ) Handles tmrDoubleClicks.Tick

        If Now.Subtract(firstClickTime).TotalMilliseconds > 
          doubleClickInterval Then

            'since there was no other click within the doubleclick speed,
            'stop waiting and fire the single click activity
            isDblClk = False
            tmrDoubleClicks.Stop()
            SingleClickActivity()
        End If
    End Sub
End Class

The crux of this code is to delay firing the click event till the double click time elapses. If within that time, another click event occurs within that time, the double click event is called without calling the click event. If, however, there is no double click, the click event is called.

This delay is particularly noticeable on computers that have a longer double click speed. On a typical computer, the double click speed is 500ms so the code will run the click event somewhere between 500ms and 600ms after a click occured.

Gabriello answered 28/1, 2011 at 10:30 Comment(3)
I couldn't help but noticing that you never call the Dispose method for the Timer object that you create in the constructor. That's almost worth a downvote from me.Chrystel
@Cody: Sorry about that. I usually place the Dispose methods in the Form.Designer.vb file (where the designer-generated code is). Thanks for pointing it out.Gabriello
No big deal, it's just that far too many people either forget about this or don't know about it at all. When posting a complete solution that someone is likely to copy-and-paste into their IDE, it's important to make sure you point this kind of thing out. And, upon further thought, it actually makes more sense to just add the timer control to the components collection of the form. That way, it'll be automatically disposed by the designer-generated code without anyone having to explicitly remember to add it. System.Windows.Forms.Timer does inherit from Component, after all.Chrystel

© 2022 - 2024 — McMap. All rights reserved.