Question

I am trying to set tooltips on an MFC dialog. I have the code for reading the tooltips from a string resource and am trying to modify it to not read from the resource and instead make up a tooltip.

My implementation is causing garbage to be displayed as the tooltip instead of teh string I want to. Also, it is causing a crash when run from the debugger but not when the executable is run directly (I am sure there is a buffer corruption or something similar)

Here is the code which is relevant:

BOOL CPreviewDlg::OnToolTipText(UINT, NMHDR* pNMHDR, LRESULT* pResult)
{
    ASSERT(pNMHDR->code == TTN_NEEDTEXT);

    TOOLTIPTEXT* pTTT = (TOOLTIPTEXT*)pNMHDR;

    if (!(pTTT->uFlags & TTF_IDISHWND))
        return FALSE;

    UINT_PTR hWnd = pNMHDR->idFrom;
    // idFrom is actually the HWND of the tool
    UINT nID = (UINT)(WORD)::GetDlgCtrlID((HWND)hWnd);

    CString sDlgItemText;
    (UINT)(WORD)::GetDlgItemText(this->GetSafeHwnd(), nID, sDlgItemText.GetBufferSetLength(50), 50);
    sDlgItemText.ReleaseBuffer();

    if(sDlgItemText.IsEmpty())
        sDlgItemText = _T("Unnamed");

    CString sToolTip = _T("");
    sToolTip.Format(_T("%s \n This is the %s control. Here we can put its description."), sDlgItemText, sDlgItemText);

    pTTT->lpszText = sToolTip.GetBufferSetLength(sToolTip.GetLength());/* MAKEINTRESOURCE(nID);*/
    pTTT->hinst = AfxGetInstanceHandle();
    sToolTip.ReleaseBuffer();
    *pResult = 0;

    // bring the tooltip window above other popup windows
    ::SetWindowPos(pNMHDR->hwndFrom, HWND_TOP, 0, 0, 0, 0,
        SWP_NOACTIVATE|SWP_NOSIZE|SWP_NOMOVE|SWP_NOOWNERZORDER);

    return TRUE;    // message was handled
}

I am pretty sure the error is where I am trying to set the pTTT->lpszText (instead of MAKEINTRESOURCE) I think I am not getting the correct way of setting a LPSTR from a CString

CString sDlgItemText;
(UINT)(WORD)::GetDlgItemText(this->GetSafeHwnd(), nID, sDlgItemText.GetBufferSetLength(50), 50);
sDlgItemText.ReleaseBuffer();

if(sDlgItemText.IsEmpty())
    sDlgItemText = _T("Unnamed");

CString sToolTip = _T("");
sToolTip.Format(_T("%s \n This is the %s control. Here we can put its description."), sDlgItemText, sDlgItemText);

pTTT->lpszText = sToolTip.GetBufferSetLength(sToolTip.GetLength());/* MAKEINTRESOURCE(nID);*/
Was it helpful?

Solution 2

The root problem is that you are returning the address of a local variable (sToolTip.m_pData) from your OnToolTipText handler. When control leaves the notification handler, sToolTip goes out of scope and its destructor is run, leaving behind garbage.

To solve the issue you have 2 options:

  1. Copy the tooltip text to the TOOLTIPTEXT::szText[] array.
  2. Increase the lifetime of the buffer you return so that it is available when the system needs it. The lifetime doesn't need to be any longer than the enclosing entity (the dialog in this case), so a class member of the dialog will do.

Unrelated to your question: When assigning a pointer to TOOLTIPTEXT::lpszText member you should be using a const_cast instead of calling CString::GetBuffer[SetLength](). The TOOLTIPTEXT structure is used in both directions, to set and retrieve tooltip information. Consequently, the members cannot be declared const, even if they are. It may look awkward but you would rather want to do the following:

pTTT->lpszText = const_cast<LPTSTR>(static_cast<LPCTSTR>(sToolTip));

OTHER TIPS

I think your problem is that the pointer value in pTTT->lpszText is not valid any more once you exit the function.

If your text is less than 80 characters long, or if you can afford to truncate the tooltip, you can use

lstrcpy(pTTT->szText, sToolTip);

Otherwise, you will need to make sToolTip either a global variable or a member variable of the CPreviewDlg class.

Reference: MSDN article.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top