Contents

A custom tooltip is a seductive but dangerous notion, and if you don't do it right you could have the Interface Police knocking on your door. Nevertheless, if you decide you do want complete control over the contents of your tooltips and you're using MFC, here's an approach that should get you going.

You'll need to supply two virtual functions to size and draw your tips in a class derived from CustomToolTip, and a little "TipData" class to hold the data that your tips need. Adding the resulting custom tipper into a window is no more work than for a regular tooltip.

Two example custom tooltips (CustomToolTipPlain using GDI, and CustomToolTipPlus using GDI+) are included in the source to get you going. And there are separate demo projects for each.

The source code for the custom tip turns out to be relatively simple, which you might agree is nice for a change.

As a little bonus, there's a harness in place for optional "fade-in" animation. CustomToolTip members let you set the number of frames and duration for the animation, and your virtual tip drawing function receives a floating-point argument that varies from 0.0 (first frame) to 1.0 (last frame). CustomToolTipPlain uses it to vary the background and text color in the tip during the first half-second that each tip is displayed. CustomToolTipPlus puts a variable gradient fill in the background of the tip, and the gradient shows through selected colors in the image at left of the tip.

I've tried to write this up in detail, so apologies in advance for stating the obvious now and then.

Here's a class diagram with just the good bits to give you the Big Picture:

You derive your custom tip class from CustomToolTip, you derive a tip data holder from TipData (since only you know what data your tip needs), and you put three functions and a data member into your window that wants the tips to make them show.

Given your "WindowThatWantsTips", you can give it custom tooltips by:

deciding what custom data the tips will display - perhaps just a CString, perhaps something more elaborate - and deriving your tip data holder TipDataYours from TipData with appropriate data members

deriving your own CustomToolTipYours from CustomToolTip: there are only two non-trivial functions, SetTipWindowSize() which determines the size of the tip window for the current tip, and CreateTipImage() which is where you roll up your sleeves and put in the actual custom drawing code

bolting your custom tooltip into your WindowThatWantsTips, which involves adding a few lines of code to create the tipper, some AddTool() calls to set up tips for specific controls or rectangles, and a couple of lines in PreTranslateMessage() to show tips when appropriate.

Of course, you can call your derived classes anything you like. In the GDI demo, for example, they're called CustomToolTipPlain and TipDataPlain. (OK that was obvious, but at least I apologized in advance.)

The CustomToolTip base class handles all of the tooltip window behavior, including showing, hiding, positioning and sizing the tip window. It also keeps lists of the CWnds or rects that serve as triggers for the tips, together with a list of TipData pointers for the contents of the tips.

You can use entirely arbitrary data when drawing your custom tips, not just CStrings. That's the point of the TipDataYours in the design, a class where you fill in the exact data members that you want. However, CustomToolTip just tracks a list of pointers to the base class TipData, in order to allow you to have several custom tooltips, each with its own derived version of TipData. As a result, when it comes time to do the sizing and drawing you'll need to cast a generic TipData* to the exact type of data they expect. This happens in your implementations of SetWindowSize() and CreateTipImage() - in the GDI demo, for example, you'll find

TipDataPlain *theTip =
dynamic_cast<TipDataPlain*>(m_tips[tipIndex]);

With that small nuisance out of the way, it might cheer you up to know that you can derive any number of different custom tooltips for use in the same application, and have them share a common TipData holder or use different ones. You can also show tips for a window using two or more custom tooltips.

The actual data in your derived data holder for the tips (such as m_otherData in TipDataYours in the above diagram) can be owned by the custom tooltip or it can be a pointer to some object with a longer lifetime. For example, if TipDataYours owns m_otherData, then put "delete m_otherData;" in the destructor for TipDataYours. If you don't want m_otherData deleted - then don't delete it, and it will live on after your tooltips die.

CustomToolTipDemo shows a relatively plain and simple GDI-based custom tooltipper called CustomToolTipPlain. CustomToolTipPlusDemo shows a tipper called CustomToolTipPlus that uses GDI+ for drawing, and the tip for that one is slightly fancier. If you like the potential after taking a look, you might want to give one of them a try in your own project, to see if the concept will work for you: guides to adding them into your project can be found at the bottom of CustomToolTipPlain.cpp, and CustomToolTipPlus.cpp. All I can promise is that it works for me to provide tips in a highly customized dialog. If the tipper works for you, then you're in good shape to invest the time writing your own version.

