|
Introduction
While working on a new app, I decided it would be nice to use balloon-style tooltips to convey simple messages to the user rather than relying on ye olde ::AfxMessageBox. It would be a less disruptive way of warning the user of minor things like editing mistakes, failing to fill in a field and so on.
I found an existing CodeProject article that seemed to be exactly what I was looking for: Balloon Help as a Non-modal Replacement for MessageBox(). On closer inspection, I decided against using this because it doesn't use Windows' own balloon tooltip. Instead, it draws its own, and at the time of writing I think it needs a small update for Vista. Surely there had to be a simpler solution that uses the real tooltip control? Well I didn't find one, so I wrote my own and it's called CBalloonMsg.
Prerequisites
Before going any further, please be aware that I coded this for use in theme-aware apps (i.e. those with a manifest requesting common controls v6) running on Vista or XP (preferably SP2). I haven't tried using it on Windows 2000, but I'm pretty sure it wouldn't look right. And don't even think about NT4 and Win9x!
Using the Code
This couldn't be much simpler:
The Show/ShowForCtrl methods display the tooltip at the nominated position or over the nominated control. By default, the tooltip stays up for 10 seconds then closes automatically. It'll close earlier if there's a change in focus or if the mouse moves appreciably. You can change the "stay up" or autopop time easily (more on this later) and you can close or "pop" the balloon at any time using the RequestCloseAll() static method.
How it Works (Briefly)
When you call one of the Show methods, a separate user interface thread is created. This in turn creates its own small, transparent window around the current mouse position, then sets about processing that window's message queue. The window acts as the parent for a tooltip control, and calls CToolTipCtrl::RelayEvent to make sure that the tooltip has first bit at all relevant Windows messages. The tooltip is given a zero millisecond initial delay, so it appears as soon as it is activated. When the tooltip eventually closes, a call is made to PostQuitMessage which gets rid of the transparent parent window and ends the thread. The thread wrapper self-deletes for completeness.
That's about it. You'll find more detailed information on the inner workings in the code's comments.
Points of Interest
SafeShowMsg and BalloonsEnabled
By default, Windows allows the use of balloon tooltips. There is however a registry tweak available that prevents balloons from being displayed. It seems this is most often used to kill those often redundant and distracting tray balloons that Windows likes to present from time to time.
The tweak involves setting zero for the DWORD value EnableBalloonTips in HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced (thanks to DerMeister for reporting this!)
To get around this, the latest version of the class now includes two additional methods: BalloonsEnabled and SafeShowMsg.
BalloonsEnabled() returns TRUE if the user hasn't suppressed balloon tooltips. Following on from that, SafeShowMsg() uses balloons to display a message if they're enabled, and reverts to AfxMessageBox if the user has switched them off. The demo shows this function in action - take a look at CTTTestDlg::DoDataExchange.
Icons
You can supply your own icon to the Show calls, OR you can use Windows' built-in icons by using the special values described in the MSDN doc for TTM_SETTITLE: 1 for info, 2 for warning, 3 for error.
You can also change quite a few of the defaults for CMessageBalloon by setting new values for its static members:
s_nTimerStep |
Defaults to 30 milliseconds. Determines frequency of checks for balloon termination states (see History for Version 2 changes) |
s_nAutoPop |
Time before balloon self-closes in milliseconds. Defaults to 10 seconds. Set to 0 to let the balloon stay up until closed through focus changes, user action etc. |
s_nMaxTipWidth |
Maximum tip width in pixels. Makes the balloon use linebreaks. |
s_nToolBorder |
The amount by which the mouse can move before the balloon pops |
GetGUIThreadInfo
Finally, note the use of the handy function GetGUIThreadInfo which allows us to check the focus window in another thread.
Special Version Adapted for VC6 by Damir Valiulin
The third download at the top of the article is for a VC6 version of the demo project. It contains some minor differences from the original code, as follows:
- Minor changes to be able to compile under VC6
- Check for Win32 (no balloon tip there) and for registry disabling hack
- Changes to function calls for simplification (got rid of calls with
string IDs)
Many thanks to Damir for this!
History
Version 1: 6th March, 2008
Version 2: 16th March, 2008
Changed to using a tracking tooltip to counter anomalies in positioning of the balloon and its pointer when the dialog was near the edge of the screen. The use of TTF_TRACK necessitated other changes:
- Repositioning of the balloon is now achieved via the
TTM_TRACKPOSITION message rather than through the use of SetWindowPos.
TTM_TRACKACTIVATE is now used to activate the tooltip, rather than the MFC method Activate().
- A timer is also created and set to fire every 30 milliseconds or so (this is configurable via
s_nTimerStep above). The timer gives us a chance to spot changes in the state of windows owned by the primary thread that should bring about closure of the balloon. It also lets us (re)implement the automatic closure interval (autopop) that is used when s_nAutoPop is non-zero.
Version 3: 23rd March, 2008
- Added
SafeShowMsg and BalloonsEnabled to detect when the user has suppressed balloons via a reg tweak and fall back to AfxMessageBox.
- Added overloads to take
HWND for target control as well as CWnd*.
Version 4: 30th March, 2008
- Added a VC6 version, kindly adapted by Damir Valiulin.
| You must Sign In to use this message board. |
|
| | Msgs 1 to 25 of 47 (Total in Forum: 47) (Refresh) | FirstPrevNext |
|
 |
