5,661,954 members and growing! (13,654 online)
Email Password   helpLost your password?
Desktop Development » List Controls » List Controls     Intermediate License: The Code Project Open License (CPOL)

CListCtrl and displaying a tooltip

By Snakefoot

Examples of how to implement tooltips in the MFC list control.
C++, Windows, MFC, Dev

Posted: 27 Aug 2008
Updated: 27 Aug 2008
Views: 5,137
Bookmarked: 15 times
Announcements
Loading...



Search    
Advanced Search
Sitemap
7 votes for this Article.
Popularity: 3.71 Rating: 4.39 out of 5
1 vote, 14.3%
1
0 votes, 0.0%
2
0 votes, 0.0%
3
2 votes, 28.6%
4
4 votes, 57.1%
5

Introduction

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.

screenshot.png

Background

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.

How to implement a tooltip in CListCtrl

For normal MFC controls, there are usually two methods for enabling tooltips:

  • Calling CWnd::EnableToolTips().
  • Adding CToolTipCtrl as a member variable.

The CListCtrl has a few more tricks up its sleeve, which add some more options:

  • Has its own CToolTipCtrl member variable, which can be accessed with CListCtrl::GetToolTips().
  • Recognizes the extended style LVS_EX_LABELTIP, which activates the tooltip when the mouse hovers over a cell where the entire text is only partially displayed.
  • Recognizes the extended style 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.

CWnd::EnableToolTips()

This method usually requires three steps:

  1. Call CWnd::EnableToolTips(TRUE) during the control initialization, which for the CListCtrl means over-defining CWnd::PreSubclassWindow().
  2. Over-define 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.
  3. Implement a 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).

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();
    GetToolTips()->Activate(FALSE);

    // Activates 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, tooltip.GetString(), sizeof(pTTTA->szText));
    else
        _mbstowcsz(pTTTW->szText, tooltip.GetString(), sizeof(pTTTW->szText));
#else
    if (pNMHDR->code == TTN_NEEDTEXTA)
        _wcstombsz(pTTTA->szText, tooltip.GetString(), sizeof(pTTTA->szText));
    else
        lstrcpyn(pTTTW->szText, tooltip.GetString(), sizeof(pTTTW->szText));
#endif
    return TRUE;
}

The CToolTipCtrl member variable

This method usually requires four steps:

  1. Add the CToolTipCtrl as a member variable, and call CToolTipCtrl::Create() during the control initialization, which for the CListCtrl means over-defining CWnd::PreSubclassWindow().
  2. Over-define CWnd::PreTranslateMessage() for the CListCtrl, and use this method to relay all events to the CToolTipCtrl.
  3. Add a message handler for the mouse move (ON_WM_MOUSEMOVE). This should be used to tell the CToolTipCtrl whether it should display a tool tip.
  4. Implement a 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, 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();
    GetToolTips()->Activate(FALSE);
    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);
}

CListCtrl::GetToolTips()

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:

  • Subclassing the CToolTipCtrl to perform custom drawing will fail when trying to reposition the tooltip.
  • Tooltips will only show shortly, when running on Windows Vista without Vista-style.
  • Tooltips sometimes get stuck, when running on Windows Vista. When moving the mouse to different cells, the tooltip will stay in the same position, but the tooltip text will be updated correctly.

Extended style label tip

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

Extended style Info Tip

The Info Tip will only work for the label-column, and it is limited to 80 characters. The solution requires two steps:

  1. Enable the extended style LVS_EX_INFOTIP during the control initialization, which for the CListCtrl means over-defining CWnd::PreSubclassWindow().
  2. Implement a 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, tooltip.GetString(), pInfoTip->cchTextMax);
    }
    return FALSE;    // Let parent-dialog get chance
}

Using the code

The source code provides examples of how to implement the described tooltip solutions for CListCtrl.

History

  • 2008-08-28 - First release of the article.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Snakefoot



Location: Denmark Denmark

Other popular List Controls articles:

Article Top
Sign Up to vote for this article
You must Sign In to use this message board.
FAQ FAQ Noise ToleranceSearch Search Messages 
 Layout  Per page   
  (Refresh) 
-- There are no messages in this forum --

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

PermaLink | Privacy | Terms of Use
Last Updated: 27 Aug 2008
Editor: Smitha Vijayan
Copyright 2008 by Snakefoot
Everything else Copyright © CodeProject, 1999-2008
Web13 | Advertise on the Code Project