Here's a copy of the instructions for adding CustomToolTipPlain to a window in one of your projects, to give you a preview. 'ParentWindow' is the class in your project that you'll be giving custom tips. The source files to add are included in both of the downloads above.

For a quick start, make a copy of one of the supplied examples (either CustomToolTipPlain, which uses GDI for drawing, or CustomToolTipPlus, which uses GDI+). Rename the .cpp and .h files, replace the class name with your own new name, and you're ready to go.

Your first bit of work will no doubt be a bit of spec and design, during which you'll come up with a list of the data items that your tip will need for drawing. This doesn't have to be elaborate if it's just a data wrapper: for example, here's the entire data holder that goes with CustomToolTipPlain:

Both of the supplied examples assume that you will draw your tip into an offscreen buffer image. You'll find the "boilerplate" code for doing that in the examples, and once the setup is done there's no difference between drawing to the offscreen buffer and drawing directly to the screen.

If for some reason you can't buffer your tooltip contents then you'll need to put your drawing code in the virtual function DrawTip() instead of CreateTipImage(), and leave CreateTipImage() as an empty function. Buffering is usually better, since it avoids flicker. From here on I'll assume you'll be buffering your drawing.

You can't draw the tip until you make it the right size, and often you can't make it the right size until you've gone through all the steps needed to draw the contents, minus the actual drawing. So the drawing takes place in two steps, SetTipWindowSize() and CreateTipImage().

void CustomToolTipPlain::SetTipWindowSize(size_t tipIndex)

The sole purpose of SetTipWindowSize() is to set the members m_tipImageHeight and m_tipImageWidth. Those members represent the full width and height of your tip window contents, and will also correspond to the size of your offscreen buffer image. The heart of the plain demo version's SetTipWindowSize(), for example, is:

Two goals here: resize your offscreen bitmap (CBitmap* m_tipImage in the demo), and draw your tip contents into the offscreen bitmap. The offscreen bitmap management can be copied as-is into your version. And from there, drawing the tip is the same as for any window - for example, the plain demo calls DrawText(), this time without the DT_CALCRECT option, to show the text for the tip.

I'll gamble that you like code, so here's the entire function from TipDataPlain:

