Search code examples
vb.netwinformslistviewcolumnheader

Show ToolTips on ListView first row in Framework 4.0


I have a WinForm application developed in Framework 2.0 with VB.Net which was using the event MouseMove on all the ListView objects to display ToolTip text on the first row of the ListViews - as it's not possible to have ToolTips on ColumnHeader, as far as I know, without third part tools.

The problem is that since I converted the application to Framework 4.0 this "trick" is not working and the ToolTips are not displayed anymore.

Does anyone know a solution or, even better, a way to display ToolTips on ListView ColumnHeaders?

Here's my code snippet:

Private Sub ShowTooltip(ByVal sender As Object, ByVal e As MouseEventArgs) 
    Handles myListView.MouseMove
  Dim iColumn As System.Int32 = FindListViewColumnHeader(e.X, e.Y)
  If Me.myListView.Columns.Count > 0 AndAlso iColumn >= 0 AndAlso
     iColumn <= Me.myListView.Columns.Count - 1 Then
         Me.myToolTip.Active = True
         Me.myToolTip.UseAnimation = True
         Me.myToolTip.UseFading = True
         Me.myToolTip.AutomaticDelay = 10000
         Me.myToolTip.AutoPopDelay = 10000
         Me.myToolTip.InitialDelay = 0
         Me.myToolTip.ReshowDelay = 2000

         Dim sTooltipText As System.String = SomeText(...)
         If sTooltipText <> DirectCast(Me.myToolTip.Tag, System.String) Then
                 Me.myToolTip.Tag = sTooltipText
                 Me.myToolTip.SetToolTip(Me.myListView, sTooltipText)
         End If
  Else
         Me.myToolTip.Active = False
  End If
End Sub

Protected Overridable Function FindListViewColumnHeader(ByVal X As System.Int32, 
       ByVal Y As System.Int32) As System.Int32
   If Y > 20 And Y < 40 Then
       Dim iCount As System.Int32
       Dim iLeft As System.Int32
       For iCount = 0 To myListView.Columns.Count - 1
           iLeft = iLeft + myListView.Columns(iCount).Width
           If X <= iLeft Then
               Return iCount
               Exit For
           End If
       Next
       Return iCount
   Else
       Return -1
   End If
End Function

Note: myToolTip is

Friend WithEvents myToolTip As System.Windows.Forms.ToolTip

and myListView is

Protected WithEvents myListView As System.Windows.Forms.ListView

Please notice that, as suggested in the question: How to set tooltip for a ListviewItem, ShowItemToolTips is already set to True.


Solution

  • You can get the handle of the header column and subclass it:

    <DllImport("user32.dll", SetLastError:=True)> _
    Private Shared Function SetWindowLong(ByVal hWnd As IntPtr, ByVal nIndex As Integer, ByVal newProc As Win32WndProc) As IntPtr
    
    End Function
    
    <DllImport("user32.dll")> _
    Private Shared Function CallWindowProc(lpPrevWndFunc As IntPtr, hWnd As IntPtr, Msg As UInteger, wParam As Integer, lParam As Integer) As Integer
    End Function
    
    <DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
    Private Shared Function SendMessage(ByVal hWnd As IntPtr, ByVal Msg As UInteger, ByVal wParam As Integer, ByVal lParam As Integer) As IntPtr
    End Function
    
    Private Delegate Function Win32WndProc(ByVal hWnd As IntPtr, ByVal Msg As UInteger, ByVal wParam As Integer, ByVal lParam As Integer) As Integer
    
    Private Const GWL_WNDPROC As Integer = -4
    Private Const WM_LBUTTONDOWN As Integer = &H201
    Private Const WM_MOUSEMOVE As Integer = &H200
    
    Private oldWndProc As IntPtr = IntPtr.Zero
    Private newWndProc As Win32WndProc = Nothing
    
    Private Sub SubclassHWnd(ByVal hWnd As IntPtr)
        'hWnd is the window you want to subclass...,
        'create a new delegate for the new wndproc
        newWndProc = New Win32WndProc(AddressOf MyWndProc)
        'subclass
        oldWndProc = SetWindowLong(hWnd, GWL_WNDPROC, newWndProc)
    End Sub
    
    Private Function MyWndProc(ByVal hWnd As IntPtr, ByVal Msg As UInteger, ByVal wParam As Integer, ByVal lParam As Integer) As Integer
    
        Select Case Msg
            Case WM_LBUTTONDOWN
                'The lower 2 bytes of lParam are the x coordinate 
                'and the higher 2 bytes the y.
                ToolTip1.Show("My tooltip", ListView1, lParam And &HFFFF, (lParam >> 16) And &HFF, 2000)
                Exit Select
            Case Else
                Exit Select
        End Select
    
        Return CallWindowProc(oldWndProc, hWnd, Msg, wParam, lParam)
    End Function
    

    To subclass the header use:

        'LVM_GETHEADER = &H101F
        Dim hwndHeader As IntPtr = SendMessage(ListView1.Handle, &H101F, 0, 0)
        SubclassHWnd(hwndHeader)
    

    I used the WM_LBUTTONDOWN event for convenience. You can use the WM_MOUSEMOVE event and check which column the mouse is etc... and show the tooltip

    The code for subclassing: Subclass an Unmanged Window in C#