I've subscribed to the MouseLeave event of my ListView. The event should be raised when the Mouse Pointer leaves the ListView bounds.
That works, but when the Mouse Pointer enters the ListView's Header and then leaves the ListView bounds, the event is not be raised.
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
What can I do?
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.
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.
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