Click here to Skip to main content
15,889,754 members
Articles / Programming Languages / C#
Article

ListView with Item-Hover Event

Rate me:
Please Sign up or sign in to vote.
4.93/5 (12 votes)
21 Oct 20032 min read 232K   4.1K   67   31
An article to display tooltips over listview items and subitems.

ListView with ItemHover event

Introduction

The ListView used by Microsoft Windows Explorer shows tool tips when a particular item is partly visible, saving the user the task of scrolling or resizing. Keeping this as my goal, I set out to achieve the same effect.

All controls that derive from System.Windows.Forms.Control have a MouseHover event. Most of the time this event is used for displaying additional or extended information about the control. For controls like the ListView, which displays different data, this event although useful, leaves the programmer aching for more information. The details about the items within the control are elusive to get at.

While there are events like ItemCheck, ItemActivate, ItemDrag, there isn't any event like the ItemHover. A little investigation with the Spy++ tool and digging through MSDN revealed that every ListView maintains an internal tool tip. This tool tip sends a message in the form of a WM_NOTIFY handler whenever it needs text from the ListView. I ended up trapping this message and exposing it off as an ItemHover event. The client can then consume this event and display additional information as needed or display a tool tip as shown in figure.

However, I quickly found out that it is very distracting after a while to see a series of tool tips flickering around on the screen. This happens because the ListView gets the message when the mouse hovers over its items, but it doesn't know whether the item or sub item is visible. It would be nice to add that information as an event argument. That was achieved by comparing the string width to the actual displayed rectangle of the item or sub item.

Armed with this information, the client can intelligently display more information about the partially hidden item, as seems fit. I tried showing it in a tool tip, you can even show it on a StatusBar or maybe the more courageous can display a Balloon Tip.

Longer lines of text wouldn't wrap around to display in the tool tip. For the purpose of the sample I wrote a small procedure to wrap those lines of text into multiple lines, by inserting the new line character. You can change this logic to fit your needs. Enjoy the code and any feedback is appreciated.

Using the code

A small example of how to use the control has been included in the form. This control works properly when the ListView is in Report mode. I haven't tested it with any other mode.

Points

The ListView can also be made to display its internal tool tip by setting its style to LVS_EX_INFOTIP. However, according to MSDN, the ListView needs to have the LVS_FULL_ROWSELECT style set too for sub items. Hence I experimented with this method.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
United States United States
Ramesh is very much into Microsoft technologies, and silently marvels at the power of Windows SDK.
He juggles his time between his family, work and his computer.

