【问题标题】:Listview MouseLeave event: the Header is not includedListview MouseLeave 事件:不包括 Header
【发布时间】:2020-07-11 17:37:34
【问题描述】:

我已经订阅了我的 ListView 的 MouseLeave 事件。当鼠标指针离开 ListView 边界时,应该引发该事件。

这可行,但是当鼠标指针进入 ListView 的 Header 然后离开 ListView 边界时,不会引发该事件。

Private Sub LV1_test_MouseLeave(sender As Object, e As EventArgs) Handles LV1_test.MouseLeave
    // Not raised when the Pointer leaves the premises from the top of the ListView
End Sub

我能做什么?

【问题讨论】:

标签: vb.net winforms listview events mouseleave


【解决方案1】:

ListView Header其实是一个不同的对象,它的类名是SysHeader32。
Header 显示在 Details View 中,但它是与 ListView 一起创建的,因此即使您看不到它(如果您至少添加了一个 Column),它也存在。

它不是 ListView 的托管子控件:ListView.Controls 集合通常为空。
但它是托管类派生自的SysListView32 本机控件的子控件,因此,您可以获取它的句柄并读取它的消息; WM_MOUSELEAVE 消息,在这种情况下。

  • 我们可以使用FindWinDowEx 或SendMessage(使用LVM_GETHEADER)获取它的句柄,将句柄分配给NativeWindow 类,覆盖它的WndProc 并拦截我们收到的消息需要处理。在WM_MOUSELEAVE 上,NativeWindow 类引发 parent ListView 可以订阅的事件,从而引发它自己的MouseLeave 事件。

如上所述,由于 Header 是一个不同的对象,当鼠标指针移到其 Header 上时,ListView 会生成一个MouseLeave 事件。我们需要重写这个行为,所以MouseLeave 事件只有在鼠标指针完全离开 ListView 边界时才会触发。

  • 我们可以覆盖OnMouseLeave,验证MousePosition(转换为客户端度量)返回的位置是否在ListView客户端范围内,并让该方法仅在没有时引发MouseLeave事件。

编辑:
添加了WM_PARENTNOTIFY 消息检查(用于WM_CREATE 事件通知)以在运行时处理标头创建。


自定义 ListView 控件:

现在,如果您订阅此自定义控件的MouseLeave 事件,则该事件仅在鼠标指针离开 ListView 的客户区时引发,无论光标位于何处。

Imports System.ComponentModel
Imports System.Runtime.InteropServices
Imports System.Windows.Forms

<DesignerCategory("Code")>
Class ListViewCustom
    Inherits ListView

    Private Const LVM_GETHEADER As Integer = &H1000 + 31

    <DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
    Friend Shared Function SendMessage(hWnd As IntPtr, uMsg As Integer, wParam As IntPtr, lParam As IntPtr) As IntPtr
    End Function

    Private sysHeader As SysHeader32 = Nothing

    Private Sub AddSysHeaderHandler()
        If DesignMode Then Return
        If sysHeader Is Nothing Then
            Dim sysHeaderHwnd = SendMessage(Me.Handle, LVM_GETHEADER, IntPtr.Zero, IntPtr.Zero)
            If sysHeaderHwnd <> IntPtr.Zero Then
                sysHeader = New SysHeader32(sysHeaderHwnd)
                AddHandler sysHeader.SysHeaderMouseLeave,
                    Sub(s, evt)
                        Me.OnMouseLeave(evt)
                    End Sub
            End If
        End If
    End Sub

    Protected Overrides Sub OnHandleCreated(e As EventArgs)
        MyBase.OnHandleCreated(e)
        AddSysHeaderHandler()
    End Sub

    Protected Overrides Sub OnMouseLeave(e As EventArgs)
        If Not Me.ClientRectangle.Contains(PointToClient(MousePosition)) Then
            MyBase.OnMouseLeave(e)
        End If
    End Sub

    ' Handles the Header creation at run-time
    Protected Overrides Sub WndProc(ByRef m As Message)
        Select Case m.Msg
            Case &H210 'WM_PARENTNOTIFY
                Dim msg As Integer = m.WParam.ToInt32() And &HFFFF
                Select Case msg
                    Case &H1 ' WM_CREATE
                        AddSysHeaderHandler()
                End Select
        End Select
        MyBase.WndProc(m)
    End Sub

    Protected Overrides Sub Dispose(disposing As Boolean)
        If (disposing) Then sysHeader?.ReleaseHandle()
        MyBase.Dispose(disposing)
    End Sub

    Private Class SysHeader32
        Inherits NativeWindow

        Public Event SysHeaderMouseLeave As EventHandler(Of EventArgs)

        Public Sub New(handle As IntPtr)
            AssignHandle(handle)
        End Sub

        Protected Friend Overridable Sub OnSysHeaderMouseLeave(e As EventArgs)
            RaiseEvent SysHeaderMouseLeave(Me, e)
        End Sub

        Protected Overrides Sub WndProc(ByRef m As Message)
            Select Case m.Msg
                Case &H2A3 'WM_MOUSELEAVE
                    OnSysHeaderMouseLeave(EventArgs.Empty)
                    m.Result = IntPtr.Zero
                    Exit Select
                Case Else
                    ' NOP: Log other messages, add more cases...
            End Select
            MyBase.WndProc(m)
        End Sub
    End Class
End Class

【讨论】:

  • 要获取标头控制句柄,请发送 LVM_GETHEADER。从标头获取鼠标离开通知需要调用 TrackMouseEvent(),使用第一个 WM_MOUSEMOVE 进行调用。但是请记住,从顶部使用鼠标仍然存在问题,还没有 MouseEnter。使用计时器或 Application.Idle 确实要简单得多。
  • @Hans Passant 谢谢汉斯。我想过这个问题。另一种可能的方法是在第一个WM_NCHITTEST 之后处理WM_NCMOUSEMOVE,因为每次指针从任何一侧进入客户区时都会发送这些信息,然后检查是否存在标头或创建的 ListView 是否没有。但我认为它可能会变得复杂(长)并且(因为 OP 只是要求MouseLeave 事件)它已经足够好了。我回答这个主要是为了发布与NativeWindow 用法相关的内容。不过,我会按照建议升级。
  • 这样就解决了问题。绝对够用了,非常感谢!
  • 不客气 :) 我可能会给它一个曲折,从顶部处理 MouseEnter 事件。我会通知你的。
猜你喜欢
  • 2012-03-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-19
  • 2019-12-16
  • 2016-04-03
  • 2012-09-15
  • 1970-01-01
相关资源
最近更新 更多