If you’ve worked with ListViews in WinForms before, you might have encountered the discouraging fact that the default ListView does not support multi-lined tool-tips for SubItems. For this to work, you have to create a custom control that inherits from ListView, and make some nasty calls to P/Invoke.
Here’s how this can be achieved:
public class ListViewEx : ListView
{
private const int WM_NOTIFY = 78;
private const int TTN_FIRST = -520;
private const int TTN_NEEDTEXT = (TTN_FIRST - 10);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct NMHDR
{
public IntPtr hwndFrom;
public Int32 idFrom;
public Int32 code;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct NMTTDISPINFO
{
public NMHDR hdr;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpszText;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
public string szText;
public IntPtr hinst;
public int uFlags;
public IntPtr lParam;
}
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SendMessage(IntPtr hWnd, IntPtr uMsg, IntPtr wParam, IntPtr lParam);
protected override void OnNotifyMessage(Message m)
{
// Handle only WM_NOTIFY messages
if (m.Msg != (int)WM_NOTIFY)
{
base.OnNotifyMessage(m);
return;
}
// Get the details of the WM_NOTIFY message, in the form of an NMHDR object
var nmHdr = (NMHDR) Marshal.PtrToStructure(m.LParam, typeof (NMHDR));
// We will handle only the TTN_NEEDTEXT message
if (nmHdr.code != TTN_NEEDTEXT)
return;
// Calculate the tool-tip
var tip = CalculateToolTip();
if (tip == null)
return;
// Get the details of the notification message in the form of an NMTTDISPINFO object
var nmttDispInfo = (NMTTDISPINFO)Marshal.PtrToStructure(m.LParam, typeof(NMTTDISPINFO));
// Set max-width, to force multi-line tooltips (here, I've hard-coded the max-width to 320px.
// You can calculated it based on the length of the tool-tip, screen resolution, etc.)
SendMessage(nmttDispInfo.hdr.hwndFrom, (IntPtr)1048, IntPtr.Zero, (IntPtr)320);
// Update tool-tip text
nmttDispInfo.lpszText = tip;
// Update the handle of the object that will host the tool-tip
nmttDispInfo.hdr.hwndFrom = m.HWnd;
// Update message information with new data
Marshal.StructureToPtr(nmttDispInfo, m.LParam, false);
}
private string CalculateToolTip()
{
// Convert MousePosition to position inside the ListView
var pt = PointToClient(MousePosition);
// Perform HitTest, to allow us to find the correct ListView item/subitem
var hitTestInfo = HitTest(pt);
var item = hitTestInfo.Item;
var subitem = hitTestInfo.SubItem;
// If we didn't hit any item, no need for a tool-tip
if (item == null)
return null;
return string.Format("Tool-tip for item: {0}\r\nSubitem: {1}", item.Index, item.SubItems.IndexOf(subitem));
}
}
As you can see, the code is not overly complex, but not something that you could have guessed without Binging it first.