Comments and Discussions

 
GeneralDoes not work at all Pin
ReorX1-Aug-08 1:56
ReorX1-Aug-08 1:56 
GeneralSolution for displaying custom tooltip control on subitems [modified] Pin
gbrlhtclcq21-Sep-07 5:37
gbrlhtclcq21-Sep-07 5:37 
GeneralRe: Solution for displaying custom tooltip control on subitems Pin
Russell Gantman12-Nov-07 7:11
Russell Gantman12-Nov-07 7:11 
GeneralRe: Solution for displaying custom tooltip control on subitems Pin
DigiOz Multimedia25-Sep-08 9:22
DigiOz Multimedia25-Sep-08 9:22 
GeneralRe: Solution for displaying custom tooltip control on subitems Pin
JBress8-Jan-10 0:36
JBress8-Jan-10 0:36 
GeneralRe: Solution for displaying custom tooltip control on subitems Pin
gbrlhtclcq8-Jan-10 2:10
gbrlhtclcq8-Jan-10 2:10 
QuestionDisplay too slow! Pin
kidconan28-May-07 22:07
kidconan28-May-07 22:07 
QuestionSame problem in .NET 2.0 Pin
FlyDN26-Nov-06 21:30
FlyDN26-Nov-06 21:30 
QuestionI need set Tooltip for each listview header. Pin
MichaelNu24-Oct-06 2:41
MichaelNu24-Oct-06 2:41 
GeneralProblems using .NET 2.0 Pin
DanielMuhlig9-Jan-06 2:35
DanielMuhlig9-Jan-06 2:35 
GeneralSimple solution Pin
fkoestner26-Aug-05 3:34
fkoestner26-Aug-05 3:34 
GeneralRe: Simple solution Pin
charlie ash30-Nov-05 16:55
charlie ash30-Nov-05 16:55 
GeneralRe: Simple solution Pin
kidconan28-May-07 21:55
kidconan28-May-07 21:55 
GeneralRe: Simple solution Pin
Baranidahran2-Aug-07 0:46
Baranidahran2-Aug-07 0:46 
AnswerRe: Simple solution Pin
Andreas Saurwein27-Oct-08 10:28
Andreas Saurwein27-Oct-08 10:28 
GeneralRe: Simple solution Pin
fkoestner27-Oct-08 10:47
fkoestner27-Oct-08 10:47 
GeneralRe: Simple solution Pin
geoff.appleby29-Nov-09 18:55
geoff.appleby29-Nov-09 18:55 
GeneralLVS_EX_INFOTIP on a multi-column listview Pin
id10t13-Mar-05 16:20
id10t13-Mar-05 16:20 
GeneralRe: LVS_EX_INFOTIP on a multi-column listview Pin
bswole14-Mar-05 3:35
bswole14-Mar-05 3:35 
QuestionOnly displays once? Pin
bswole10-Mar-05 6:21
bswole10-Mar-05 6:21 
AnswerRe: Only displays once? Pin
Sam DenHartog22-May-06 10:30
Sam DenHartog22-May-06 10:30 
QuestionVB.NET version ? Pin
mistert0064-Jun-04 5:56
mistert0064-Jun-04 5:56 
AnswerRe: VB.NET version ? Pin
mr_lasseter16-Jun-04 7:29
mr_lasseter16-Jun-04 7:29 
Converted VB code. Wrapped this into the listview itself. It inherits off of a listview control
#Region " HoverItem Code "

Private Const WM_NOTIFY As Int32 = &H4E
' tooltip
Private Const TTN_FIRST As Int32 = -520
Private Const TTN_NEEDTEXT As Int32 = (TTN_FIRST - 10)


Private Const LVM_GETITEMRECT As Int32 = LVM_FIRST + 14
Private Const LVM_GETCOLUMNWIDTH As Int32 = LVM_FIRST + 29
Private Const LVM_SUBITEMHITTEST As Int32 = LVM_FIRST + 57
Private Const LVM_GETSTRINGWIDTHW As Int32 = LVM_FIRST + 87

Private Const LVIR_LABEL As Integer = 2

Private m_lastItem As Integer = 0
Private m_lastSubItem As Integer = 0

<structlayout(layoutkind.sequential)> _
Private Structure NMHDR
Public hwndFrom As IntPtr
Public idFrom As Int32
Public code As Int32
End Structure

<structlayout(layoutkind.sequential)> _
Private Structure LVHITTESTINFO
Public pt As Point
Public flags As Int32
Public iItem As Int32
Public iSubItem As Int32
End Structure

Protected Overrides Sub OnNotifyMessage(ByVal m As System.Windows.Forms.Message)
Dim n As NMHDR

If m.Msg = WM_NOTIFY Then
n = CType(Marshal.PtrToStructure(m.LParam, GetType(NMHDR)), NMHDR)
If n.code = TTN_NEEDTEXT Then
NeedText()
End If
End If

End Sub

Private Sub NeedText()
Dim ihea As New ItemHoverEventArgs
Dim lvh As New LVHITTESTINFO

lvh.pt = PointToClient(Control.MousePosition)
Me.ListView_SubItemHitTest(lvh)
'Check if Hit Test Returned Valid Object
If lvh.iItem < 0 Or lvh.iSubItem < 0 Then
Return
End If
ihea.Item = lvh.iItem
ihea.SubItem = lvh.iSubItem
ihea.ItemTextInVisible = IsItemTextHidden(lvh)

If Me.ToolTipOnLongItems Then
m_itemHover(Me, ihea)
End If

End Sub

'Finds whether the listview item text is completely visible or
'contains a trailing ellipsis "...".
'True if text is hidden, false otherwise
Private Function IsItemTextHidden(ByVal lvhi As LVHITTESTINFO) As Boolean
Dim rect As Rectangle = Rectangle.Empty
Dim stringWidth As Int32
Dim colWidth As Int32

