拦截单击或双击鼠标 - 只在双击时执行双击代码

7

我有一个情况,需要在表单上处理单击和双击事件。在两种情况下都需要加载一些内容,但是当发生双击时,我不希望执行与单击事件相关的代码。

是否有一种方法可以拦截鼠标点击并检查是双击还是单击,然后适当地执行正确的事件?

也许通过拦截窗口的WndProc之类的方式可以实现?


这是WinForms还是WPF? - ChrisF
2个回答

6

不,除非你有一台时间机器,否则这几乎是不可能的。 一旦你了解Windows如何区分双击和单击,这甚至没有任何意义。

事实证明,Windows Shell团队的开发人员Raymond Chen在一篇名为“Windows将单击转换为双击的逻辑后果”的博客文章中详细解释了这一点。

具体来说,Windows只有在GetDoubleClickTime函数指定的时间间隔内发生第二次点击时才将其解释为双击。因为要提前确定某个操作是单击还是双击需要超人的洞察力(正如Raymond所说),窗口管理器会在接收到第一次点击时立即发送WM_LBUTTONDOWN消息。只有在确认第二次点击确实代表双击后,才会稍后发送WM_LBUTTONDBLCLK消息。总之,您的应用程序将始终接收每个接收到的WM_LBUTTONDBLCLK消息的两个WM_LBUTTONDOWN消息。
现在,.NET Framework 的设计者了解这个过程的工作原理,并相应地设计了引发事件。当然,他们无法阻止单击总是在双击之前发生,但如果确定用户想要将其作为双击事件的一部分,则可以抑制第二个单击消息。正如Control.MouseClick 事件(大致对应于 WM_LBUTTONDOWN 消息)的文档所述:

如果两次单击的时间间隔足够接近(由用户操作系统的鼠标设置确定),则会生成一个 MouseDoubleClick 事件,而不是第二个 MouseClick 事件。

我上面提到的 Raymond 的博客文章确实为那些坚持双击操作与单击操作无关的应用程序和开发人员提供了可能的解决方法(尽管我们都不建议您这样做)。
现在假设您是一个程序,尽管想继续采用双击操作与单击操作无关的可疑设计。那么你该怎么办呢?好吧,您可以在收到WM_LBUTTONDOWN消息时除了设置一个定时器以在GetDoubleClickTime()毫秒后触发之外,什么也不做。 [更正于上午10点。]如果您在该时间内收到WM_LBUTTONDBLCLK消息,则它确实是双击。如果没有,则必须是单击,因此您可以执行单击操作(虽然有点晚)。

我已经删除了我的答案,因为我完全错过了问题的要点。 - ChrisF
这可能会有所帮助: http://stackoverflow.com/questions/9553595/how-to-run-same-code-in-click-event-as-the-double-click-event - CrazyTim

4
这里是能够满足你要求的代码。
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

这段代码的关键在于延迟点击事件,直到双击时间流逝。如果在此期间内再次发生点击事件,则直接调用双击事件而不调用单击事件。但是,如果没有进行双击操作,则会调用单击事件。
这种延迟特别明显在双击速度较慢的电脑上。在典型的计算机上,双击速度为500毫秒,因此代码将在点击后的500毫秒至600毫秒之间运行单击事件。

1
我不禁注意到你在构造函数中创建了一个 Timer 对象,但从未调用过 Dispose 方法。这几乎让我想要给你一个踩下去的投票。 - Cody Gray
@Cody:对不起,我通常会将 Dispose 方法放在 Form.Designer.vb 文件中(即设计器生成的代码所在的位置)。感谢您指出。 - Alex Essilfie
没什么大不了的,只是太多人要么忘记了这一点,要么根本不知道。当发布一个完整的解决方案供别人复制粘贴到他们的IDE时,重要的是要确保你指出这种情况。而且,经过进一步的思考,实际上更有意义的做法是将计时器控件添加到窗体的“组件”集合中。这样,它将被设计器生成的代码自动处理,无需任何人明确记得添加它。毕竟,“System.Windows.Forms.Timer”确实继承自“Component”。 - Cody Gray

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接