Listview MouseLeave 事件:不包括 Header

Listview MouseLeave event: the Header is not included

我已经订阅了我的 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

我能做什么?

ListViewHeader实际上是一个不同的object,它的class名字是SysHeader32.
Header 显示在 Details View 中,但它是与 ListView 一起创建的,因此即使您看不到它(如果您至少添加了一个 Column),它也在那里。

它不是 ListView 的托管 child 控件:ListView.Controls collection 通常是空的。
但它是 SysListView32 本机控件的 child 控件,托管 class 从中派生,因此,您可以获得它的句柄并 读取 它的消息; WM_MOUSELEAVE 消息,在这种情况下。

  • 我们可以使用 FindWinDowEx or SendMessage (with LVM_GETHEADER), assign the handle to a NativeWindow class 获取它的句柄,覆盖它的 WndProc 拦截 我们需要处理的消息。在 WM_MOUSELEAVE 上,NativeWindow class 引发了 parent ListView 可以订阅的事件,引发了它自己的 MouseLeave 事件结果。

因为如上所述,Header 是一个不同的 object,当鼠标指针移动到其 Header 上时,ListView 会生成一个 MouseLeave 事件。我们需要覆盖此行为,因此仅当鼠标指针完全离开 ListView 边界时才会引发 MouseLeave 事件。

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

编辑:
添加了 WM_PARENTNOTIFY message check (for the WM_CREATE 事件通知)以处理 run-time 处的 Header 创建。


自定义 ListView 控件:

现在,如果您订阅此自定义控件的 MouseLeave 事件,则无论 Cursor 位于何处,只有当鼠标指针离开 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