For end user, The list control that has long and multiple columns can be real hassle… Scrolling up and down, scrolling left and right, and resizing truncated column string. For this reason, almost all of the list control based control has ToolTip and/or TitleTip. Even ToolTip or TitleTip list controls, however, sometimes can’t be enough to make a really good user-friendly list control.
This article show you a sort of data tip list control plus sophisticated(?) header tool tip.
Implementing data tip in list control is quite easy. All the functions you concern about is following two functions.
OnToolHitTest() is for detecting mouse movement and retriving list control item on which mouse is on, and OnToolTipText() is for supplying actual tip text to display.
Like OnMouseMove(), OnToolHitTest() is called every time mouse moves, and at this time, you can find current row and fill up TOOLINFO structure with some infomations
int CDataTipListCtrl::OnToolHitTest(CPoint point, TOOLINFO * pTI) const
{
CRect rect;
GetClientRect(&rect);
if(rect.PtInRect(point))
{
if(GetItemCount())
{
int nTopIndex = GetTopIndex();
int nBottomIndex = nTopIndex + GetCountPerPage();
if(nBottomIndex > GetItemCount())
nBottomIndex = GetItemCount();
for(int nIndex = nTopIndex;
nIndex < = nBottomIndex; nIndex++)
{
GetItemRect(nIndex, rect, LVIR_BOUNDS);
if(rect.PtInRect(point))
{
pTI->hwnd = m_hWnd;
pTI->uId = (UINT)(nIndex+1);
pTI->lpszText = LPSTR_TEXTCALLBACK;
pTI->rect = rect;
return pTI->uId;
}
}
}
}return -1;
}
After some delay, OnToolTipText() function is called. in this function, you supply actual tip text to display.
BOOL CDataTipListCtrl::OnToolTipText(UINT id,
NMHDR* pNMHDR,
LRESULT* pResult)
{
// I want to implement this in PreSubclassWindow(),
// but it crashes.
if(!m_bToolTipCtrlCustomizeDone)
{
AFX_THREAD_STATE* pThreadState = AfxGetThreadState();
CToolTipCtrl *pToolTip = pThreadState->m_pToolTip;
// Set max tip width in pixel.
// you can change delay time, tip text or background
// color also. enjoy yourself!
pToolTip->SetMaxTipWidth(500);
m_bToolTipCtrlCustomizeDone = TRUE;
}
// need to handle both ANSI and UNICODE versions of
// the message
TOOLTIPTEXTA* pTTTA = (TOOLTIPTEXTA*)pNMHDR;
TOOLTIPTEXTW* pTTTW = (TOOLTIPTEXTW*)pNMHDR;
UINT nID = pNMHDR->idFrom;if(nID == 0) // Notification in NT from automatically
return FALSE; // created tooltipint nItem = nID – 1;
CString strTip;
TCHAR buf[MAX_TIP_LENGTH+1];
HDITEM hdCol;
hdCol.mask = HDI_TEXT;
hdCol.pszText = buf;
hdCol.cchTextMax = MAX_TIP_LENGTH;
int nNumCol = m_Header.GetItemCount();
for(int col=0; col < nNumCol; col++)
{
m_Header.GetItem(col, &hdCol);
strTip += hdCol.pszText;
strTip += _T(“: “);
strTip += GetItemText(nItem, col);
if(col < nNumCol-1) strTip += _T(‘\n’);
}#ifndef _UNICODE
if(pNMHDR->code == TTN_NEEDTEXTA)
{
if(m_pchTip != NULL)
delete m_pchTip;m_pchTip = new TCHAR[strTip.GetLength()+1];
lstrcpyn(m_pchTip, strTip, strTip.GetLength());
m_pchTip[strTip.GetLength()] = 0;
pTTTW->lpszText = (WCHAR*)m_pchTip;
}
else
{
if(m_pwchTip != NULL)
delete m_pwchTip;m_pwchTip = new WCHAR[strTip.GetLength()+1];
_mbstowcsz(m_pwchTip, strTip, strTip.GetLength());
m_pwchTip[strTip.GetLength()] = 0; // end of text
pTTTW->lpszText = (WCHAR*)m_pwchTip;
}
#else
if(pNMHDR->code == TTN_NEEDTEXTA)
{
if(m_pchTip != NULL)
delete m_pchTip;m_pchTip = new TCHAR[strTip.GetLength()+1];
_wcstombsz(m_pchTip, strTip, strTip.GetLength());
m_pchTip[strTip.GetLength()] = 0; // end of text
pTTTA->lpszText = (LPTSTR)m_pchTip;
}
else
{
if(m_pwchTip != NULL)
delete m_pwchTip;m_pwchTip = new WCHAR[strTip.GetLength()+1];
lstrcpyn(m_pwchTip, strTip, strTip.GetLength());
m_pwchTip[strTip.GetLength()] = 0;
pTTTA->lpszText = (LPTSTR) m_pwchTip;
}
#endif*pResult = 0;
return TRUE; // message was handled
}
As you already noticed, to increase tip text’s length, I introduced m_pchTip, m_pwchTip(for unicode) data member. Without this, you can’t display a tip more than 80 characters. And allocated memory for tip text is always deleted next call, but the last call is not, so you must delete it in the destructor. Like this,
CDataTipListCtrl::~CDataTipListCtrl()
{
if(m_pchTip != NULL)
delete m_pchTip;if(m_pwchTip != NULL)
delete m_pwchTip;
}
And for safety reason, this code should go to constructor too.
CDataTipListCtrl::CDataTipListCtrl()
{
m_pchTip = NULL;
m_pwchTip = NULL;
m_bToolTipCtrlCustomizeDone = FALSE;
}
pToolTip->SetMaxTipWidth(500); is quite essential to this article. Without this line, tool tip never display "multi line" tool tips. m_bToolTipCtrlCustomizeDone data member is for "Only once" purpose.
Don’t forget to mapping messages for above two function by inserting the following two lines in you messge map macro.
ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF, OnToolTipText)
ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF, OnToolTipText)
And last, in PreSubclassWindow(), Enable our tool tip.
void CDataTipListCtrl::PreSubclassWindow()
{
// TODO: Add your specialized code here and/or call
// the base class
SetExtendedStyle(LVS_EX_FULLROWSELECT); // Enables full row
// selection
EnableToolTips(); // Enables list contrl ToolTip control
m_Header.SubclassDlgItem(0, this); // Replaces new header
// control with default
// list contrl header
// control.CListCtrl::PreSubclassWindow();
}