Re: ToolTips in a View and TTN_NEEDTEXT

From: Bill Thompson (billt61_at_rgv.rr.com)
Date: 11/19/04


Date: Fri, 19 Nov 2004 10:36:30 -0600


"Rick Lee" <Rick@Custom-Made-SoftwareNOSPAM.com> wrote in message
news:419DA3E0.3F98810B@Custom-Made-SoftwareNOSPAM.com...
> I have been trying to reuse code for ToolTips in a View ("Data Tips")
> that I found in the
> April 97 issue of MSJ:
> http://www.microsoft.com/msj/0497/tooltip/tooltip.aspx
>
> Most of the required functions and portions of functions --
> OnMouseMove(), OnInitialUpdate(),
> PreTranslateMessage() HitTest() -- are called correctly. But for some
> reason the TTN_NEEDTEXT
> message appears never to be sent because OnToolTipNeedText() is never
> called.
>
> Note that the only value of pMsg->message that is relayed to the ToolTip
> is 512, and I have
> no idea if that's right. But it does seem to be the case in the Sample
> Project I downloaded.
> (Click tooltips.exe at the top of the article for the self-extracting
> archive. The Data Tips
> is in the DTTest directory.)
>
> Thanks,
> Rick Lee
>

[code snipped]

This class works for me. You derive your dialog based classes off of
CToolTipDialog instead of CDialog, and 'voilez' your dialog has tooltips.

NOTES:
1) Derive CToolTipDialog from CDialog (or whatever other base Dialog class
   you wish to use) instead of CResizeDialog
2) Derive dialogs that you want tool tips from this class.
3) A context menu has been included that allows you to set the
    tooltip from your program at run time. You have to either
    create a context menu resource with the resource ID
    IDR_TOOLTIP_MENU and a menu item to set the tooltip
    with ID IDR_SET_TOOLTIP, or delete the OnContextMenu
    code
4) This code uses a 'tooltip manager'. Since it's a database application,
    I store all toolips in a database. You may have another way to
    store your tooltips. Here's a (slightly revised) tooltip manager class:
5) You might want to associate resource ID's with the Dialog/Control ID's
     instead of strings.

#ifndef TOOLTIPMANAGER_H
#define TOOLTIPMANAGER_H

class CToolTipManager
{
public:
    CToolTipManager();
    ~CToolTipManager();
    void Fini();
        // gets the tool tip associated with a given dialog/control
    void GetToolTipText(int DialogID, int ControlID, CString &ToolTip);
        // associates a tool tip with a given dialog/control
    bool SetToolTipText(int DialogID, int ControlID, CString &ToolTip);
        // invokes a dialog that let's the user add a tooltip
    void InvokeDialog(int DialogID, int ControlID);

private:
    // you will want some member, such as a CMap type class
    // to associate tooltip text with dialog/controls here
};

////////////////////////////////////////////////////////////////
//***** Following is the tooltip dialog class:
////////////////////////////////////////////////////////////////
#if
!defined(AFX_TOOLTIPDIALOG_H__4FCE09A3_FBF1_4FB1_BAE6_1B1549FC88EE__INCLUDED
_)
#define AFX_TOOLTIPDIALOG_H__4FCE09A3_FBF1_4FB1_BAE6_1B1549FC88EE__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
// ToolTipDialog.h : header file
//

////////////////////////////////////////////////////////////////////////////
/
// CToolTipDialog dialog
// NOTE: replace CResizeDialog with CDialog,
// or whatever base dialog class you are using.

class CToolTipDialog : public CResizeDialog
{
    DECLARE_DYNAMIC(CToolTipDialog)
// Construction
public:
    CToolTipDialog(UINT nIDD, LPCTSTR DialogTitle, bool UseScrollbars =
false, CWnd* pParent = NULL); // standard constructor

// Dialog Data
    //{{AFX_DATA(CToolTipDialog)
        // NOTE: the ClassWizard will add data members here
    //}}AFX_DATA

// Overrides
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CToolTipDialog)
    public:
    protected:
    virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
    //}}AFX_VIRTUAL