|
|
Using WinXP32 Pro SP2, VS2005 Pro SP1. Demo executable works fine, but added to my pgm doesn't work (no balloons appear). I wonder if this is because I am using "normal" tooltips in my MFC project.
My code: CBalloonMsg::Show("GREETING","Hi");
Stepping through with debugger, I see CBalloonMsg::Create(NULL) GetWndClass CToolTipCtrl::CreateEx(CBalloonMsgWnd hWnd=0x50978,67,0) returns 0 (failure) Within CreateEx: CToolTipCtrl::Create AfxDeferRegisterClass returns 1 CWnd::CreateEx(0,"tooltips_class32",NULL,0x80000043,0x80000000,0x80000000,0x80000000,0x80000000,0x50978,NULL,NULL) (x,y,nWidth,nHeight all 0x80000000) PreCreateWindow returns 1 AfxCtxCreateWindowEx returns hWnd=0x100B24 AfxUnhookWindowCreate returns 1 ModifyStyleEx(0,0) returns 0 (failure) SendMessage(TTM_ADDTOOL) returns 1 SetTitle returns 1 m_bReposition = 0 SendMessage(TTM_TRACKACTIVATE) returns 1 SetTimer(1,30,NULL) returns 1 (do you mean to check every 30 msec?)
The only TRACE output is "STARTING: CBalloonMsgThread"
Any ideas what's wrong?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Very nice. I'm using this control to display extra information when a user clicks on a custom control I wrote. The user can click in many places in rapid succession to very properties of those locations (think viewing the locations on a map). Anyway, the default version of the code would display several balloons at the same time. I needed a way to close all existing balloons. What I did was add a CWnd* parameter to the CBalloonMsg::Show() function, with a default value of NULL. That value is passed to the CBalloonMsgThread that is made in Show(). Then in CBalloonMsgThread::InitInstance(), I send a custom message to that CWnd, with the HWND of the tooltip control as its LPARAM. The CWnd (I've used my custom control as the handler, can be anything) has a handler for that message. It stores the HWND in a vector. Finally I have a function called CloseAllTooltips(). It contains this:
for (size_t i = 0 ; i < m_ToolTips.size() ; i++) { HWND wnd = m_ToolTips[i]; ::SendMessage(wnd, WM_CLOSE, NULL, NULL); } m_ToolTips.clear();
So now when I need to close the outstanding tooltips (in my case there is only one since I always close all before showing a new one), I can call this function.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I try to write a program with a taskbar button and modified you source code. I want to see the balloon message point to the icon on the system tray when I click taskbar button. But I have not made it ok yet. So I leave this message so that everyone help me to solve this problem. Please help me to make it. Thank you very much! (I'm sorry. I'm not good at English)
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
For VC6, when defining cTTM_SETTITLE, you need to take account of Unicode:
#ifdef _UNICODE #define cTTM_SETTITLE (WM_USER + 32) #else #define cTTM_SETTITLE (WM_USER + 33) #endif
Great class, thanks!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
When Building an MFC application,there is nothing.The tooltip show truly.But in an MFC rugular DLL project,I create a dialg and test it,the tooltip can not show.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Just to check my understanding: does the problem show up in a project that produces a DLL, or just in a project where MFC is linked as a DLL (as opposed to static linking)?
I haven't checked either of those scenarios yet - I always use static linkage to avoid any DLL-hell problems, but I'll look into it as soon as I get a bit of free time.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
In my app, the balloon disappears much to soon, even if i don't move the mouse (the balloon is displayed 'far away' from current mouse pos, but i'm not sure that this is a reason. Solution/Workaround:
case TTN_POP: { /* // The tooltip is closing - let's make sure our thread does likewise TRACE( "CBalloonMsgWnd: Closing due to TTN_POP\n" ); ::PostQuitMessage( 0 );*/ *pResult = 0; return TRUE; }
i just commented this PostQuitMessage....
sideeffect: moving the mouse does not hide the tip anymore... does anyone have a better solution?
btw.: Thanks for the code !
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi RedFraggle - can you tell me which version of the code you're using? When I tested the most recent update I found that TTN_POP wasn't firing at all, but I left it in just in case...
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
i upgraded to the most recent version, the same day i posted the message. I did not use it as static call CBallonMsg::Show() but i made a member variable and used it via m_aBallonMsg.Show(...), maybe thats a problem... i will try it again when i get some time.
additional infos: OS: WinXP SP2, maybe no manifest used (i included it, but it seems not to work) WIN_VER increased from 0x500 to 0x501, no difference as far as i noticed...but i'm not quite sure The popup is shown exactly like a 'normal' tooltip... (yellow)
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Paul,
Great job! I've always wanted to have an unobtrusive way of showing an error in input for dialogs. Showing balloon tips over controls seems to be the right answer. I've also seen the other implementation, but found it a bit flaky.
Anyways, I've modified your classs a bit to compile on VC6 without Windows SDK headers. I can send you the project if you want. It also has a suggested fix for registry hack. Basically I check if registry key is set to non zero value, then I display AfxMessageBox (with warning, error or info icon), otherwise it will show balloon.
Thanks, Damir
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
That sounds great - why don't you mail it to me (I've sent you my email address privately) and I'll pop it up with my next revision of the article?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
It seems like the "email reply" system is broken on this site because I didn't get your message. So maybe just send me e-mail and then I'll send you the files. My address is XXXXXXXXX
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
1. Instead of using a CWnd*, why not use an HWND to the nominated control for the ShowForCtrl method?
2. Overload the AfxMessageBox function to receive a handle to the nominated control, along with the normal AfxMessageBox parameters. The implementation of the new AfxMessageBox could call CBalloonMsg::ShowForCtrl if themes are enabled, if not, then call the real AfxMessageBox. It could, based on the nType parameter, determine which icon to use in the tooltip. This would truly make your tooltip control a true alternative to AfxMessageBox.
Nice work, BTW!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
#1 - yep, I'll try to remember to add a version that takes a HWND #2 - Nice idea, very nice. I'll take a look at that this weekend.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
1. Unlike the screenshot, when you click the Test button, the balloon pops up underneath the edit box, pointing up. I don't see anything obvious in the class, like an "orientation" setting. Is there any way to control how the balloon pops up?
2. Clicking on the dialog background doesn't close the balloon, although clicking anywhere else (caption, balloon, edit box) seems to close it. It seems like clicking on the dialog background should close the balloon too.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
1) Balloon orientation
Yep, I should probably update the screenshot.
In the first version, I used non-tracking tooltips. The balloon appeared above the ctrl BUT it's behavior was less than brilliant when the dialog was near a screen edge. In the bottom right corner of the screen, the balloon pointer was severely off target. Near the top edge of the screen, the balloon sometimes inverted and again the pointer didn't point to the right place.
The new version uses tracking tooltips and appears to be consistent and reliable, but for some reason MS decided that the default orientation should be below the target point, except in cases where the balloon won't fit on screen - e.g. when near the lower edge of the screen. As far as I can see, there's now way to change this, and I have spent a fair bit of time looking for one.
2) Click on dialog background
Yep that's a good point. I'll see if I can add that next weekend. The complication here is that with a standard tooltip, the ctrl takes care of closing on mouse activity. With a tracking tooltip, it needs to be coded explicitly.
Cheers
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I believe the reason for everyone not seeing it is because they likely used a tweak utility or modified the registry setting for balloon tips on the taskbar. Your code uses the technique that requires this to be enabled to see them.
Go to the following location in your registry HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced
and change the DWORD value for EnableBalloonTips from 0 to 1 and that should fix the problem
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Many thanks for that - I hadn't even considered that a registry tweak might be involved! I'll check it out and maybe put a warning into the class if balloon tips have been disabled.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I've updated the source and demo downloads with a new version that uses tracking tooltips. In tests on my PC, this seems to get round problems with the balloon pointer when the parent window is near a screen edge. It also allows the balloons to stay up indefinitely if required (as requested by Hans Dietrich).
It may also help in the cases where folks aren't seeing the balloon at all, and even if it doesn't, there are extra traces in this version that could help if the problem persists on those systems.
|
| Sign In·View Thread·PermaLink | 2.00/5 (1 vote) |
|
|
|
 |
|
|
Hi,
Nice app.
Small problem, clicking the OK button shows the balloon pointing to the OK button, not the text box.
I tested the precompiled app on xp sp2.
|
| Sign In·View Thread·PermaLink | 1.00/5 (1 vote) |
|
|
|
 |
|
|
Thanks for letting me know!
I can't get this to happen on my test system - does the same thing happen when you press the "Test" button rather than the OK button? And could you tell me whether you're using the mouse to hit the OK button or using the Enter key?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
This happens when I use the mouse to click the OK button, clicking the test button with the mouse is ok. If I push enter when the app launches (the OK button is in focus) then the position of the balloon is ok also.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Thanks for the info!
I've finally managed to reproduce this one by moving the dialog close to the lower right hand edge of the screen. I'll put some time into it tomorrow and hopefully get a solution.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
General News Question Answer Joke Rant Admin
|