Two variants of AddTool and DelTool let you add tips for any sort of CWnd (in which case the window's screen CRect as given by GetWindowRect() is used) or for a CRect (in which case the rect should be in the local coordinates of the parent window that wants tips). Note AddTool first calls DelTool, so AddTool can also be used to update tips.

The tipData argument should point to the specific sort of TipData that you're using for your tip, such as TipDataPlain in the plain demo. Later, the data is used by your derived CreateTipImage() to do the actual tip drawing.

void SetMaxTipWidth(int maxWidth)

maxWidth is the maximum width of the tip in pixels (default 300). However, since all drawing is under your control, you can interpret this differently if you want to.

void ShowTipForPosition()

Call in the parent window that wants tips, in response to every WM_MOUSEMOVE.

void HideTip()

"Hides" the tip window by shrinking it down and moving to x 0, y 10. This avoids activation flicker in the parent window. You can call it if you need to, but you'll probably find that the default hiding is good enough.

void SetHideDelaySeconds(UINT delayInSeconds)

Seconds after which the tip window is hidden. Zero means never hide the tip.

void SetAvoidCoveringItem(bool avoidItem)

avoidItem true means the tip window will not cover any part of the underlying CWnd or CRect corresponding to the tip. False means the tip will appear near the cursor, wherever it is.

void SetAnimationStepDuration(UINT msecs)

The duration of each startup animation step, in milliseconds. The default is about 55 milliseconds.

void SetAnimationNumberOfFrames(UINT numFrames)

The default is about ten, which in combination with the default of 55 msecs between frames gives about half a second of animation when each tip is shown.

When the tooltip window first comes up, it immediately re-activates the parent window that called it up, and is never activated again. It "hides" by shrinking down to nothing and moving off to a corner of the screen with

This is perhaps a slightly unusual way of avoiding activation flicker, but works fine. And it's the main reason the tip base class is relatively simple.

If you'd like to see the GDI+ version of these custom tips in a "real" application, you're welcome to try out TenClock, a little freeware app that shows Outloook appointments as colored wedges on the face of a rather nice 3D clock, among other things. After you've installed it, right-click on the clock and pick Configure... to see the dialog with the custom tips. Outlook and 2000/XP are required (not tested yet under Vista - if you try it, please let me know if you see any problems). By the way, if you check TenClock's About box you might see your name there!

If you're on the fence about upgrading your tips, allow me to predict that 2007 will be the Year of the Eye Candy: in fact, I'll bet you'll be seeing a double-Beryled Vista of it - ow, sorry about that....

If you use this - leave a little comment? That would cheer me right up after all this typing.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

Until now the tooltip is working fine, but we have seen the below problem from sterday:

The tooltip sometimes doesn't show any text or backgroundcolor but just reflects whatever is present in the background. ie if i
hover the mouse over the trend, the tooltip just shows a part of the trend wherever is underneath it and it never changes after that. I debugged and couldn't find the same behaviour, but however if i make the "createFont" fail in the "CustomToolTipPlain::CreateTipImage" function by making madefont
zero, my tooltip is reflecting whatever is in the background. In this case the "SetTipWindowSize" is working and the createtipimage is not working (i disabled it by making madeFont zero).

Clearly a CClientDC should have a default white background with black text and dc.DrawText in "SetTipWindowSize" is using DT_CALCRECT so its not drawing anything but just calculating the rectangle. So if dcMem.DrawText in createtipimage is never called.

My question is where is it picking the background (whatever is on trend) from and which function is drawing that background on it? May be an answer for this helps for my solution. Thanks a lot for your time and help.

First off, since you say the problem just started yesterday - what changed yesterday? That's your best clue to pinning down the offending line of code.

Your symptoms as described suggest that your code in "CreateTipImage()" is throwing an error sometimes. The catch() at the bottom of that method zeroes out the tip image, with the same bad drawing result as setting "madefont" to zero. Assuming you left the catch() in your version, and I hope you did

Since the problem is not very repeatable, it sounds like a bad pointer in your drawing code, specfically in your custom version of CreateTipImage(). That's my best guess, anyway. Have you changed the drawing code, or any resources such as bitmaps that the code uses? Yesterday or very recently maybe? If not, alas you'll want to review all recent code changes to your project, especially looking for pointers that are deleted but not set to 0.

If you still can't trace the problem, try replacing your actual tip drawing code in CreateTipImage() with just a text message - if you need to, see the demo code for CustomToolTipPlain() that you started from for an example of drawing a text message. If that works in a stable way, you can gradually add your custom drawing code back in until something breaks.

Sorry I can't be of more specific help, but it's hard without seeing your code, and even then I haven't done much with MFC in several years so I'm feeling a bit rusty. Good luck, and if you have other questions that come up then just ask away, never hurts to ask.

There are situations where, this ShowWindow(SW_SHOW) is called even before calling the DrawTip, so i thought this might be the reason the tip is blank. I thought CClientDC dc(this); fails when windows is already shown. But this is not the main reason, the CreateTipImage is failing causing the m_tipImage to be deleted like you said.

I still have the catch, but the GetLastError() in the catch of CreateTipImage is returning 0 always. The tip is always having the standard size, ie the image width, height, everhting are claculated but nothing is drawn on it. Any other suggestions to find out where it is failing? or get the last error code or something like that? One sad thing is its happening only in release not in debug so still trying to figure out

I'm not sure what you mean there about the OnTimer() function, because if you look in the original version of CustomToolTip.cpp you'll see that it already has "void CustomToolTip::OnTimer(UINT nIDEvent)", around line 659. If you added "if (!MouseInTipArea()) { HideTip(); } else { ShowWindow(SW_SHOW); " to the existing OnTimer() function, offhand I'd say it can do nothing to help and has the potential to harm, so I'd revert to the original shipped version of CustomToolTip::OnTimer() if I were you. To put it another way, don't hack that base class or you have "voided the warranty"

Have you found a way to reproduce the problem consistently in the Release version? If so, you could try running the Release version under the debugger. You didn't mention, so I'll explain in case you haven't tried it before.
In Visual Studio, use Build->Configuration Manager.. to select your Release version, and Build if needed. Set a breakpoint in the catch() at the bottom of CreateTipImage(). Select Debug->Start Debugging(), which will run your Release version, and see if you can trip the breakpoint while playing with your tooltip. If so, then move the breakpoint to the top of your drawing code, and step through to see if you can spot any bad data, especially a pointer. If you can't spot the problem, move the breakpoint at the top of your drawing code down one line at a time until the bad drawing happens but the breakpoint is not tripped - at that point your breakpoint is one line past the bug. This is not guaranteed to work, alas, but it's worth a try.

If you can't reproduce the problem consistently, then I would still suggest examining all changes in the last few days, and also replacing your CreateTipImage() drawing code with a simple drawText() call, then gradually adding back your drawing code until the problem reappears.

I was able to resolve the issue. When i am applying the color to the text of the tooltip, i am pulling it out from another array of colors and i am giving the index higher then the index in the array, so the SetTextColor function is failing and nothing is being drawn. And you are true the m_tipImage is empty and causing a transparent background. So fixed the index and now i got rid of that issue.

One more thing, we created our tooltip using:: CreateEx(WS_EX_TOPMOST -- This means put this on top of all non topmost windows. This is fine, but if i have my "Task Messenger" opened with "Always On Top" checked, our tooltip is covering the Task Messenger too .

If possible can you test to see if this scenario is on your side too. Also when the monitor goes to sleep mode the tooltip still appears along with the screen saver. Right now i am working to fix this issue.

Updated:
This is also happening, when we hold Alt + Tab and switch to any window, the tooltip still appears

It's nice that you found your bad pointer, sometime they can be very hard to track down as I recollect (thinking back ten years to my last one

The important thing up front: you have hacked the base class for the tooltip, and judging by the snippet of OnTimer() that you posted you are altering the tooltip's behaviour. I would suggest removing all of your hacks to the base class, at least the ones you added on what one might call an "experimental" basis. Then it might stop popping up and redrawing when it's not supposed to.

It seems by the way your tooltip is a bit ambitious, wanting to be on the screen all the time: perhaps it would be happier as a regular window? I'm not sure how yours works, but perhaps it should just be there all the time and never be hidden? If so, it's not really a tooltip.

When several windows want to be "TOPMOST" I think the last one made, or at rather the last one made TOPMOST, becomes the one on top. Googling "Task Messenger" produces a gazillion results that I'm not going to wade through, but if you want the Task Messenger window to be in front of your tooltip you could make it topmost after starting your own app with the tooltip (sorry to be vague, but I don't know the name of your app with tooltip). There's an app for that, as the saying goes - Google "windows always on top" and take your pick. Or you could get a second monitor. Or stop making your "toolip" topmost, just bring it in front of your own application as a regular non-topmost window (and I'm sure with a little MSDN hunting you can do that).

