Link to home
Start Free TrialLog in
Avatar of sdillman
sdillman

asked on

Showing Tool Tips for an Area of a Window, Not for a Control

I have a custom control that is implemented as a derived class from CWnd.  I would like to provide tool tips for various rectangular areas within the client area of the custom control.

I am familiar with implementing tool tips for controls within a dialog by overriding the virtual OnToolHitTest() function and by providing a OnToolTipNotify() message handler for the TTN_NEEDTEXT notification.  When providing tool tips for controls, you fill in the TOOLINFO structure in OnToolHitTest() by specifying TTF_IDISHWND in the uFlags member.  If you fill in the appropriate window handles in the hwnd and uID members, then your OnToolTipNotify() function gets called correctly.

In my case, however, I am not dealing with a tool tip on a control, but rather on an area of the window.  If you read the documentation on TOOLINFO, you can see that there is a member of the structure that you can use to specify the rectangular region for the tool.  The docs also say, however, that this member is ignored if you have TTF_IDISHWND in uFlags.  Since I am not providing a tool tip for a control, I don't want to include TTF_IDISHWND in the uFlags member.  For some reason, however, when I remove this from the flags, my OnToolTipNotify() function never gets called.

Has anyone ever implemented tool tips on a rectangular area of the client window rather than on a control?  Please tell me how to do this and still be able to provide the tool-tip text on demand in a OnToolTipNotify() function.
Avatar of RONSLOW
RONSLOW

Yes
I'll post some code for you ASAP .. .takes a little while to cut, trim and paste

First step .. in your view class...

BOOL CMyView::PreTranslateMessage(MSG* pMsg) {
  m_ToolTip.RelayEvent(pMsg);
  return QIDView::PreTranslateMessage(pMsg);
}

Next you need some members added to your view class

  CToolTipCtrl m_ToolTip;
  CMyItem* m_pToolTipItem;

where I've assumed (as in my code) that your document class has a list of items that it displays on the view, and this is a pointer to the one we wnat to get a tip on.

If this is not the case, then you need some other indicator as to what part of your window the mouse is over.

In you message map you need a line for

afx_msg BOOL OnToolTipNeedText(UINT id, NMHDR * pNMHDR, LRESULT * pResult);

Next add the following to your class

in the message map add a line for

ON_NOTIFY_EX_RANGE(TTN_NEEDTEXT,0,0xFFFF,OnToolTipNeedText)

and also add the function

BOOL CMyView::OnToolTipNeedText(UINT /*id*/, NMHDR * pNMHDR, LRESULT * pResult) {
  *pResult = 0;
  CPoint pointClient;
  ::GetCursorPos(&pointClient);      // get cursor pos in screen coords
  ScreenToClient(pointClient);      // convert to client coords
  CRect rectClient;
  GetClientRect(rectclient);            // get client rectangle
  // Make certain that the cursor is in the client rect, because
  // the mainframe also wants these messages to provide tooltips
  // for the toolbar.
  if (rectClient.PtInRect(pointClient)) {
    TOOLTIPTEXT *pTTT = (TOOLTIPTEXT *)pNMHDR;
    // find the item at the cursor position
    // using my GetHitItem function
    m_pToolTipItem = GetHitItem(pointWorld);
    // if there is an item here
    if (m_pToolTipItem) {
      // Adjust the text by filling in TOOLTIPTEXT
      CString strTip = m_pToolTipItem->WhoAmI();
      if (strTip.GetLength() >= sizeof(pTTT->szText)) {
        strTip = strTip.Left(sizeof(pTTT->szText)-4) + "...";
      }
      ::strcpy(pTTT->szText, strTip);
    } else {
      pTTT->szText[0] = '\0';
    }
    return true;
  }
  return false;
}

You'll need to write your own GetHitItem (or equivalent) that returns a pointer to (or some indication of) the area over which the mouse is sitting.

In my cas, GetHitItem asks the doc to iterate thru the items in its list and find out which one's bounding rect contains the point, then returns a poitner to the item found (or NULL if nothing found).

Avatar of sdillman

ASKER

