Click here to Skip to main content
Click here to Skip to main content

RichText Tool-tip Control

By , 6 Oct 2008
 
RichToolTipCtrl Demo

Introduction

Recently, I wanted to show a tool-tip to display some information about an object in a diagram. I wanted the tool-tip to contain the name of the object, followed by any comment text about that object. That was fine, except it looked very plain. What I really wanted was to show the object's name in a bold font, and the rest in the normal font, so I came up with the control presented here.

This control is a drop-in replacement for a CToolTipCtrl control, with the added benefit of being able to specify rich-text as the tool-tip text. Information on the rich text format can be found on MSDN.

Using the Demo Application

The demo application demonstrates the rich-tip control using a simple edit control, whose text is added to its tool-tip, and also a rich-edit control, with a few formatting buttons. Text in the rich-edit control appears in the tip for the rich-edit control. Rich-text may be pasted into the rich-edit control from other applications, or alternatively, if there is a file named test.rtf in the EXE's directory, then it is streamed into the control on running the application.

Using the Control

This control should be used in exactly the same way as CToolTipCtrl. While the demo application demonstrates how to add tool-tips to various controls, the basic steps for simple usage are as follows:

Add a CRichToolTipCtrl member variable to the window class which is to have the tool tips, thus:

CRichToolTipCtrl m_tip;

Next, add a call to create the control in your OnInitDialog, if it's a dialog, or your OnInitialUpdate, if it's a view, or in OnCreate for a control or other window:

m_tip.Create(this);

You should then add the following line to your PreTranslateMessage override:

m_tip.RelayEvent(pMsg);

Once that's done, you can start adding tools to the control. So, to add a tool tip to the OK button of a dialog, you would then call, in your OnInitDialog, after creating the tip control:

m_tip.AddTool(GetDlgItem(IDOK), _T("{\\rtf This is my {\\b OK} button}"));

As with the standard CToolTipCtrl, if you don't specify any text for the tool, then the tool-tip control sends the dialog a TTN_GETDISPINFO/TTN_NEEDTEXT notification. See the code of the demo application for an example of how to handle this.

Implementation Notes

Once I had realised that I was going to write my own control, I had to come up with a way of entering text such that it could be parsed, and displayed. I had a choice:

  1. Devise my own markup, and parse it.
  2. Accept and parse RTF or HTML.
  3. Use a control which parses RTF/HTML, and can output to a given device context.

I didn't really want to do 1 or 2, and I favoured RTF over HTML, as images can be embedded into RTF, so I had a look at the WordPad sources, which uses CRichEditView, to see how it rendered its output to the printer, and found that it uses CRichEditCtrl::FormatRange(), which draws the output to a specified device context.

Having found out how to render the RTF, I added a handler for the NM_CUSTOMDRAW notifications to draw to the tool-tip's window. To prevent the default rendering appearing, I set the text colour to the back colour.

This was fine except that the tool-tip window was sized to fit the original mark-up text, which is often very big, so it needed resizing. After some searching, I found the article Calculating a Rich Edit Control Minimum Size by Thales P. Carvalho, which allowed me to calculate the correct size for the tip window. I then re-positioned the window to a sensible place based on the cursor position.

This was enough to display basic formatted text in a tip window, but I wanted to be able to support embedded images. To implement this in a rich edit control, it is necessary to give the control an OLE callback handler (a pointer to an IRichEditOleCallback COM object). This is required to allow the control to allocate memory for the storage of embedded items. I added the interface code from the MFC's CRichEditView to my control. The only method that needed implementing was GetNewStorage(), so all other methods simply return S_OK.

Documentation

The CRichToolTipCtrl class has just one public function, in addition to the default constructor and virtual destructor:

static CString MakeTextRTFSafe(LPCTSTR lpszText);

This static function takes a text string, and escapes special characters to make the text acceptable for RTF.

Acknowledgements and References

History

Version 1.2 - 06 Oct 2008

  • Updated to work correctly on Vista and XP
  • Updated project to use Visual Studio 2008
  • Added project for Visual C 6

Version 1.1 - 22 Mar 2005

  • Added static method for escaping plain text to make it RTF safe
  • Modified the positioning to work on multi-monitor systems
  • Removed dependency on RichToolTipCtrlDemo.h

Version 1.0 - 14 Mar 2005

  • First version

License

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

About the Author

