Tooltip for individual column header

CToolTipCtrl	m_tooltip;

Step 2: Create the tooltip object

Override PreSubclassWindow() in your CListCtrl derived class. After calling the base class PreSubclassWindow(), create the tooltip object. We override the PreSubclassWindow() instead of OnCreate() because the control is usually attached to the C++ object after it has already been created – usually from a dialog resource – and therefore OnCreate is never called for the object. It is important to note that the call to GetDlgItem(0) may fail if the control was created with a style other than LVS_REPORT.

If you are deriving from CListView then the code to create the tooltip and add a tool to it can be moved to OnCreate() or the OnInitialUpdate() function.

void CMyListCtrl::PreSubclassWindow()
{
	CListCtrl::PreSubclassWindow();

	// Add initialization code
	m_tooltip.Create( this );
}

Step 3: Call RelayEvents() function of the tooltip object

Override PreTranslateMessage() and call the RelayEvents() function of the CToolTipCtrl object. Calling RelayEvents() gives the tooltip an oppurtunity to determine whether the mouse entered any of the tool areas. Although we pass on every message that the list view control gets, the tooltip control processes only the WM_?BUTTONDOWN, WM_?BUTTONUP and the WM_MOUSEMOVE messages.

BOOL CMyListCtrl::PreTranslateMessage(MSG* pMsg)
{
	m_tooltip.RelayEvent( pMsg );
	return CListCtrl::PreTranslateMessage(pMsg);
}

Step 4: Add helper function to add tooltips

A single tooltip control can handle multiple tools or multiple rectangular areas for which a tooltip is needed. The helper function AddHeaderToolTip() simply adds a new tool to the tooltip control.