I changed my code over to use a CToolTipCtrl object in my CWnd-derived class.  I called CToolTipCtrl::Create() once my window was created, and I called CToolTipCtrl::AddTool() to add a tool that corresponds to a rectangle within my window.  I specified LPSTR_TEXTCALLBACK so that it should send me the TTN_NEEDTEXT message.  I added the TTN_NEEDTEXT entry to the message map and I added the OnToolTipNeedText member function to my CWnd-derived class.  My OnToolTipNeedText function never gets called.  It should get called when I leave the mouse cursor over the rectangle within my window that I specified in the AddTool() call.  This is the same behavior that I saw using the other method.  My window never receives the TTN_NEEDTEXT message.

Things to Note:
I am not using a CView-derived class as you are.  My class is a custom control derived from CWnd.
My window that I am trying to get tool tips for exists as a custom control on a CDialogBar.

Any ideas are greatly appreciated.
On a hunch, I tried overriding the OnNotify() function in my CWnd-derived class.  Sure enough, I receive TTN_NEEDTEXT notification calls to this function when using both my original method with OnToolHitTest(), and when using the CToolTipCtrl method described by RONSLOW.  The problem is apparently in the message-map handling of the TTN_NEEDTEXT message.

For now, I am going to go ahead and process the message directly in OnNotify().  If anyone can figure out why the message handler is not called when putting the ON_NOTIFY_EX entry in the message map, I would still appreciate the information.  I did find some references on the web about an error in VC++ 5.0 SP2 that affected TTN_NEEDTEXT notifications.  I'm not sure if this is related.
I TOLD you how to make it work .. why did you not follow what I suggested?
you say "I changed my code over to use a CToolTipCtrl object in my CWnd-derived class.  I called CToolTipCtrl::Create() once my window was created, and I called CToolTipCtrl::AddTool() to add a tool that corresponds to a rectangle within my window.  I specified LPSTR_TEXTCALLBACK so that it should send me the TTN_NEEDTEXT message.  I added the TTN_NEEDTEXT entry to the message map and I added the OnToolTipNeedText member function to my CWnd-derived class."

That was not what I said to do.  There is not NEED for all that.

You SHOULDN'T put a ON_NOTIFY_EX entry .. you put in a ON_NOTIFY_EX_RANGE entry otherwise it won't get called (because the id's don't match)

The code I suggested to you is from working code of my own .. it should work fine.

I tried to follow EXACTLY what you suggested, but it did not seem complete (and it didn't work).  And yes, I did use ON_NOTIFY_EX_RANGE in my code.  After putting in the exact code that you suggested and running into some problems, I started trying to fix it.  For one thing, without calling CToolTipCtrl::Create(), the m_ToolTip.RelayEvent(pMsg) call in PreTranslateMessage will fail because the CWnd for the object does not exist.  I didn't see how you could have a CToolTipCtrl object and never create the window for it or add tools to it, so I tried those things.  I got your code to the point where I receive notification messages if I override OnNotify() (just as I do with my code) but the ON_NOTIFY_EX_RANGE entry in the message map would never route the messages to my message handler (just as it does not with my code).  There are several differences in what we are working with that might cause problems.  As I mentioned before, I am not working with a document/view architecture but rather with a CWnd-derived custom control on a CDialogBar.  I have come across many problems in the past with messages getting routed to controls on a CDialogBar, and this may be yet another such problem.

I will review your original code suggestions and see if I missed anything in my implementation, but I have a deadline coming up fast and I have the code working at this point (just not in the clean manner I had hoped for).  I thank you again for all of your help.  Hopefully I will have some time soon to try a simplified version of my situation and see if I can figure out what is going on.
ASKER CERTIFIED SOLUTION
Avatar of RONSLOW
RONSLOW

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Just found another bit ... this is what happens when one tries to extract relevant bits from large chunks of wroking code...

void CMyView::OnMouseMove(UINT nFlags, CPoint point) {
  // if we have a tool tip
  if (::IsWindow(m_ToolTip.m_hWnd)) {
    // is there an item under the mouse
    CMyItem* pItem = GetHitItem(point);
    // if no item or item change, kill old tooltip
    if (! pItem || pItem != m_pToolTipItem) {
      // Use Activate() to hide the tooltip.
      m_ToolTip.Activate(FALSE);        
    }
    // if there is a new item
    if (pItem) {
      m_ToolTip.Activate(TRUE);
      m_pToolTipItem = pItem;
    }
  }
  CView::OnMouseMove(nFlage,point);
}

Sorry about the confusion.