Solved

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

Posted on 1998-04-23
12
451 Views
Last Modified: 2013-11-20
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.
0
Comment
Question by:sdillman
  • 9
  • 3
12 Comments
 
LVL 10

Expert Comment

by:RONSLOW
Comment Utility
Yes
0
 
LVL 10

Expert Comment

by:RONSLOW
Comment Utility
I'll post some code for you ASAP .. .takes a little while to cut, trim and paste

0
 
LVL 10

Expert Comment

by:RONSLOW
Comment Utility
First step .. in your view class...

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

0
 
LVL 10

Expert Comment

by:RONSLOW
Comment Utility
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);

0
 
LVL 10

Expert Comment

by:RONSLOW
Comment Utility
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).

0
 

Author Comment

by:sdillman
Comment Utility
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.
0
How to improve team productivity

Quip adds documents, spreadsheets, and tasklists to your Slack experience
- Elevate ideas to Quip docs
- Share Quip docs in Slack
- Get notified of changes to your docs
- Available on iOS/Android/Desktop/Web
- Online/Offline

 

Author Comment

by:sdillman
Comment Utility
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.
0
 
LVL 10

Expert Comment

by:RONSLOW
Comment Utility
I TOLD you how to make it work .. why did you not follow what I suggested?
0
 
LVL 10

Expert Comment

by:RONSLOW
Comment Utility
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.

0
 

Author Comment

by:sdillman
Comment Utility
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.
0
 
LVL 10

Accepted Solution

by:
RONSLOW earned 150 total points
Comment Utility
My humble apologies .. just found the code I forgot to post

In the OnInitialUpdate for the view:

  if (! m_ToolTip.GetSafeHwnd() && ! ::IsWindow(m_ToolTip.GetSafeHwnd())) {
    if (m_ToolTip.Create(this, TTS_ALWAYSTIP) && m_ToolTip.AddTool(this)) {
      m_ToolTip.SendMessage(TTM_SETMAXTIPWIDTH, 0, /*SHRT_MAX*/160);
      m_ToolTip.SendMessage(TTM_SETDELAYTIME, TTDT_AUTOPOP, /*SHRT_MAX*/5000);
      m_ToolTip.SendMessage(TTM_SETDELAYTIME, TTDT_INITIAL, 500);
      m_ToolTip.SendMessage(TTM_SETDELAYTIME, TTDT_RESHOW, 500);
    } else {
      TRACE("Error in creating ToolTip");
    }
  }

Hope that clears things up

NOTE that this tool tip apepars after you hover for a short time and then automatically disappears.  It also limits its width so that it will go multi-line if a longer tip is required.

0
 
LVL 10

Expert Comment

by:RONSLOW
Comment Utility
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.

0

Featured Post

What Should I Do With This Threat Intelligence?

Are you wondering if you actually need threat intelligence? The answer is yes. We explain the basics for creating useful threat intelligence.

Join & Write a Comment

Suggested Solutions

Title # Comments Views Activity
Storage is shown as RAW 12 78
Sed question 2 45
SBS serve  2008 offsite backup 2 54
How do I extend a volume on a hard drive? 10 74
We planned to use NetApp's SnapManager for Exchange (SME) to make three snapshots a day for quick recovery. Additional we planned to use Symantec NetBackup7 to backup to tape media for disaster recovery. We followed the best practice guide from NetA…
Workplace bullying has increased with the use of email and social media. Retain evidence of this with email archiving to protect your employees.
This tutorial will walk an individual through the process of configuring basic necessities in order to use the 2010 version of Data Protection Manager. These include storage, agents, and protection jobs. Launch Data Protection Manager from the deskt…
This tutorial will walk an individual through the process of installing of Data Protection Manager on a server running Windows Server 2012 R2, including the prerequisites. Microsoft .Net 3.5 is required. To install this feature, go to Server Manager…

772 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

13 Experts available now in Live!

Get 1:1 Help Now