Paul Vickery
Software Developer (Senior)
United Kingdom United Kingdom
Member
Originally from an electronics background, I moved into software in 1996, partly as a result of being made redundant, and partly because I was very much enjoying the small amount of coding (in-at-the-deep-end-C) that I had been doing!
 
I swiftly moved from C to C++, and learned MFC, and then went on to real-time C on Unix. After this I moved to the company for which I currently work, which specialises in Configuration Management software, and currently program mainly in C/C++, for Windows. I have been gradually moving their legacy C code over to use C++ (with STL, MFC, ATL, and WTL). I have pulled in other technologies (Java, C#, VB, COM, SOAP) where appropriate, especially when integrating with third-party products.
 
In addition to that, I have overseen the technical side of the company website (ASP, VBScript, JavaScript, HTML, CSS), and have also worked closely with colleagues working on other products (Web-based, C#, ASP.NET, SQL, etc).
 
For developing, I mainly use Visual Studio 2010, along with an in-house-designed editor based on Andrei Stcherbatchenko's syntax parsing classes, and various (mostly freeware) tools. For website design, I use Dreaweaver CS3.
 
When not developing software, I enjoy listening to and playing music, playing electric and acoustic guitars and mandolin.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralMy vote of 5memberFranc Morales2hrs 57mins ago 
Thank you for sharing. Useful stuff.
QuestionWonderful application - if only I could stop the flickeringmemberMichael B Pliam22 Jul '11 - 15:52 
I have successfully deployed your code using VS 2010 (Win 7/64-bit) to create a CRichEditView SDI interface that put's me well on the road to building an 'Intellisense' like interface. I have two questions:
 
1 - is there some way to stop the flickering (see previous posts on this problem) ?
2 - would it be possible to insert a control, say a combo box, as well as a tooltip message ?
 

According to the P. Vickery, this code gets the minimium size for a rich edit control, and is
taken from http://www.codeproject.com/richedit/richeditsize.asp. That author states:
 
"Do note that, in the TCX Message Box class, I do all this calculation and resizing
before I actually display the window. Otherwise, it will flick crazily as the
application traverses the binary search loop. Therefore, if you need to recalculate
the Rich Edit Control size when it's already visible, you must first freeze the
parent window redraw, or you should move the Rich Edit Control to outside of the
visible area, do all the calculation and, when it's done, you get it back to the
visible area."
 
Further down a posting in the above article states:
If you set the ES_AUTOHSCROLL style on the rich edit control, the rect returned
in EN_REQUESTRESIZE will contain the actual rect needed to contain all of the text
without any wrapping.
 
I tried this but can't get it to work. Has anyone else tried this?
 
Thanks for sharing this great work. I defy anyone to find another publicly available C++ source code that shows how to create tooltips in a CRichEditView. Believe me, I have spent alot of time looking. Smile | :)
GeneralBug correctionmemberOctopod21 Oct '09 - 5:08 
Hi again !
 
The tooltip in your demo can't display more than one image, only the first occurence is displayed. Whatever its location in the text, whatever image it is. Also some images didn't work when displayed in the tooltip but work fine in the rich edit. Your class _RichToolTipCtrlCookie is buggy.
 
I fixed it, I found on Painless streaming of rich text to/from CRichEditCtrl[^] a much simple example than the _RichToolTipCtrlCookie class you used. I deleted all this code and simply replaced it by :
DWORD __stdcall StreamInCallback(DWORD_COOKIE dwCookie, LPBYTE pbBuff, LONG cb, LONG *pcb)
{
	if(cb > (((CString*)dwCookie)->GetLength()))	cb = ((CString*)dwCookie)->GetLength();
 
	memcpy(pbBuff,*(CString*)dwCookie,cb); // can't think of a better way.

	*pcb = cb; 
	((CString*)dwCookie)->Delete(0,cb); // remove the text we just copied from the source (pointed to by dwCookie)
	return 0;
}
 
int CRichToolTipCtrl::CRichToolTipRichEditCtrl::StreamTextIn(LPCTSTR lpszText)
{
	CString text ( lpszText );
	EDITSTREAM es;
	es.dwCookie = (DWORD_COOKIE)&text;
	es.pfnCallback = StreamInCallback;
	return StreamIn(SF_RTF, es);
}
 
And now it works like a charm.
 
Despite this, thank you for your control, I really like it.
 
modified on Thursday, November 19, 2009 10:34 AM

GeneralBuffer overrunmemberOctopod21 Oct '09 - 5:04 
Hi !
I'm reporting a bug. In the file RichToolTipCtrlDemoDlg.cpp, function RichTextCtrlCallbackOut, the line
strncpy_s(lpszBuf, cb, (LPCSTR)pbBuff, cb);
..should be replaced by..
strncpy_s(lpszBuf, cb+1, (LPCSTR)pbBuff, cb);
 
Cheers
GeneralSome replacements for Test.rtf cause re-displaymemberl_d_allan8 May '09 - 5:11 
Nice project. Thanks for providing it.
 
I was trying out different files to replace the supplied Test.rtf, to see what would happen. They happen to have more lines than will fit in the multiline RichEdit field (which may or may not be a factor).
 
Here are links to two files I used that had problems:
www.berbible.org/misc/Rtf17_Highlight_sa_Super.rtf
www.berbible.org/misc/Ps119.rtf
 
I changed the following lines:
BOOL CRichToolTipCtrlDemoDlg::OnInitDialog()
{
....
  CFile file;
  TCHAR* pszFilename = _T("Test.rtf");
  pszFilename = _T("Rtf17_Highlight_sa_Super.rtf");
  pszFilename = _T("Ps119.rtf");
  if (file.Open(pszFilename, CFile::modeRead | CFile::shareDenyNone)) {
 
When I hover over the multiline RichEdit field that has been pre-filled with the contents of Rtf_Highlight_sa_Super.rtf, it works ok the first time (and sometimes the second time). Subsequent hovers cause the tooltip to be displayed twice. It shows the contents once briefly, flashes, and then re-displays.
 
With the long Ps119.rtf, it redisplays continuously and never "settles down".
AnswerRe: Some replacements for Test.rtf cause re-displaymemberPaul S. Vickery14 May '09 - 1:24 
I've looked into this behaviour, and found that this is a problem in any multi-line tool-tip whose window overlaps the cursor position (i.e. not just in my control). You can fix it in my control by adding some code into the OnShow method:
  rc.bottom = rc.top + (size.cy);  // existing code
 
  // ensure we don't overlap the cursor, else the tool-tip is popped and re-shown repeatedly
  CPoint pt;
  GetCursorPos(&pt);
  if (rc.PtInRect(pt))
    rc.MoveToX(pt.x == rc.left ? pt.x + 1 : pt.x - (rc.Width() + 1));
 
  MoveWindow(&rc);  // existing code

 

"The way of a fool seems right to him, but a wise man listens to advice" - Proverbs 12:15 (NIV)

GeneralRe: Some replacements for Test.rtf cause re-displaymemberl_d_allan14 May '09 - 2:05 
Thanks, but I'm still experiencing problems.
 
Both test files still "flash" once, but the Ps119.rtf is better behaved in that it doesn't flash repeatedly.
 
I'm using WinXp-Sp3, if that makes a difference.
 
Here's a link to a release-build .exe:
www.berbible.org/misc/RichToolTipCtrlDemoVC71.exe
GeneralBugfix for multi-monitor setupmemberChristian Severin3 Sep '08 - 2:17 
Mr. Vickery,
 
first off: nice work, thank you.
 
We had some issues with a multi-monitor setup when the secondary monitor was on the right of the primary one. The tooltips worked fine on the main monitor or when the secondary monitor was on the left of the main monitor, but when it was on the right, tooltips on this secondary monitor would appear at the very left of the screen.
I took the liberty of changing a few lines in CRichToolTipCtrl::OnShow from
  if ((size.cx + rc.left) > rcDesktop.Width())
    rc.left = max(rcDesktop.left, rc.left - ((rc.left + size.cx) - rcDesktop.Width()));
  // position it above the mouse if it would go off the bottom of the screen
  // but don't move it off the top of the screen
  if ((size.cy + rc.top) > rcDesktop.Height())
to
  if ( (size.cx + rc.left) >= rcDesktop.right )
    rc.left = std::max( rcDesktop.left, rcDesktop.right - size.cx );
  // position it above the mouse if it would go off the bottom of the screen
  // but don't move it off the top of the screen
  if ((size.cy + rc.top) >= rcDesktop.bottom )
    rc.top = std::max( rcDesktop.top, (ptCursor.y - 16) - size.cy);
 
Again, thank you for your work ( and, YAY! ZX Spectrum! Smile | :) )
 
Cheers,
Christian
Question?????????????????????memberariel_lerman_1235 Nov '07 - 9:20 
I am using it well, tnx.
but on win2k i can not get the tooltip text but only ???????????????
why?
any ideas?
Ariel
AnswerRe: ?????????????????????memberPaul S. Vickery6 Nov '07 - 4:36 
Is this in your own program, or the demo? Also, are you building with UNICODE defined? Is it only on Win2K that you see the problem text?

 

"The way of a fool seems right to him, but a wise man listens to advice" - Proverbs 12:15 (NIV)

QuestionUsing RichToolTipCtrl with a CComboBox doesn't workmemberbholman28 Sep '07 - 10:28 
I'm using RichToolTipCtrl for a CComboBox. It is working fine for CButtons and CEdit controls, but I must be incorrectly implementing it for the combo box. Here is the way I am doing it:
 
In InitDialog:
 
m_tip.Create();
 
m_tip.AddTool(GetDlgItem(IDC_COMBO_TRANS_X_TYPE), " ");
 
Then I override UpdateDate() and add the following:
*******************************************************************
CWnd* editXRate = this->GetDlgItem(IDC_COMBO_TRANS_X_TYPE);
CString strXrate = "";
if (editXRate)
editXRate->GetWindowText(strXrate);
m_tip.UpdateTipText(strXrate, GetDlgItem(IDC_COMBO_TRANS_X_TYPE));
*******************************************************************
This is exactly the way I coded for the CEdit contorls on the dialog, and it works fine for them, but no tooltips show up for the CComboBox.
 
Anyone have any ideas where I'm going wrong?
 
Thank you in advance for any help with this.
 
Bill Holman
 

 
Bill Holman
Sr. Systems Programmer
bholman@bholman.com

AnswerRe: Using RichToolTipCtrl with a CComboBox doesn't workmemberPaul S. Vickery5 Oct '07 - 1:03 
You are not doing anything wrong. Unfortunately this is due to the make up of combo box controls. If the combo box is set to the drop-list style, then you should find that the tool-tip works correctly, but if the combo uses the drop-down style, then the edit control will not show the tool-tip, as the edit control is a completely separate control.
 
To get tool-tips to work, you will need to get the handle of the edit control, and set the tool on that control as well as the combo itself.
 
If targetting Win98 or later, then there is an easy means of getting the edit control's handle, by using CComboBox::GetComboBoxInfo(). There are several articles on CodeProject which include methods of doing this. If you target earlier platforms and don't want to use this call, you can catch the edit control in a WM_CTLCOLOR handler. See my ComboBoxCS article[^] for where I have code which uses GetComboBoxInfo to get the list box, and falls back on the WM_CTLCOLOR method on earlier platforms.

 

"The way of a fool seems right to him, but a wise man listens to advice" - Proverbs 12:15 (NIV)

GeneralRe: Using RichToolTipCtrl with a CComboBox doesn't workmemberbholman5 Oct '07 - 4:55 
Paul,
 
Thank you very much for your timely and informative response. I will give it a try.
 
Bill
 
Bill Holman
Sr. Developer
Visionael
 
Bill Holman
Sr. Systems Programmer
bholman@bholman.com

GeneralUpdateTipText while tooltip is visible causes flickeringsussHeinz Drewer24 May '05 - 23:54 
Hi Paul,
 

there is another problem:
If I call UpdateTipText when the tooltip is visible, the tolltip flickers significant (even with the same rtf string).
I think you calculate the size several times, and this causes a number of repaintings.
Do you have any idea, how to bypass this problem?
 

cu
Heinz
GeneralRe: UpdateTipText while tooltip is visible causes flickeringmembernightstalker438436348t423 Jul '06 - 21:24 
i have the same problem. If the tooltip to be displayed is bigger than what can fit on the screen, the tooltip box will continually refresh itself (flicker). Any suugestions appreciated. Thanks.
 

GeneralRe: UpdateTipText while tooltip is visible causes flickeringmemberMike Marquet7 Sep '06 - 23:02 
I've got the same problem, did you have found a solution ?
 
In my case, I must show a tooltip for the dialog (this pointer) and for a certain position in the client area of my window, show the appropriated tooltip.
 
I do this in the WML_MLOUSEMOVE message like this :
 
BOOL CMyTestDlg::OnInitDialog()
{
...
 
m_tip.Create(this);
 
CRect rectTool;
 
GetClientRect(&rectTool);
 
m_tip.AddTool(this, "", &rectTool, 9999);
 
...
}
 
void CMyTestDlg::OnMouseMove(UINT nFlags, CPoint point)
{
CDialog::OnMouseMove(nFlags, point);
 
m_tip.UpdateText( _T("{\\rtf {\\bMy dialog box}}") , this , 9999 );
 
m_tip.Update();
}
 

P.S. : Works fine when I use CToolTipCtrl !
GeneralSeems like setting text with UpdateTipText doesn't worksussHeinz Drewer18 May '05 - 1:37 
HI Paul,
 
first, I think you did a good job. Your demo works fine.
 
I tried to replace CToolTipCtrl in my application - and it doesn't work as desired. I create the tooltip control with the folloing lines:
 
if(!m_cToolTip.Create(this))
TRACE(_T("Unable to Create ToolTip"));
else
if (!m_cToolTip.AddTool(this, "Info"))
TRACE(_T("Unable to add tip for the control window"));
else
{
m_cToolTip.Activate(TRUE);
m_cToolTip.SetMaxTipWidth(800);
m_cToolTip.SetDelayTime(TTDT_AUTOPOP, 32000);
m_cToolTip.SetDelayTime(TTDT_INITIAL, 350);
m_cToolTip.SetDelayTime(TTDT_RESHOW, 350);
}
 
Later I change the tiptext with:
m_cToolTip.UpdateTipText(sTooltip, this);
 
If I set sTooltip to a richtext string like
sTooltip.Format(_T("{{The tool's window text is \'{\\b{BOLD}}\'}}"));
I always see the unformatted text with all backslashes and the other garbage.
 
Thx in advance
Heinz
 

GeneralRe: Seems like setting text with UpdateTipText doesn't workmemberPaul S. Vickery19 May '05 - 23:31 
I've tried using UpdateTipText, and it works OK for me (I'm on XP). If it is a problem, then I would like to fix it. Perhaps you could email me a small project which demonstrates the problem?
 

"The way of a fool seems right to him, but a wise man listens to advice" - Proverbs 12:15 (NIV)
GeneralRe: Seems like setting text with UpdateTipText doesn't worksussHeinz Drewer24 May '05 - 23:21 
I found my mistake - I wanted a tooltip for my dialog window an in such cases I have to fill all 4 parameters of the AddTool function. Now it works.
 
cu
Heinz
General[Message Deleted]memberXPiS17 May '05 - 0:12 

GeneralRe: Does NOT word on windows 98memberPaul S. Vickery17 May '05 - 23:50 
Mmm. You're right. It appears that EN_REQUESTRESIZE doesn't return the correct results. I'll look into it and try and come up with a solution.

 

"The way of a fool seems right to him, but a wise man listens to advice" - Proverbs 12:15 (NIV)
GeneralRe: Does NOT word on windows 98memberPaul S. Vickery19 May '05 - 0:46 
To fix the size problem, the RichEdit control needs to be created as visible on Windows 98 for the EN_REQUESTRESIZE to work properly. To fix the tip control just change the line:
  m_edit.Create(WS_CHILD | ES_MULTILINE | ES_READONLY, CRect(0, 0, 0, 0), this, 1);
to:
  // create a child richedit control
  // (Windows 98 requires it to be visible for RequestResize to work)
  m_edit.Create(WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_READONLY, CRect(0, 0, 0, 0), this, 1);
Unfortunately the formatting seems to get a bit messy on Win98, so I'll have a look at tweaking that to look better, and post the results here.
 

"The way of a fool seems right to him, but a wise man listens to advice" - Proverbs 12:15 (NIV)
GeneralRe: Does NOT word on windows 98memberPaul S. Vickery19 May '05 - 22:45 
The reason the formatting looks wrong is because the rich-edit control is visible over the tool-tip. The correct fix to the problem is to show the control before doing the RequestResize(), and then hide it again once it's resized. So, in CRichToolTipCtrl::CalculateMinimiumRichEditSize(), insert:
  m_edit.ShowWindow(SW_SHOW);
just before the 'do' loop, and insert
  m_edit.ShowWindow(SW_HIDE);
just before the call to MoveWindow().
 
I'll try to post an update in the next few days.
 

"The way of a fool seems right to him, but a wise man listens to advice" - Proverbs 12:15 (NIV)
GeneralThis fix olso helps for NTmemberDieter Hammer11 Aug '05 - 4:55 
with NT there are the same probs. The fix helps her too.
Thanx!

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130516.1 | Last Updated 7 Oct 2008
Article Copyright 2005 by Paul Vickery
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid