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);
}



Comments

  • CToolTipCtrl::GetToolInfo() does not work under WinXP!

    Posted by Legacy on 01/04/2003 12:00am

    Originally posted by: Zhao Wei

    The function CToolTipCtrl::GetToolInfo() does not seem to work with Common Controls Version 6 under WinXP. 
    
    With Common Controls V5, the CToolInfo parameter in GetToolInfo() does contain information about
    the specified tool's text and rect, but with Common Controls V6, it doesn't.
    We can fix this bug by rewriting the function RecalcHeaderTips with a call to CToolTipCtrl::SetToolRect().

    void CHeaderToolTipListCtrl::RecalcHeaderTips()
    {
    CHeaderCtrl* pHeader = GetHeaderCtrl();
    RECT rctooltip;
    // Update all tools' rect
    int numcol = pHeader->GetItemCount();
    for (int col = 0; col < numcol; col++ ) {
    pHeader->GetItemRect(col, &rctooltip);
    m_tooltip.SetToolRect(pHeader, col+1, &rctooltip);
    }
    }

    If your CListCtrl supports LVS_EX_HEADERDRAGDROP style, we can use Ilya Kheifets' method:
    post a LVM_SETCOLUMNWIDTH message to the control so that the HDN_ITEMCHANGEDA/HDN_ITEMCHANGEDW notification
    will be received after the columns order has changed.

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

    switch(pHDN->hdr.code){
    case HDN_ITEMCHANGEDA:
    case HDN_ITEMCHANGEDW:
    RecalcHeaderTips();
    break;
    case HDN_ENDDRAG:
    {
    LPNMHEADER pNMHeader = (LPNMHEADER)pHDN;
    int nWidth = GetColumnWidth(pNMHeader->iItem);
    PostMessage(LVM_SETCOLUMNWIDTH, pNMHeader->iItem, nWidth);
    }
    break;
    }

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


    Reply
  • www.tooltips.net

    Posted by Legacy on 12/19/2002 12:00am

    Originally posted by: Vitaly

    Just for further exploration of the subject, more tooltips can be found here: www.tooltips.net


    Reply
  • It doesn't work with tool tips longer than 80 characters

    Posted by Legacy on 06/24/2002 12:00am

    Originally posted by: Norbert Eggert

    Thanks for the source code, it was exactly what I need for my CListCtrl.
    
    I found in my Implementation a problem with tool text longer than 80 characters. It looks like a problem with compatibility with older versions of mfc. So I solve the problem with new phrasing of RecalcHeaderTips().


    void CToolTipListCtrl::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);
    int numcol = pHeader->GetItemCount();
    for (int col = 0; col <= numcol; col++)
    {
    rctooltip.right = rctooltip.left + GetColumnWidth(col);
    m_tooltip.SetToolRect(pHeader, col+1, &rctooltip);
    rctooltip.left += GetColumnWidth(col);
    }
    }

    Reply
  • Updated version: WTL, HEADERDRAGDROP

    Posted by Legacy on 05/18/2002 12:00am

    Originally posted by: Ilya Kheifets

    My version of ToolTipped header has following differences:
    1. It's based on WTL.
    2. It support LVS_EX_HEADERDRAGDROP style (drag-and-drop reordering of columns in a list-view control).
    3. It uses m_Header.GetItemRect (instead of GetScrollPos and GetColumnWidth).

    The only interesting moment is that we recieve HDN_ENDDRAG notification BEFORE postions of colomns have changed. So we post LVM_SETCOLUMNWIDTH in order to recieve HDN_ITEMCHANGED notification AFTER postions of colomns have changed.

    CToolTipCtrl m_HdrToolTip;
    CHeaderCtrl m_Header;

    BEGIN_MSG_MAP(CMyDlg)
    NOTIFY_CODE_HANDLER(HDN_ITEMCHANGED, OnHeaderChanged)
    NOTIFY_CODE_HANDLER(HDN_ENDDRAG, OnHeaderDraged)
    END_MSG_MAP()

    static LPCTSTR s_carszToolTips[];
    LPCTSTR* GetHdrToolTips()
    {
    return s_carszToolTips;
    }
    LPCTSTR s_carszToolTips[] = { "0", "1", "2" };

    void Init()
    {
    m_Header = GetList()->GetHeader();
    //add tooltip
    VERIFY( m_HdrToolTip.Create( m_Header, 0, 0, WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP, WS_EX_TOPMOST ) );

    for( int i=0; i<m_Header.GetItemCount(); ++i )
    {
    char szbuf[128]="";
    TOOLINFO ti={sizeof(ti)};
    IniToolInfo( i, ti, szbuf );
    VERIFY( m_HdrToolTip.AddTool( &ti ) );
    }
    }

    void IniToolInfo(int i, TOOLINFO& ti, LPTSTR pText)
    {
    VERIFY( m_Header.GetItemRect( i, &ti.rect ) );
    ti.uFlags = TTF_SUBCLASS;
    ti.hwnd = m_Header.m_hWnd;
    ti.uId = i;
    lstrcpy( pText, GetHdrToolTips()[i] );
    ti.lpszText = pText;
    }

    LRESULT OnHeaderChanged(int idCtrl, LPNMHDR pnmh, BOOL& bHandled)
    {
    if( !m_Header.IsWindow() || !m_HdrToolTip.IsWindow() )
    return 0;
    // change tool tip
    int iCnt = m_Header.GetItemCount();
    if(!iCnt)
    return 0;
    ASSERT( iCnt == m_HdrToolTip.GetToolCount() );
    for( int i=0; i<iCnt; i++ )
    {
    char szbuf[128]="";
    TOOLINFO ti={sizeof(ti)};
    IniToolInfo( i, ti, szbuf );
    m_HdrToolTip.SetToolInfo( &ti );
    }
    return 0;
    }

    LRESULT OnHeaderDraged(int idCtrl, LPNMHDR pnmh, BOOL& bHandled)
    {// force OnHeaderChanged to be called atfer new position of colomns are defined
    if( IsWindow( GetList()->m_hWnd ) )
    {
    int iw = GetList()->GetColumnWidth(0);
    GetList()->PostMessage(LVM_SETCOLUMNWIDTH, 0, MAKELPARAM(iw, 0));
    }
    return 0;
    }

    Reply
  • Bug in OnNotify + fix

    Posted by Legacy on 07/23/1999 12:00am

    Originally posted by: Sander Rorije

    Roger Onslow's last note is that column adjustments should be catched in order to call RecalcHeaderTips. This can be done by adding the cases HDN_ENDTRACKW and HDN_ENDTRACKA to the OnNotify method (and calling RecalcHeaderTips in those cases). I noticed that RecalcHeaderTips gets called indeed, but the tooltip "hotspot" rectangles remain unaffected. In other words, the call of RecalcHeaderTips has no effect.

    This is caused by the fact that during the call to RecalcHeaderTips (where GetColumnWidth is used to determine the new column widths), the columns widths aren't updated yet. To solve this, the HDN_ENDTRACKW and HDN_ENDTRACKA constants in OnNotify should be changed to HDN_ITEMCHANGEDW and HDN_ITEMCHANGEDA. Now RecalcHeaderTips gets called AFTER the column widths are updated, and everything works fine.

    Furthermore I would like to thank Codeguru, you've helped me a lot the last year. Keep up the good work!

    Reply
  • Placing m_tooltip.Create in PreSubclassWindow causes ASSERT failure

    Posted by Legacy on 11/07/1998 12:00am

    Originally posted by: Chip

    This article suggests putting the call to m_tooltip.Create() in PreSubclassWindow:
    
    

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

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

    However, when I attempted to implement this, an assertion in AfxWindowProc() failed on the call to Create(). I moved the call to CMyListCtrl::OnCreate() and that cleared up the problem.

    Reply
  • You must have javascript enabled in order to post comments.

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • The open source cloud computing project OpenStack has come a long way since NASA and Rackspace launched it in 2010. Backed by leading technology infrastructure providers including Cisco, Dell, EMC, HP, IBM, Intel, and VMware, OpenStack underpins significant workloads at an increasingly diverse set of organizations, including BWM, CERN, Comcast, eBay, and Wal-Mart. For CIOs engaged in broader programs to win, serve, and retain customers -- and refocus business technology (BT) spend -- a planned and pragmatic …

  • Entire organizations suffer when their networks can't keep up and new opportunities are put on hold. Waiting on service providers isn't good business. In these examples, learn how to simplify network management so that your organization can better manage costs, adapt quickly to business demands, and seize market opportunities when they arise.

Most Popular Programming Stories

More for Developers

RSS Feeds

Thanks for your registration, follow us on our social networks to keep up-to-date