1

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?

1

1 Answer 1

1

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
Sign up to request clarification or add additional context in comments.

4 Comments

To get the header control handle send LVM_GETHEADER. Getting the mouse leave notification from the header requires pinvoking TrackMouseEvent(), use the first WM_MOUSEMOVE to make the call. But keep in mind that there's still a problem with mousing from the top, no MouseEnter yet. It really is much simpler with a timer or Application.Idle.
@Hans Passant Thanks Hans. I thought about it. Another possible way was to handle WM_NCMOUSEMOVE after the first WM_NCHITTEST, since these are sent each time the pointer enters the client area from any side, then check whether the header is there or the ListView is created without one instead. But I thought it could get complicated (long) and (since the OP just asked for the MouseLeave event) that it was good enough. I answered this one mainly to post something related to the NativeWindow usage. However, I'll see to upgrade as suggested.
That solves the problem. It is absolutely sufficient.Thank you very much!
You're welcome :) I'll probably give it a twists, to handle the MouseEnter event from the top. I'll notify you.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.