Thanks for your reply, let me give you more information so that you can be clear about my situation:

My custom tool tip has the functionality you have describes in your example and in addition i have each line on the tooltip with different colors, each color corresponds to its pen color on the trend, and this is how i have done it. for every line of tooltip i am doing this

this way i was able to get different colors for the tooltip. Now the text back color and background color of tooltip is white. If i want to see the trend underneath instead of the white color,

1.) using TransparentBlt which one should be the source(the tip or trend) and which one should be the destination(tip or trend) to mark as co ordinates. That is should i copy the color block from tip to trend excluding white or should i copy color block from trend to tip excluding nothing.

2.) m_tipper has UpdateLayered function, can i use it to make it transparent?

I take it "trend" is your main window, and you want to show tooltips for the controls or rectangles there, right?

If your trend window and tooltip window are so large that the tooltip window will always obscure something important in the trend window, then I don't think there's an attractive solution to the problem. Blending the tooltip with the trend window will just look messy and distracting, no matter what blend you use. With that out of the way...

1) I haven't actually used TransparentBlt, but judging by the docs you want to copy from your "trend" window into the tooltip window, excluding the background color in your trend window. Having said that, it will probably look like hell. Better, I think, is to do about a 50-50 blend in the tooltip window, a blend of the tooltip and what's in the trend window behind it. The tooltip is in front, so it's the window that has to be changed - changing the trend window will have no effect, because the tooltip will just draw over it. And UpdateLayered will do that - see next.

