|
I simply compiled the sample:
- It shows the wrong tooltip (always the first one in the lifetime)
- Tooltips flicker shortly and disappear immediately afterwards
- It makes assumptions about FullRowSelect (for which it does not work)
Some of these problems are related with the fact, that using WM_NOTIFY and looking for TTN_NEEDTEXT does not work (TTN_NEEDTEXT does not reappear as expected)
As this seems to be the basic idea of the control, it looks as though the control is useless.
The Saviour of the World is a Penguin and Linus Torvalds is his Prophet.
|
|
|
|
|
Hi,
I wondered that it was not possible to use the .net 2.0 control "ToolTip" so it can be shown on hovering subitems of my listview control, not only for the first column... But finally i found a workaround. And the good new is that you can take advantage of the .net control such as adding an icon, a title to the tooltip !
Note : I use VB .net 2.0 but it can work for sure for C#.
This is how (and simply) I did it :
You have to add a listview of course. Mine's name is "MyListView".
First, add a ToolTip control in your app. I named it "MyToolTip".
Second, set the property "ShowItemToolTips" to true on your listview control.
Third, go into the code section of the event "ItemMouseHover" of the listview and copy-paste the following code :
Private Sub MyListView_ItemMouseHover(ByVal sender As Object, ByVal e As System.Windows.Forms.ListViewItemMouseHoverEventArgs) Handles MyListView.ItemMouseHover<br />
'duration is set to 2000 but can be changed<br />
'location can also be customized<br />
Me.MyToolTip.Show("Any custom text", Me.MyListView, MyListView.PointToClient(Control.MousePosition), 2000)<br />
<br />
End Sub
That's all, now our tooltip is displayed on hovering items and subitems !
You can access the hovered item with e.Item, if you need a per-item tooltip.
Gab H.
[EDIT]
However there is a (maybe big) drawback I just discovered : changing the delay properties of the Tooltip control will not change anything, as the listview control use the default Windows hovering delays for triggering ItemMouseHover... Aargh I'm looking for a solution but can't find. Any idea ? Can we rewrite ItemMouseHover so we can set the delays ? Thanks for any help!
-- modified at 13:27 Saturday 22nd September, 2007
|
|
|
|
|
I tried this in C# and I do not get the ItemMouseHover event when I hover over a subItem. I only get that event when I hover over the first column (in which case this works)
Is there anything special I need to do with my listView to enable Hover events with subitems?
Has any other C# programmers attempted this solution and got it to work?
Or could this be just a slight different in VB and C#?
I skate to where the puck is going to be, not where it is.
Wayne Gretzky
|
|
|
|
|
Thank you Gabriel! Your solution worked perfectly for me in VB.NET 2005, including the delay for the tooltip display.
Pete
Pete Soheil
DigiOz Multimedia
http://www.digioz.com
|
|
|
|
|
Hello, thx for the tip
First i tested your ItemMouseHover solution, worked nice.
However, i needed the tooltip to change on each subitem, even on the SAME item (when hovering the same row, in Details mode).
So ItemMouseHover was not the solution.
Here my SubItemMouseHover event implementation (created a ListViewEx derived from ListView ) :
private Point _mouseMoveLastLocation = Point.Empty;
private ListViewHitTestInfo _mouseMovePrevHitInfo = null;
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (e.Location != _mouseMoveLastLocation)
{
_mouseMoveLastLocation = e.Location;
if (this.SubItemMouseHoverEventAttached)
{
ListViewHitTestInfo info = base.HitTest(e.Location);
if (_mouseMovePrevHitInfo == null || info.SubItem != _mouseMovePrevHitInfo.SubItem)
{
int subItemIndex = ListViewEx.GetSubItemIndex(info.Item, info.SubItem);
OnSubItemMouseHover(new ListViewSubItemMouseHoverEventArgs(info.Item, info.SubItem, subItemIndex, e.Location));
}
_mouseMovePrevHitInfo = info;
}
}
}
#region SubItemMouseHover event
public class ListViewSubItemMouseHoverEventArgs : EventArgs
{
public readonly ListViewItem Item;
public readonly ListViewItem.ListViewSubItem SubItem;
public readonly int SubItemIndex;
public readonly Point Location;
public ListViewSubItemMouseHoverEventArgs(ListViewItem item, ListViewItem.ListViewSubItem subItem, int subItemIndex, Point location)
{
this.Item = item;
this.SubItem = subItem;
this.SubItemIndex = subItemIndex;
this.Location = location;
}
}
public delegate void ListViewSubItemMouseHoverEventHandler(object sender, ListViewSubItemMouseHoverEventArgs e);
[Category("Action")]
public event ListViewSubItemMouseHoverEventHandler SubItemMouseHover;
protected void OnSubItemMouseHover(ListViewSubItemMouseHoverEventArgs e)
{
if (this.SubItemMouseHoverEventAttached)
this.SubItemMouseHover(this, e);
}
private bool SubItemMouseHoverEventAttached { get { return this.SubItemMouseHover != null; } }
#endregion
public static int GetSubItemIndex(ListViewItem lvi, ListViewItem.ListViewSubItem lvsi)
{
if (lvi == null || lvsi == null) return -1;
for (int i = 0; i < lvi.SubItems.Count; i++) if (lvi.SubItems[i] == lvsi) return i;
return -1;
}
And to use it :
private void myListView_SubItemMouseHover(object sender, JBLib.Controls.ListViewEx.ListViewSubItemMouseHoverEventArgs e)
{
if (e.SubItemIndex == myDateColumn.Index)
myTooltip.Show("Double-click to change the date", myListView, e.Location.X + 12, e.Location.Y, 2000);
else if (e.SubItemIndex == myCityColumn.Index)
myTooltip.Show("Double-click to change the city", myListView, e.Location.X + 12, e.Location.Y, 2000);
}
And maybe it also fix the delay problem on ItemMouseHover ?
(ps: sry for my english)
|
|
|
|
|
Thank you for your solution, I will try this
|
|
|
|
|
I see that the tooltips are display wrong because OnNotifyMessage catchs the message too slow. When I hover mouse B item, I still display the name of previous item. It is wrong! How Can I fix it?
|
|
|
|
|
I got the same problem to showing tooltip in .NET 2.0 (displayed once then disappearing). Can anyone help on this?
Cheers
Dan
|
|
|
|
|
How can i set Tooltip for each listview header and not for item?
|
|
|
|
|
Sweet solution, but I have some problems using it in .NET 2.0
It seems that after the tooltip has been displayed the first time, the ItemHover event is no longer fired .
Does anyone have a solution to this?
/Daniel
|
|
|
|
|
Not to minimize the contribution here , but I needed a very light-weight solution, and finally found an example on another site that did what I needed. I thought I would share it here, since it was so simple, yet I didn't think of it myself, and maybe others are searching for an answer, too.
Do the following:
1. Add a tooltip to your form.
2. Put the desired string in the Tag of each item. Alternatively, of course, you could construct whatever string you wanted on the fly.
3. Handle the following two events:
<br />
private void listView_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e)<br />
{<br />
ListViewItem thisItem = this.listView.GetItemAt( e.X, e.Y );<br />
<br />
if( thisItem != null )<br />
{<br />
this.toolTip.SetToolTip( this.listView, (string)thisItem.Tag );<br />
}<br />
else<br />
{<br />
this.toolTip.SetToolTip( this.listView, "" );<br />
}<br />
}<br />
<br />
<br />
private void listView_MouseLeave(object sender, System.EventArgs e)<br />
{<br />
this.toolTip.SetToolTip( this.listView, "" );<br />
}<br />
Hope this helps someone, like I have been helped by all the code here.
-- modified at 9:40 Friday 26th August, 2005
|
|
|
|
|
Thanks very much for your simple solution - works great for me
|
|
|
|
|
yeah! How simple it!
Di khap the gian khong ai tot bang me
Gian kho cuoc doi khong nang ganh bang cha
|
|
|
|
|
One problem with this method though is you don't get the actual tooltip (Show the tip after the inital delay) and we end up setting tooltips many times than is needed.
Another way is to add a handler to PopUp and get the index of the item in the Popup handler. By this the calculation is done only when the tooltip window is about to show and this has better performance as well.
|
|
|
|
|
Since this article is about subitem tooltips you might wish to adapt your solution a bit so it actually works with subitems too:
private void cList_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
{
ListViewItem thisItem = this.cList.GetItemAt(e.X, e.Y);
if(thisItem != null)
{
ListViewItem.ListViewSubItem thisSubItem = thisItem.GetSubItemAt(e.X, e.Y);
if(thisSubItem.Tag != null)
{
this.subItemToolTip.Show(thisSubItem.Tag.ToString(), this.cList, thisSubItem.Bounds.Location);
}
}
else
{
this.subItemToolTip.SetToolTip(this.cList, "");
}
}
And remove the MouseLeave handler, the mousemove event will take care of it, otherwise the tooltip disappears the moment its shown (since it will trigger a mouseleave for the listview).
Leon[^] - Enterprise Anti-Spam Server
|
|
|
|
|
Thank you - nice update!
|
|
|
|
|
actually that didn't quite work perfectly. i updated the code to include:
if(thisSubItem.Tag != null)
{
this.subItemToolTip.Show(thisSubItem.Tag.ToString(), this.cList, thisSubItem.Bounds.Location);
}
else
{
this.subItemToolTip.SetToolTip(this.cList, "");
}
I just added the else. until i did, ghost tooltips were left hanging around.
That might just be me though, because i had randomaly scattered blank subitems.
|
|
|
|
|
How do you get LVS_EX_INFOTIP to work on a multi-column listview?
Thanks.
|
|
|
|
|
I believe this current source works for multi-col listviews. Any col that has data wider than it is will display a tip when hovered over. However, keep in mind the "only displays once" problem.
|
|
|
|
|
It seems that if you hover over an item that displays a tooltip, then hover over an item that does not display a tooltip, when you go back to the exact same item that you hovered over before to display the tooltip, it does not display. However, if you hover over a different item that displays a tooltip, it will display, and it will also display if you then hover over the original.
In other words, a tooltip won't display but once for a particular item (once the tip disappears) until you hover over another "tooltipable" item.
|
|
|
|
|
This can be fixed by changing the following in the listViewTool1_ItemHover method:
else<br />
{<br />
toolTip1.SetToolTip(listViewTool1, string.Empty);<br />
}
To this:
else<br />
{<br />
toolTip1.SetToolTip(listViewTool1, string.Empty);<br />
m_lastItem = e.Item;<br />
m_lastSubItem = e.SubItem;<br />
}
Now it will always redisplay if you move off that item and then back on.
Cheers,
Sam DenHartog
|
|
|
|
|
Do you have a version of this for VB.NET ?
Thanks
|
|
|
|
|
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
|
|
|
|
|
Mike,
Thanks for the VB.NET code. Sorry, I do not know how to implement this as you described. Can you give me information on how to wrap this into the listview itself?
Thanks very much.
MisterT
|
|
|
|
|
just create a new user control and inherit from the listview control then copy and paste the code into the user control.
|
|
|
|
|