Introduction
I was inspired to create
XBalloonMsg when I saw great article by
Paul Roberts,
CBalloonMsg.
I like balloon messages because visually they seem to be more lightweight
and less obtrusive than standard message box:
Standard
|
XBalloonMsg
|
This is ideal way to draw user's attention to a control or area of the
screen. I decided to learn more about
balloon tooltips which are used to show balloon messages, and find out how
to integrate them in my projects.
XBalloonMsg Features and Behaviors
Here are features I wanted in
XBalloonMsg:
|
The balloon message box should display until some user action occurs -
clicking mouse, pressing key (including Esc key), changing focus to another window, etc. - or until
a specified time interval has passed.
|
|
In addition to closing balloon message box after user action
mentioned above, there should be close button ()
on balloon message box, to
give user a visual cue as to how to dismiss balloon message.
|
|
You should be able to specify title and message strings either embedded or
as resource IDs.
|
|
It should be possible to display very long message strings - longer than the
standard 80-character strings you are limited to with
LPSTR_TEXTCALLBACK tooltips.
|
|
MFC should not be used, to allow for use in any C++ project.
|
|
The balloon message box should work properly on Vista.
|
|
In case balloon tips are disabled in
registry,
regular (rectangular) tooltip should be displayed.
|
The programmatic interface to XBalloonMsg is very
simple: just two functions, one of which you only need if you want
to dismiss message balloon for some particular reason:
Function |
Description |
void Show()
|
Show balloon message
|
void Destroy()
|
Remove balloon message from screen
|
Show() Function
Here are details for
Show()
function:
Show
The Show function creates, displays, and operates a
balloon message box. The message box contains an application-defined message
and title, a close button, and an optional icon.
void Show(
LPCTSTR lpszTitle,
LPCTSTR lpszMsg,
HWND hCtrl,
HWND hParent,
HINSTANCE hInstance,
UINT nIcon = TTI_INFO,
BOOL bUseBalloonTips = TRUE,
UINT nTimeOutSeconds = 0,
LPRECT pRect = NULL
);
Parameters
lpszTitle
[in] Pointer to a null-terminated string that contains the balloon message
title. If this parameter is NULL, no title and no icon will be displayed.
The special symbol LPCTSTR_DEFAULT_TITLE may be used; it will cause the
executable module name to be displayed.
lpszMsg
[in] Pointer to a null-terminated string that contains the balloon message
to be displayed. Must not be null. Text callbacks
(LPSTR_TEXTCALLBACK ) are not used, so the text string can be as
long as you want, up to the size of the internal text buffer
m_szMsg .
hCtrl
[in] Handle to the control that the message is being displayed for.
Must not be null.
hParent
[in] Handle to the parent window of the control. Must not be null.
hInstance
[in] Handle to the instance of the module that contains the
string resource. May be null if string resource is not used.
nIcon
[in] Specifies the icon to associate with the balloon message.
This can be one of the following values:
Value |
Meaning |
TTI_ERROR |
Use the error icon |
TTI_INFO |
Use the information icon |
TTI_NONE |
Use no icon |
TTI_WARNING |
Use the warning icon |
This parameter may also be the handle of an icon obtained from
LoadIcon
or
LoadImage
functions. If not present, this parameter defaults to TTI_INFO.
bUseBalloonTips
[in] Specifies whether balloon tips are to be used to display the message.
If this parameter is TRUE, balloon tips will be used unless disabled
in the registry. If this parameter is FALSE, regular tooltips will be used,
regardless of value of registry key.
If not present, this parameter defaults to TRUE.
nTimeOutSeconds
[in] Specifies the number of seconds before the
balloon message is automatically closed. If
this parameter is zero, the balloon message
will not be automatically closed. If not
present, this parameter defaults to zero.
pRect
[in] Pointer to a RECT struct that contains position where balloon message
is to be displayed. May be null if default position should be used.
If not present, this parameter defaults to null.
bSubclassParent
[in] Specifies whether parent is to be subclassed. Not subclassing
parent will cause a slight loss in functionality - clicking on another
app will not dismiss the balloon message.
If not present, this parameter defaults to TRUE.
NOTE: Using XBalloonMsgDll.dll with
VB.net apps requires that this parameter be FALSE.
|
Implementation Notes
I started by trying to get balloon message to display:
m_hWndBalloon = ::CreateWindowEx(0, TOOLTIPS_CLASS, 0,
WS_POPUP | WS_VISIBLE | TTS_ALWAYSTIP | TTS_NOPREFIX |
TTS_BALLOON | TTS_CLOSE | TTS_NOFADE | TTS_NOANIMATE,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
hParent, 0, 0, 0);
This worked, except that close button
(
)
did not display. I checked my code, then I checked
MSDN.
No joy. OK, so I started looking at USENET forums, and discover that
close button will only appear if app has
manifest for common controls v6 or
later. (I was in a hurry to get this working, so I hadn't bothered
with manifest.) OK, I add manifest, and close button appears. The same is
also true for VB applications - there must be manifest, or close button
will not be displayed.
Now that I have balloon message displayed,
how to get rid of it? Clicking close button of course works,
but what if user clicks on another window? I have taken care of
this in other projects by using TTF_SUBCLASS
flag.
This time, TTF_SUBCLASS
has no effect.
After trying several things to get this to work,
I give up and decide to subclass the parent window myself,
looking for specific messages that would indicate that user
is doing something, and so message balloon should be dismissed.
After implementing this,
I verify that this approach works by clicking on dialog caption,
clicking on another control, clicking on dialog background, and
clicking on another window.
All of these actions now dismiss the message balloon.
However, things are still not working quite right. Some keys (arrow keys,
function keys) are just ignored. What's worse, the Esc key
causes the balloon tip to disappear, but also closes the dialog!
The solution to these problems was to install a keyboard hook
using the
SetWindowsHookEx()
function. This allowed me to catch the keystrokes that a user would
expect to dismiss the balloon tip.
XBalloonMsgDLL |
|
The Win32 DLL
XBalloonMsgDLL.dll exports function
XBalloonMsgShow()
, which takes same parameters
as
CXBalloonMsg::Show()
(see above).
The download includes Visual Basic projects for both VS6 and VS2005,
that show how to use DLL with VB:
The DLL has the XP help icon, Vista help icon, and
question icon embedded as resources,
so you don't have to manually include those icons. The following table
shows available
special values for
XBalloonMsgShow()
's
nIcon parameter:
Value |
SDK Symbol |
Meaning |
0
|
TTI_NONE
|
Use no icon
|
1
|
TTI_INFO
|
Use the information icon
|
2
|
TTI_WARNING
|
Use the warning icon
|
3
|
TTI_ERROR
|
Use the error icon
|
10
|
—
|
Use the XP help icon
|
11
|
—
|
Use the Vista help icon
|
12
|
—
|
Use the question icon
|
Depending on whether your application is running on XP or Vista,
you can select the appropriate help icon. Here is code
from C++ demo app that checks for Vista:
BOOL IsVista()
{
BOOL rc = FALSE;
OSVERSIONINFO osvi = { 0 };
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
if (GetVersionEx(&osvi))
{
if ((osvi.dwPlatformId == VER_PLATFORM_WIN32_NT) &&
(osvi.dwMajorVersion >= 6))
{
rc = TRUE;
}
}
return rc;
}
This is how DLL's XBalloonMsgShow()
function is called
from VB6 (please see Form1.frm for complete code to VB app):
Private Sub ShowMessage(nIcon As Long)
Dim ptCursor As POINTAPI
Dim hWndOver As Long
Dim l As Long
Call GetCursorPos(ptCursor)
hWndOver = WindowFromPoint(ptCursor.X, ptCursor.Y)
l = XBalloonMsgShow("My Title", "This is My Message", _
hWndOver, GetParent(hWndOver), 0, nIcon, 1, 0, 0, 1)
End Sub
For VB2005, the last parameter (bSubclassParent) must be
FALSE
:
Private Sub ShowMessage(nIcon As Integer)
Dim ptCursor As POINTAPI
Dim hWndOver As Integer
Dim l As Integer
Call GetCursorPos(ptCursor)
hWndOver = WindowFromPoint(ptCursor.X, ptCursor.Y)
l = XBalloonMsgShow("My Title", "This is My Message", _
hWndOver, GetParent(hWndOver), 0, nIcon, 1, 0, 0, 0)
End Sub
C++ Demo App
Here is what demo app looks like:
If you do not want to use balloon messages (or cannot because
registry setting
disables them), regular tooltip will be displayed:
XBalloonMsg is also able to display large messages,
since text callbacks (
LPSTR_TEXTCALLBACK
) are not used:
‘What's This’ Help
In the above screenshot, notice
in dialog caption. The demo app implements
What's This help by using
XBalloonMsg - click on
and cursor changes to
help select cursor
().
Then click on a control and
you will see help displayed for that control in message balloon:
On Vista you will see similar message balloon:
The tooltip control has built-in support for info, warning, and error icons,
but none for help icon ( ). To display help icon you see in screenshot,
I extracted help.ico file from the image library that comes with
VS2008, VS2008ImageLibrary.zip. There are two help.ico files,
one for XP and one for Vista. (In VS2005, the zip is called
VS2005ImageLibrary.zip, but only contains XP icons).
This file can be found in Common7 directory.
The help.ico files contain multiple formats - from 16x16x16 to 48x48x32.
To reduce the size, I edited them (using excellent
ArtIcons Pro
icon editor)
to remove everything but the 32bpp formats. To ensure that correct
size (16x16) was loaded, I used LoadImage() rather than
LoadIcon() , after first checking whether XP or Vista
icon should be used.
|
This technique allows you to have What's This help for any control
without having to create a help file. Here is code that displays
What's This help balloon:
BOOL CXBalloonMsgTestDlg::OnHelpInfo(HELPINFO* pHelpInfo)
{
CString s = _T("");
if (s.LoadString(pHelpInfo->iCtrlId))
{
CXBalloonMsg::Show(_T("What's This? Help"),
s,
::GetDlgItem(m_hWnd, pHelpInfo->iCtrlId),
m_hWnd,
AfxGetInstanceHandle(),
(UINT)m_hHelp);
}
else if (pHelpInfo->iCtrlId != -1)
{
s.Format(_T("There is no help for control #%d.\r\n")
_T("Please click on another control."),
pHelpInfo->iCtrlId);
CXBalloonMsg::Show(_T("Help Unavailable"),
s,
::GetDlgItem(m_hWnd, pHelpInfo->iCtrlId),
m_hWnd,
AfxGetInstanceHandle(),
TTI_INFO);
}
return TRUE;
}
One final note about What's This help: it is customary to use
Shift+F1 to invoke What's This help. However, in MFC
by default the F1 key (unshifted) will activate What's This help.
The following code will restore the Windows standard behaviors for F1 and
Shift+F1.
BOOL CXBalloonMsgTestDlg::PreTranslateMessage(MSG* pMsg)
{
BOOL rc = TRUE;
if (pMsg->message == 0x004D)
{
if (GetKeyState(VK_SHIFT) & 0x8000)
SendMessage(WM_SYSCOMMAND, SC_CONTEXTHELP);
else
OnHelp();
}
else
{
rc = CDialog::PreTranslateMessage(pMsg);
}
return rc;
}
void CXBalloonMsgTestDlg::OnHelp()
{
}
If you want to add What's This help to a dialog, you must also select the
extended style Context help in dialog properties. One gotcha: you
can't have a minimize or maximize box at the same time, or the
What's This button won't be displayed.
|
Balloon Tip Registry Key
On most systems, balloon tips are enabled by default.
However, there is registry key that may be set that will
disable balloon tips from displaying. If balloon tips are disabled,
this has unfortunate effect of preventing
any tooltip created with
TTS_BALLOON
style from displaying - it does not
automatically fall back to displaying a regular tooltip.
To handle this situation, XBalloonMsg checks
HKEY_CURRENT_USER
registry key
Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced\EnableBalloonTips
and if it is set to 0,
TTS_BALLOON
style
will not be used. This allows regular tooltips to be displayed:
How to use
Step 1 - Add Files
To integrate
CXBalloonMsg
into your app,
you first need to add following files to your project:
- XBalloonMsg.h
- XBalloonMsg.cpp
The .cpp file
should be set to Not using precompiled header
in Visual Studio. Otherwise, you will get error
fatal error C1010: unexpected end of file while looking for precompiled header directive
XBalloonMsg.h should be included in whatever module you intend to use
XBalloonMsg in. Also, if you want to implement
What's This
help, you should add one or both of the help icon files included with the demo.
Step 2 - Call CXBalloonMsg::Show()
The
CXBalloonMsg::Show()
function
displays message balloon, and may be called using
embedded strings like this:
CXBalloonMsg::Show(_T("Edit1 Info "),
_T("This is an info string for Edit1."),
m_ctrlEdit1.m_hWnd,
m_hWnd,
AfxGetInstanceHandle(),
TTI_INFO);
or using string resource IDs like this:
CXBalloonMsg::Show(MAKEINTRESOURCE(IDS_EDIT2_INFO_TITLE),
MAKEINTRESOURCE(IDS_EDIT2_INFO_MSG),
m_ctrlEdit2.m_hWnd,
m_hWnd,
AfxGetInstanceHandle(),
TTI_INFO);
Internally there is just one function used for both calls.
The
function determines whether
the calling parameter is resource id or string,
by inspection of high-order word of pointer:
if (HIWORD(lpszMsg) == 0)
{
UINT nID = LOWORD((UINT)(UINT_PTR)lpszMsg);
if (!::LoadString(hInstance, nID, m_szMsg, sizeof(m_szMsg)/sizeof(TCHAR)-2))
{
XBALLOONMSGTRACE(_T("ERROR - failed to load message string %d\n"), nID);
_ASSERTE(FALSE);
}
}
else
{
_tcsncpy(m_szMsg, lpszMsg, sizeof(m_szMsg)/sizeof(TCHAR)-2);
}
Revision History
Version 1.3 - 2008 July 11
- Added parameter to control parent subclassing
- Added separate VB and DLL projects for VS6 and VS2005
- Added example of timed XBalloonMsg
Version 1.2 - 2008 July 2
Version 1.1 - 2008 July 1
- Added DLL project and VB demo program
Version 1.0 - 2008 June 23
Usage
This software is released into the public domain. You are free to use it
in any way you like, except that you may not sell this source code. If you
modify it or extend it, please to consider posting new code here for everyone
to share. This software is provided "as is" with no expressed or implied
warranty. I accept no liability for any damage or loss of business that
this software may cause.