2) UpdateLayeredWindow is a bit slow, but it does work and if you want to try it there's a very good article here on codeproject, Per Pixel Alpha Blend[^]

Idea: I suppose you're trying to make the tooltip partially transparent because there isn't room on the screen for both your trend window and the tooltip window. But if you can shrink down your windows enough with some creative re-design, preferably shrinking the tooltip, then you can set the tooltip to avoid covering up the item that has called up the tip with

m_tipper->SetAvoidCoveringItem(true); // true == position near item being tipped, without obscuring it

If you look at CustomToolTip::MoveAndResize(), you might be able to adjust the way it avoids covering the tip item so that you get a good view of the trend window.

I do hope that helps, especially if it inspires you to redesign your tooltip so that it doesn't cover up your trend too much

This custom tooltip, i was not able to activate or deactivate the tipper. I see too much of flickering when i hide and show the tipper for every mouse move. Is there anyway i can activate or deactivate the tipper.

Are you experiencing this problem with one of the demo projects as-is, or have you altered the source or put the tooltip in your own project?

Sorry you've caught me at an awkward moment, between development machines (as one breaks and I'm waiting for the new one), but I'll try to help - perhaps you could run under debug and try to pin the problem down a bit more? On the surface, its sounds like the tip window comes up and then the tip thinks it is not over the parent item any more, so it goes away.... Anyway, please come back to me with more details that you think might help and we'll see if we can sort this out.

My dialog ahs a trend and two buttons. I am using the tooltip control for a trend. Whenever the mouse leaves the trend, i should hide the tooltip.

My first question is:
Whenever i hover over the trend, i can see the ToolTip but my parent dialog(which contains the trend) is loosing focus. Is this how it should happen? Whenever the Tooltip disappears, my dialog is gaining focus again(flickering of parent dialog).

My Second question is:
The second way i am doing is to, delete the tipper completely

delete m_tipper;
m_tipper = 0;

instead of hiding it. I am creating it always when mouse comes into region. Now i still see the flickering of parent dialog.

I think the good way of solving the problem is to, deactivate the tipper instead of hiding or deleting etc. But i don't see the functions to do that. Can you please help me solve this problem.

Hi amarasat,
To answer your second question, don't delete the m_tipper instead of hiding it, that won't help your problem. The tipper is designed to be always on screen and shown, but never activated. Deactivation happens in CustomToolTip::OnActivate(), where the parent window is set to be the active window.

The real problem is that the tip window is not playing well with your parent window, you might try reviewing the how-to at the bottom of CustomToolTipPlus.cpp or CustomToolTipPlain.cpp as appropriate to ensure you haven't missed a step. At a guess, it sounds like CustomToolTip::MouseInTipArea() is always returning false, in other words the tip window isn't properly detecting that it is still over the parent window's current tip item, the control or rectangle that triggered the display of the tip.

A couple of questions for you: do the demo applications work for you? (I've tested them with VS 2003 and VS 2008, haven't had a chance to try VS 2010 yet.) And are you deriving your version from the GDI or the GDI+ version?

Yes. You can do this in the tooltip info notification handler. This can be done by either adding a tooltip to the list control itself or by having the tooltip parent relaying the notification for that specific control.

First check to see if it's over a list item. If it's over an item fill in the appropriate text (notify->ti->sTooltip). Next you will want to get the bounding rect of the item and update the coordinates of the tooltip (don't forget to convert the bounding rect to screen coordinates if necessary).

If it's not over an item set notify->ti->sTooltip to an empty string (preventing the tip from being displayed) or default tip.

Since I just started using the tool, I realize it has much potential. I'd like to know how to put simple formatted text in the tooltip (bullets, indenting, bold). Also, if there is anyone who has done some nice fancy stuff using this tool, it would be nice to share.
I saw what you did in the TenClock application - very fancy!!

Hi Mechi, you might enjoy "Programming Windows With MFC" by Jeff Prosise, chapters in there on fonts and text will get you going. Alas I've done nothing fancy myself with my own custom tooltip, all I really ever wanted was a picture and some text. Maybe you'll do a SuperTip and show us?

Thanks for TenClock comment, it's my try at "cheer you up a little because it's not too ugly" ware