// Implementation
protected:
    LPCTSTR GetToolTipText(int ControlID);
    void GetToolTipControlFromPoint(CPoint point, int &ControlID, HWND
&hWndChild) const;
    void OnSetToolTip();

    UINT m_nIDD;
        // stores the point from which the context menu was invoked
    CPoint m_PopupPoint;

    // Generated message map functions
    //{{AFX_MSG(CToolTipDialog)
    virtual BOOL OnInitDialog();
    afx_msg void OnContextMenu(CWnd* pWnd, CPoint point);
    //}}AFX_MSG
    BOOL OnToolTipNotify(UINT id, NMHDR *pNMH, LRESULT *pResult);
    virtual int OnToolHitTest( CPoint point, TOOLINFO* pTI ) const;
    DECLARE_MESSAGE_MAP()
};

//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately
before the previous line.

#endif //
!defined(AFX_TOOLTIPDIALOG_H__4FCE09A3_FBF1_4FB1_BAE6_1B1549FC88EE__INCLUDED
_)

////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////

// ToolTipDialog.cpp : implementation file
//

#include "stdafx.h"
#include "NewTrader.h"
#include "ToolTipDialog.h"
#include "ToolTipManager.h"
#include "Afx_OldToolInfo.h" // for OnToolHitTest

extern CToolTipManager TheToolTipManager;

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

// NOTE: again, you might want to replace all instances of CResizeDialog
// with CDialog
IMPLEMENT_DYNAMIC(CToolTipDialog, CResizeDialog)

////////////////////////////////////////////////////////////////////////////
/
// CToolTipDialog dialog

CToolTipDialog::CToolTipDialog(UINT nIDD, LPCTSTR DialogTitle, bool
UseScrollbars, CWnd* pParent)
    : CResizeDialog(nIDD, DialogTitle, UseScrollbars, pParent)
{
    //{{AFX_DATA_INIT(CToolTipDialog)
        // NOTE: the ClassWizard will add member initialization here
    //}}AFX_DATA_INIT
    m_nIDD = nIDD;
}

void CToolTipDialog::DoDataExchange(CDataExchange* pDX)
{
    CResizeDialog::DoDataExchange(pDX);
    //{{AFX_DATA_MAP(CToolTipDialog)
        // NOTE: the ClassWizard will add DDX and DDV calls here
    //}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CToolTipDialog, CResizeDialog)
    //{{AFX_MSG_MAP(CToolTipDialog)
    ON_WM_CONTEXTMENU()
    ON_COMMAND(IDR_SET_TOOLTIP, OnSetToolTip)
    //}}AFX_MSG_MAP
    ON_NOTIFY_EX_RANGE(TTN_NEEDTEXT, 0, 0xFFFF, OnToolTipNotify)
END_MESSAGE_MAP()

////////////////////////////////////////////////////////////////////////////
/////
// get the tool tip associated with a given control id
LPCTSTR CToolTipDialog::GetToolTipText(int ControlID)
{
    LPCTSTR RetVal = "";
    CWnd* pWndChild = GetWindow(GW_CHILD);
    int counter = 0;
    while (pWndChild != NULL)
    {
        int TestControlID = pWndChild->GetDlgCtrlID();
        if (ControlID == TestControlID)
        {
            static TCHAR MyBuf[256];
            CString ToolTipText;
            TheToolTipManager.GetToolTipText(m_nIDD, ControlID,
ToolTipText);
            _tcscpy(MyBuf, ToolTipText);
            RetVal = MyBuf;
        }
        pWndChild = pWndChild->GetWindow(GW_HWNDNEXT);
    }
    return RetVal;
}

////////////////////////////////////////////////////////////////////////////
//
// override the default OnToolHitTest
// the code was copied from the MFC Source code.
// the reason?
// so tool tips follow the cursor rather than the bottom of the control
int CToolTipDialog::OnToolHitTest(CPoint point, TOOLINFO* pTI) const
{
    int nHit = -1;
    if (pTI != NULL && pTI->cbSize >= sizeof(AFX_OLDTOOLINFO))
    {
            // find child window which hits the point
            // (don't use WindowFromPoint, because it ignores disabled
windows)
        HWND hWndChild = ::ChildWindowFromPoint(m_hWnd, point);
        if (hWndChild != NULL)
        {
                // back to the original code
                // return positive hit if control ID isn't -1
            nHit = ::GetDlgCtrlID(hWndChild);
            if (nHit != 0)
            {
                // setup the TOOLINFO structure
                pTI->hwnd = m_hWnd;
                pTI->uId = (UINT)hWndChild;
                pTI->uFlags |= TTF_IDISHWND;
                pTI->lpszText = LPSTR_TEXTCALLBACK;
            }
        }
    }
    return nHit;
}

////////////////////////////////////////////////////////////////////////////
/////
// handle tool tip notification messages
BOOL CToolTipDialog::OnToolTipNotify(UINT id, NMHDR *pNMH, LRESULT *pResult)
{
    BOOL RetVal = FALSE;
    if (pNMH)
    {
        TOOLTIPTEXT *pText = (TOOLTIPTEXT *) pNMH;
        int ControlID = ::GetDlgCtrlID((HWND) pNMH->idFrom);
        if (ControlID)
        {
            pText->lpszText = (LPTSTR) GetToolTipText(ControlID);
            pText->hinst = AfxGetInstanceHandle();
            RetVal = TRUE;
        }
    }
    return RetVal;
}

////////////////////////////////////////////////////////////////////////////
/////
////////////////////////////////////////////////////////////////////////////
/////
void CToolTipDialog::GetToolTipControlFromPoint(CPoint point, int
&ControlID, HWND &hWndChild) const
{
    ControlID = -1;
        // find child window which hits the point
        // (don't use WindowFromPoint, because it ignores disabled windows)
    hWndChild = ::ChildWindowFromPoint(m_hWnd, point);
    if (hWndChild != NULL)
    {
        ControlID = ::GetDlgCtrlID(hWndChild);
    }
}

////////////////////////////////////////////////////////////////////////////
/
// CToolTipDialog message handlers

////////////////////////////////////////////////////////////////////////////
/
////////////////////////////////////////////////////////////////////////////
/
BOOL CToolTipDialog::OnInitDialog()
{
    EnableToolTips(TRUE);

    CResizeDialog::OnInitDialog();
    return TRUE;
}

////////////////////////////////////////////////////////////////////////////
/
////////////////////////////////////////////////////////////////////////////
/
void CToolTipDialog::OnContextMenu(CWnd* /*pWnd*/, CPoint point)
{
    m_PopupPoint = point;
    ScreenToClient(&m_PopupPoint);
    // make sure window is active
    GetParentFrame()->ActivateFrame();

    CMenu menu;
    if (menu.LoadMenu(IDR_TOOLTIP_MENU))
    {
        CMenu* pPopup = menu.GetSubMenu(0);
        ASSERT(pPopup != NULL);
            // check for admin mode later
        bool ToolTipsEnabled = true;
        pPopup->EnableMenuItem(IDR_SET_TOOLTIP, ToolTipsEnabled ? MF_ENABLED
: MF_GRAYED);
        pPopup->TrackPopupMenu(TPM_RIGHTBUTTON | TPM_LEFTALIGN,
                               point.x, point.y, this); // route commands
through this window
    }
}

////////////////////////////////////////////////////////////////////////////
/
////////////////////////////////////////////////////////////////////////////
/
void CToolTipDialog::OnSetToolTip()
{
    int ControlID;
    HWND hWndChild;
    GetToolTipControlFromPoint(m_PopupPoint, ControlID, hWndChild);
    if (ControlID != -1)
    {
        TheToolTipManager.InvokeDialog(m_nIDD, ControlID);
    }
}