Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

XBalloonMsg - a non-MFC balloon-shaped message box

0.00/5 (No votes)
11 Jul 2008 21  
XBalloonMsg displays a message box using balloon-style tooltips. Message and title strings may be embedded or stored in string resource.

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
screenshot
XBalloonMsg
screenshot

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:
screenshot 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.
screenshot In addition to closing balloon message box after user action mentioned above, there should be close button (screenshot) on balloon message box, to give user a visual cue as to how to dismiss balloon message.
screenshot You should be able to specify title and message strings either embedded or as resource IDs.
screenshot 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.
screenshot MFC should not be used, to allow for use in any C++ project.
screenshot The balloon message box should work properly on Vista.
screenshot 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 screenshot
TTI_INFO Use the information icon screenshot
TTI_NONE Use no icon
TTI_WARNING Use the warning icon screenshot

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 (screenshot) 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

screenshot
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:

screenshot

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 screenshot
2 TTI_WARNING Use the warning icon screenshot
3 TTI_ERROR Use the error icon screenshot
10 Use the XP help icon screenshot
11 Use the Vista help icon screenshot
12 Use the question icon screenshot

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)                         ' Get cursor position
    hWndOver = WindowFromPoint(ptCursor.X, ptCursor.Y)  ' Get window cursor is over
    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)                         ' Get cursor position
    hWndOver = WindowFromPoint(ptCursor.X, ptCursor.Y)  ' Get window cursor is over
    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:

screenshot

If you do not want to use balloon messages (or cannot because registry setting disables them), regular tooltip will be displayed:

screenshot

XBalloonMsg is also able to display large messages, since text callbacks (LPSTR_TEXTCALLBACK) are not used:

screenshot

‘What's This’ Help

In the above screenshot, notice screenshot in dialog caption. The demo app implements What's This help by using XBalloonMsg - click on screenshot and cursor changes to help select cursor (screenshot). Then click on a control and you will see help displayed for that control in message balloon:

screenshot screenshot screenshot

On Vista you will see similar message balloon:

screenshot

The tooltip control has built-in support for info, warning, and error icons, but none for help icon (screenshot). 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("");
    // the string resource id is the same as control id
    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)    // ignore clicking on dialog box
    {
        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;

    // Trap the famous undocumented F1 key message. Windows normally 
    // responds to this message by sending the WM_HELP message (referred 
    // to as WM_HELPINFO in MFC), which we use to enter What's This 
    // (Shift+F1) help mode. This override makes unshifted F1 call
    // the normal Help function, and makes shifted F1 invoke the
    // What's This help.

    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()
//=============================================================================
{
    // Here you can implement full-blown F1 help
}

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:

screenshot

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:
    // is the message a string or a resource id?
    if (HIWORD(lpszMsg) == 0)
    {
        // id
        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
    {
        // string
        _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

  • Initial public release

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.


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.

A list of licenses authors might use can be found here