If lvhi.iSubItem > 0 Then
' MSDN : ListView_GetStringWidth() talks something about padding.
' for subitem: The text is padded with 6 pixels on either sides

stringWidth = ListView_GetStringWidth(Me.Items(lvhi.iItem).SubItems(lvhi.iSubItem).Text)
colWidth = ListView_GetColumnWidth(lvhi.iSubItem)
Return ((stringWidth + 12) > colWidth)
Else
'MSDN : ListView_GetStringWidth() talks something about padding.
'for item: The text is padded with 2 pixel on either sides

stringWidth = ListView_GetStringWidth(Me.Items(lvhi.iItem).Text)
colWidth = ListView_GetColumnWidth(0)
ListView_GetItemRect(lvhi.iItem, LVIR_LABEL, rect)
rect = Rectangle.Inflate(rect, -2, -2)
Return ((rect.Left + stringWidth + 4) > colWidth)
End If
End Function

Private Function ListView_GetItemRect(ByVal iItem As Integer, ByVal iCode As Int32, ByRef lpRect As Rectangle)
Dim rct As New Rectangle
Dim pRct As IntPtr = Marshal.AllocHGlobal(Marshal.SizeOf(rct))
Marshal.StructureToPtr(rct, pRct, True)

SendMessage(Handle, LVM_GETITEMRECT, iItem, pRct)

lpRect = CType(Marshal.PtrToStructure(pRct, GetType(Rectangle)), Rectangle)
Marshal.FreeHGlobal(pRct)
Return True
End Function

Private Function ListView_GetStringWidth(ByVal psz As String) As Int32
Dim ptr As IntPtr = Marshal.StringToHGlobalAuto(psz)
Dim iRet As Int32 = SendMessage(Handle, LVM_GETSTRINGWIDTHW, 0, ptr)
Marshal.FreeHGlobal(ptr)
Return iRet
End Function

Private Function ListView_GetColumnWidth(ByVal iCol As Integer) As Int32
Return SendMessage(Handle, LVM_GETCOLUMNWIDTH, iCol, IntPtr.Zero)
End Function

Private Sub ListView_SubItemHitTest(ByRef lvhi As LVHITTESTINFO)
Dim ptr As IntPtr = Marshal.AllocHGlobal(Marshal.SizeOf(lvhi))
Marshal.StructureToPtr(lvhi, ptr, True)
SendMessage(Handle, LVM_SUBITEMHITTEST, IntPtr.Zero, ptr)
lvhi = CType(Marshal.PtrToStructure(ptr, GetType(LVHITTESTINFO)), LVHITTESTINFO)
Marshal.FreeHGlobal(ptr)
End Sub

Public Class ItemHoverEventArgs
Inherits EventArgs

'ref to listview item and sub items
Protected m_item As Integer
Protected m_subitem As Integer
Protected m_itemTextVisible As Boolean

Public Property Item()
Get
Return m_item
End Get
Set(ByVal Value)
m_item = Value
End Set
End Property

Public Property SubItem()
Get
Return m_subitem
End Get
Set(ByVal Value)
m_subitem = Value
End Set
End Property

'The item or subitem has text which is currently
'TRUE = Invisible, FALSE = Visible.
Public Property ItemTextInVisible()
Get
Return m_itemTextVisible
End Get
Set(ByVal Value)
m_itemTextVisible = Value
End Set
End Property
End Class

Private Sub m_itemHover(ByVal sender As Object, ByVal e As ItemHoverEventArgs)
Dim ToolTip1 As ToolTip
If (e.ItemTextInVisible) Then
Dim s As String = Me.Items(e.Item).SubItems(e.SubItem).Text
If (m_lastItem <> e.Item Or m_lastSubItem <> e.SubItem) Then
ToolTip1 = New ToolTip
ToolTip1.SetToolTip(Me, s)

End If
Else
ToolTip1 = Nothing
End If
m_lastItem = e.Item
m_lastSubItem = e.SubItem
End Sub

#End Region

Mike
GeneralRe: VB.NET version ? Pin
mistert00620-Oct-04 9:34
mistert00620-Oct-04 9:34 
GeneralRe: VB.NET version ? Pin
Anonymous26-Nov-04 3:54
Anonymous26-Nov-04 3:54 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.