![]() |
Desktop Development »
List Controls »
List Controls
Intermediate
License: The Code Project Open License (CPOL)
CListCtrl and Displaying a TooltipBy SnakefootExamples of how to implement tooltips in the MFC list control |
C++, Windows, MFC, Dev
|
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||
Microsoft's CListCtrl has support for displaying data in a grid, but requires a little help to display tooltips. This article will demonstrate how we can display tooltips when using a CListCtrl. The demo application allows you to experience the behavior of the different tooltip implementations.
There are lots of advanced grid controls that extend the CListCtrl, so it is possible to display a tooltip when holding the mouse over a cell. But, because these grid controls can be very complex, it can be difficult to see how they do it.
For normal MFC controls, there are usually two methods for enabling tooltips:
CWnd::EnableToolTips() CToolTipCtrl as a member variable The CListCtrl has a few more tricks up its sleeve, which add some more options:
CToolTipCtrl member variable, which can be accessed with CListCtrl::GetToolTips() LVS_EX_LABELTIP, which activates the tooltip when the mouse hovers over a cell where the entire text is only partially displayed LVS_EX_INFOTIP, which enables tooltips for the label column This article will only focus on how to display a simple tooltip for a cell in CListCtrl. Implementing tooltips is a rather large topic, which can be seen in the MSDN articles: About ToolTip Controls and Using ToolTip Controls.
This method usually requires three steps:
CWnd::EnableToolTips(TRUE) during the control initialization, which for the CListCtrl means over-defining CWnd::PreSubclassWindow(). CWnd::OnToolHitTest(...) for the CListCtrl. It will be called every time the mouse moves over the control, and the return value of the function specifies whether a tooltip should be displayed. TTN_NEEDTEXT message handler. It will be called when the mouse is over the control and the previous method has returned that a tooltip is available. The third step is only necessary if the second step specifies that the tooltip text should be retrieved using the callback (LPSTR_TEXTCALLBACK). The reason for doing a callback is only speed consideration, in case the lookup of the tooltip text is slow. Instead of specifying a callback, we can just return the tooltip text directly by performing a malloc() to hold the text (it will be automatically deallocated) and enables tooltip text beyond 80 characters.
If using the TTN_NEEDTEXT message handler and one wants to display tooltip longer than 80 characters, then one must allocate the wanted text buffer and set the TOOLTIPTEXT::lpszText pointer to this text-buffer in the message handler (one has to deallocate this text buffer manually):
BEGIN_MESSAGE_MAP(CListCtrl_EnableToolTip, CListCtrl)
ON_NOTIFY_EX(TTN_NEEDTEXTA, 0, OnToolNeedText)
ON_NOTIFY_EX(TTN_NEEDTEXTW, 0, OnToolNeedText)
END_MESSAGE_MAP()
void CListCtrl_EnableToolTip::PreSubclassWindow()
{
CListCtrl::PreSubclassWindow();
// Disable the CToolTipCtrl of CListCtrl so it won't disturb the CWnd tooltip
GetToolTips()->Activate(FALSE);
// Activate the standard CWnd tooltip functionality
VERIFY( EnableToolTips(TRUE) );
}
INT_PTR CListCtrl_EnableToolTip::OnToolHitTest(CPoint point, TOOLINFO * pTI) const
{
CPoint pt(GetMessagePos());
ScreenToClient(&pt);
if (!ShowToolTip(pt))
return -1;
int nRow, nCol;
CellHitTest(pt, nRow, nCol);
//Get the client (area occupied by this control)
RECT rcClient;
GetClientRect( &rcClient );
//Fill in the TOOLINFO structure
pTI->hwnd = m_hWnd;
pTI->uId = (UINT) (nRow * 1000 + nCol);
// Send TTN_NEEDTEXT when tooltip should be shown
pTI->lpszText = LPSTR_TEXTCALLBACK;
pTI->rect = rcClient;
return pTI->uId;
// Must return a unique value for each cell (Marks a new tooltip)
}
BOOL CListCtrl_EnableToolTip::OnToolNeedText(UINT id, NMHDR* pNMHDR, LRESULT* pResult)
{
CPoint pt(GetMessagePos());
ScreenToClient(&pt);
int nRow, nCol;
CellHitTest(pt, nRow, nCol);
CString tooltip = GetToolTipText(nRow, nCol);
if (tooltip.IsEmpty())
return FALSE;
// Non-unicode applications can receive requests for tooltip-text in unicode
TOOLTIPTEXTA* pTTTA = (TOOLTIPTEXTA*)pNMHDR;
TOOLTIPTEXTW* pTTTW = (TOOLTIPTEXTW*)pNMHDR;
#ifndef _UNICODE
if (pNMHDR->code == TTN_NEEDTEXTA)
lstrcpyn(pTTTA->szText, static_cast<LPCTSTR>(tooltip), sizeof(pTTTA->szText));
else
_mbstowcsz(pTTTW->szText, static_cast<LPCTSTR>(tooltip),
sizeof(pTTTW->szText)/sizeof(WCHAR));
#else
if (pNMHDR->code == TTN_NEEDTEXTA)
_wcstombsz(pTTTA->szText, static_cast<LPCTSTR>(tooltip), sizeof(pTTTA->szText));
else
lstrcpyn(pTTTW->szText, static_cast<LPCTSTR>(tooltip),
sizeof(pTTTW->szText)/sizeof(WCHAR));
#endif
// If wanting to display a tooltip which is longer than 80 characters,
// one must allocate the needed text-buffer instead of using szText,
// and point the TOOLTIPTEXT::lpszText to this text-buffer.
// When doing this, one is required to release this text-buffer again
return TRUE;
}
When using CWnd::EnableToolTips(), one is sharing the same CToolTipCtrl with all other windows in the application. If we want to modify the behavior of the global CToolTipCtrl, we can acquire it using AfxGetModuleThreadState().
BOOL CListCtrl_EnableToolTip::OnToolNeedText(UINT id, NMHDR* pNMHDR, LRESULT* pResult)
{
...
// Break tooltip into multiple lines if it contains newlines (/n/r)
CToolTipCtrl* pToolTip = AfxGetModuleThreadState()->m_pToolTip;
if (pToolTip)
pToolTip->SetMaxTipWidth(SHRT_MAX);
...
}
This method usually requires four steps:
CToolTipCtrl as a member variable, and call CToolTipCtrl::Create() during the control initialization, which for the CListCtrl means over-defining CWnd::PreSubclassWindow(). CWnd::PreTranslateMessage() for the CListCtrl, and use this method to relay all events to the CToolTipCtrl. ON_WM_MOUSEMOVE). This should be used to tell the CToolTipCtrl whether it should display a tool tip. TTN_NEEDTEXT message handler. It will be called when the mouse is over the control and the previous method has returned that a tooltip is available. The last step is only required if supplying the tooltip text using LPSTR_TEXTCALLBACK. If supplying the tooltip text directly (enables tooltip text beyond 80 characters), then we must be aware that we are responsible for any needed deallocation (will not happen automatically). The TTN_NEEDTEXT message handler is identical to the one described in the above paragraph, CWnd::EnableToolTips().
The CToolTipCtrl is mainly intended to be used in CView and CDialog classes, so when adding a tooltip for a control using CToolTipCtrl::AddTool(), the TTN_NEEDTEXT message is sent to the parent. Since we want the CListCtrl to receive the tooltip callback events, we have to create a custom TTM_ADDTOOL message. This is not an issue if providing the tooltip text directly in the mouse move handler.
BEGIN_MESSAGE_MAP(CListCtrl_OwnToolTipCtrl, CListCtrl)
ON_WM_MOUSEMOVE()
ON_NOTIFY_EX(TTN_NEEDTEXTA, 0, OnToolNeedText)
ON_NOTIFY_EX(TTN_NEEDTEXTW, 0, OnToolNeedText)
END_MESSAGE_MAP()
void CListCtrl_OwnToolTipCtrl::PreSubclassWindow()
{
CListCtrl::PreSubclassWindow();
// Disable the CToolTipCtrl of CListCtrl so it won't disturb our own tooltip-ctrl
GetToolTips()->Activate(FALSE);
// Enable our own tooltip-ctrl and make it show tooltip even if not having focus
VERIFY( m_OwnToolTipCtrl.Create(this, TTS_ALWAYSTIP) );
m_OwnToolTipCtrl.Activate(TRUE);
}
BOOL CListCtrl_OwnToolTipCtrl::PreTranslateMessage(MSG* pMsg)
{
if (m_OwnToolTipCtrl.m_hWnd)
m_OwnToolTipCtrl.RelayEvent(pMsg);
return CListCtrl::PreTranslateMessage(pMsg);
}
void CListCtrl_OwnToolTipCtrl::OnMouseMove(UINT nFlags, CPoint point)
{
CPoint pt(GetMessagePos());
ScreenToClient(&pt);
// Find the subitem
LVHITTESTINFO hitinfo = {0};
hitinfo.flags = nFlags;
hitinfo.pt = pt;
SubItemHitTest(&hitinfo);
if (m_LastToolTipCol!=hitinfo.iSubItem || m_LastToolTipRow!=hitinfo.iItem)
{
// Mouse moved over a new cell
m_LastToolTipCol = hitinfo.iSubItem;
m_LastToolTipRow = hitinfo.iItem;
// Remove the old tooltip (if available)
if (m_OwnToolTipCtrl.GetToolCount()>0)
{
m_OwnToolTipCtrl.DelTool(this);
m_OwnToolTipCtrl.Activate(FALSE);
}
// Add the new tooltip (if available)
if (m_LastToolTipRow!=-1 && m_LastToolTipRow!=-1)
{
// Not using CToolTipCtrl::AddTool() because
// it redirects the messages to CListCtrl parent
TOOLINFO ti = {0};
ti.cbSize = sizeof(TOOLINFO);
ti.uFlags = TTF_IDISHWND; // Indicate that uId is handle to a control
ti.uId = (UINT_PTR)m_hWnd; // Handle to the control
ti.hwnd = m_hWnd; // Handle to window
// to receive the tooltip-messages
ti.hinst = AfxGetInstanceHandle();
ti.lpszText = LPSTR_TEXTCALLBACK;
m_OwnToolTipCtrl.SendMessage(TTM_ADDTOOL, 0, (LPARAM) (LPTOOLINFO) &ti);
m_OwnToolTipCtrl.Activate(TRUE);
}
}
CListCtrl::OnMouseMove(nFlags, point);
}
This solution only requires that we create a TTN_NEEDTEXT message handler, which is identical to the one described in the above paragraph, CWnd::EnableToolTips.
A few quirks have been discovered with this solution:
CToolTipCtrl to perform custom drawing will fail when trying to reposition the tooltip. This solution only requires that we add the extended style LVS_EX_LABELTIP. It will only display the tooltip if the cell text cannot be shown completely and the tooltip text can only be the entire cell text.
void CListCtrl_LabelTip::PreSubclassWindow()
{
CListCtrl::PreSubclassWindow();
SetExtendedStyle(LVS_EX_INFOTIP | GetExtendedStyle());
}
The Info Tip will only work for the label-column, and it is limited to 80 characters. The solution requires two steps:
LVS_EX_INFOTIP during the control initialization, which for the CListCtrl means over-defining CWnd::PreSubclassWindow(). LVN_GETINFOTIP message handler. It will be called when the mouse is over a cell in the label-column. BEGIN_MESSAGE_MAP(CListCtrl_InfoTip, CListCtrl)
ON_NOTIFY_REFLECT_EX(LVN_GETINFOTIP, OnGetInfoTip)
END_MESSAGE_MAP()
void CListCtrl_InfoTip::PreSubclassWindow()
{
CListCtrl::PreSubclassWindow();
SetExtendedStyle(LVS_EX_INFOTIP | GetExtendedStyle());
}
BOOL CListCtrl_InfoTip::OnGetInfoTip(NMHDR* pNMHDR, LRESULT* pResult)
{
// Will only request tooltip for the label-column
NMLVGETINFOTIP* pInfoTip = (NMLVGETINFOTIP*)pNMHDR;
CString tooltip = GetToolTipText(pInfoTip->iItem, pInfoTip->iSubItem);
if (!tooltip.IsEmpty())
{
_tcsncpy(pInfoTip->pszText, static_cast<LPCTSTR>(tooltip), pInfoTip->cchTextMax);
}
return FALSE; // Let parent-dialog get chance
}
The source code provides examples of how to implement the described tooltip solutions for CListCtrl.
| You must Sign In to use this message board. | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 12 Mar 2009 Editor: Deeksha Shenoy |
Copyright 2008 by Snakefoot Everything else Copyright © CodeProject, 1999-2009 Web18 | Advertise on the Code Project |