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:00amOriginally posted by: Zhao Wei
Replywww.tooltips.net
Posted by Legacy on 12/19/2002 12:00amOriginally posted by: Vitaly
Just for further exploration of the subject, more tooltips can be found here: www.tooltips.net
ReplyIt doesn't work with tool tips longer than 80 characters
Posted by Legacy on 06/24/2002 12:00amOriginally posted by: Norbert Eggert
ReplyUpdated version: WTL, HEADERDRAGDROP
Posted by Legacy on 05/18/2002 12:00amOriginally 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)
Reply{// 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;
}
Bug in OnNotify + fix
Posted by Legacy on 07/23/1999 12:00amOriginally 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!
ReplyPlacing m_tooltip.Create in PreSubclassWindow causes ASSERT failure
Posted by Legacy on 11/07/1998 12:00amOriginally posted by: Chip
Reply