// AddHeaderToolTip	- Add a tooltip for the column header
//			  The control mode should be LVS_REPORT
// Returns		- TRUE on success
// nCol			- the column index
// sTip			- the tooltip text. A NULL will use the 
//			  column header text
BOOL CMyListCtrl::AddHeaderToolTip(int nCol, LPCTSTR sTip /*= NULL*)
{
	const int TOOLTIP_LENGTH = 80;
	char buf[TOOLTIP_LENGTH+1];

	CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0);
	int nColumnCount = pHeader->GetItemCount();
	if( nCol >= nColumnCount)
		return FALSE;

	if( (GetStyle() & LVS_TYPEMASK) != LVS_REPORT )
		return FALSE;

	// Get the header height
	RECT rect;
	pHeader->GetClientRect( &rect );
	int height = rect.bottom;

	RECT rctooltip;
	rctooltip.top = 0;
	rctooltip.bottom = rect.bottom;

	// Now get the left and right border of the column
	rctooltip.left = 0 - GetScrollPos( SB_HORZ );
	for( int i = 0; i < nCol; i++ )
		rctooltip.left += GetColumnWidth( i );
	rctooltip.right = rctooltip.left + GetColumnWidth( nCol );

	if( sTip == NULL )
	{
		// Get column heading
		LV_COLUMN lvcolumn;
		lvcolumn.mask = LVCF_TEXT;
		lvcolumn.pszText = buf;
		lvcolumn.cchTextMax = TOOLTIP_LENGTH;
		if( !GetColumn( nCol, &lvcolumn ) )
			return FALSE;
	}


	m_tooltip.AddTool( GetDlgItem(0), sTip ? sTip : buf, &rctooltip, nCol+1 );
	return TRUE;
}

Step 5: Update tooltip control whenever a column is resized

Override OnNotify() to track changes to the column widths. If we do not update the tooltip information after the user has finished risizing a column, the tooltip will no longer reflect the proper column.

BOOL CMyListCtrl::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
	HD_NOTIFY	*pHDN = (HD_NOTIFY*)lParam;

	if((pHDN->hdr.code == HDN_ENDTRACKA || pHDN->hdr.code == HDN_ENDTRACKW))
	{
		// Update the tooltip info
		CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0);
		int nColumnCount = pHeader->GetItemCount();

		CToolInfo toolinfo;
		toolinfo.cbSize = sizeof( toolinfo );

		// Cycle through the tooltipinfo for each effected column
		for( int i = pHDN->iItem; i <= nColumnCount; i++ )
		{
			m_tooltip.GetToolInfo( toolinfo, pHeader, i + 1 );

			int dx;				// store change in width
			if( i == pHDN->iItem )
				dx = pHDN->pitem->cxy -
					(toolinfo.rect.right - toolinfo.rect.left);
			else
				toolinfo.rect.left += dx;
			toolinfo.rect.right += dx;
			m_tooltip.SetToolInfo( &toolinfo );
		}
	}

	return CListCtrl::OnNotify(wParam, lParam, pResult);
}

Enhancement Step: Consolidate tooltip update code

This step was suggested by Roger Onslow and will help
the code look more readable and less error prone. You will notice that the tooltip rectangle is
calculated in two places – the AddHeaderToolTip() and OnNotify() functions.

Here’s what Roger had to say.

The method you have on your site duplicates some code between the
recalc routine and the creation routine.

I have actually split the common code into my RecalcHeaderTip method

I have a DefineColumn method which wraps up the column definition code.
This method creates the tooltip for the column after defining it as
follows…

     CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0);
     if (pHeader) {
          // Get the header height
          RECT rect; pHeader->GetClientRect(&rect);
          RECT rctooltip; rctooltip.top = 0; rctooltip.bottom = rect.bottom;
          rctooltip.left = 0 - GetScrollPos(SB_HORZ);
          for (int i = 0; i < col; i++ ) rctooltip.left += GetColumnWidth(i);
          rctooltip.right = rctooltip.left + GetColumnWidth(col);
          m_tooltip.AddTool(pHeader,tip?tip:text,&rctooltip,col+1);
     }

Then I call RecalcHeaderTip in my AutoSizeColumns and my OnNotify methods

Call this routine (below) to automatically size columns to fit the contents.
(See also Autosize a column to fit its content)
You should call this in you Endlabeledit handler or whenever the contents of
the columns changes (eg after adding or deleting a row).

void CMyListCtrl::AutoSizeColumns() {
     // Call this after your list control is filled
     SetRedraw(false);   // turn off drawing while we update
     CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0);
     int numcol = pHeader->GetItemCount();
     for (int col = 0; col < numcol; col++) {
          SetColumnWidth(col,LVSCW_AUTOSIZE);
     }
     RecalcHeaderTips(); // update your header tips here if you have them
     SetRedraw(true);    // allow drawing
     Invalidate();       // and do the repaint
}

Note the call to RecalcHeaderTips. This updates header tool tips because
the column positions have changed.

void CMyListCtrl::RecalcHeaderTips() {
     // Update the tooltip info
     CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0);
     RECT rect; pHeader->GetClientRect(&rect);
     RECT rctooltip; rctooltip.top = 0; rctooltip.bottom = rect.bottom;
     rctooltip.left = 0 - GetScrollPos(SB_HORZ);
     CToolInfo toolinfo; toolinfo.cbSize = sizeof(toolinfo);
     // Cycle through the tooltipinfo for each column
     int numcol = pHeader->GetItemCount();
     for (int col = 0; col <= numcol; col++ ) {
          m_tooltip.GetToolInfo(toolinfo,pHeader,col+1);
          rctooltip.right = rctooltip.left + GetColumnWidth(col);
          toolinfo.rect = rctooltip;
          m_tooltip.SetToolInfo (&toolinfo);
          rctooltip.left += GetColumnWidth(col);
     }
}

NOTE: you should also call this when you get a HDN_ENDTRACKA|W notify for
manual column adjustment. For example

BOOL CMyListCtrl::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
     HD_NOTIFY *pHDN = reinterpret_cast(lParam);
     switch (pHDN->hdr.code) {
     // ...
     // other message handling here
     // ...
     case HDN_ENDTRACKA:
     case HDN_ENDTRACKW:
          {
               bool ok = CListCtrl::OnNotify(wParam, lParam, pResult);
               RecalcHeaderTips();
               return ok;
          }
          break;
     }
     return CListCtrl::OnNotify(wParam, lParam, pResult);
}

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read