The ListView Header is actually a different object, its class name is SysHeader32.
The Header is shown in Details View, but it's created along with the ListView, so it's there even if you cannot see it (if you have added at least one Column, that is).
It's not a managed child Control of the ListView: the ListView.Controls collection is usually empty.
But it's a child control of the SysListView32 native control from which the managed class derives, thus, you can get its handle and read its messages; the WM_MOUSELEAVE message, in this case.
- We can get its handle using FindWinDowEx or SendMessage (with
LVM_GETHEADER), assign the handle to a NativeWindow class, override its WndProc and intercept the messages we need to handle. On WM_MOUSELEAVE, the NativeWindow class raises an event that the parent ListView can subscribes to, raising its own MouseLeave event as a result.
Since, as described, the Header is a distinct object, the ListView generates a MouseLeave event when the mouse pointer is moved over its Header. We need to override this behavior, so the MouseLeave event is only raised when the mouse Pointer leaves the ListView bounds completely.
- We can override
OnMouseLeave, verify whether the position returned by MousePosition (translated to client measures) falls within the ListView client bounds and let the method raise the MouseLeave event only when it doesn't.
EDIT:
Added WM_PARENTNOTIFY message check (for the WM_CREATE event notification) to handle the Header creation at run-time.
Custom ListView Control:
Now, if you subscribe to the MouseLeave event of this Custom Control, the event is raised only when the Mouse Pointer leaves the Client Area of the ListView, no matter where the Cursor is located.
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