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.