C++ At Work

Controlling Balloon Tips, and More

Paul DiLascia

Code download available at:CatWork2006_07.exe(217 KB)

Contents

Upgrade Update
Triple-Click Update

Q I can’t find a way to make my balloon tips show whenever I want them to. That is, when my CEdit-based control has the focus, the balloon shows and then disappears whenever the control loses focus. I currently use the RelayEvent of the balloon in PreTranslateMessage event to make it pop up. How can I make my balloon tips show as I want?

Q I can’t find a way to make my balloon tips show whenever I want them to. That is, when my CEdit-based control has the focus, the balloon shows and then disappears whenever the control loses focus. I currently use the RelayEvent of the balloon in PreTranslateMessage event to make it pop up. How can I make my balloon tips show as I want?

Omid Manikhi

A For readers who don’t know what balloon tips are (there may be a few), they’re little yellow word balloons similar to tooltips that you can show when the user tabs to your edit control. Balloon tips were introduced with Windows® XP (_WIN32_WINNT>= 0x501). It’s your responsibility to show and hide balloon tips at the proper time by calling EM_SHOWBALLOONTIP and EM_HIDEBALLOONTIP.

A For readers who don’t know what balloon tips are (there may be a few), they’re little yellow word balloons similar to tooltips that you can show when the user tabs to your edit control. Balloon tips were introduced with Windows® XP (_WIN32_WINNT>= 0x501). It’s your responsibility to show and hide balloon tips at the proper time by calling EM_SHOWBALLOONTIP and EM_HIDEBALLOONTIP.

At the same time they added balloon tips, the friendly Redmondtonians also added another edit-control feature—banner cues. A banner cue is a prompt like "Yo, type your name here!" that Windows displays in grayed text when the edit control is empty. As soon as the user tabs to the control, the banner cue disappears. Unlike balloon tips, which you must hide and show dynamically, you can set the banner cue once with EM_SETBANNERCUE when you initialize your form/dialog, and Windows will display banner cues at the proper time. Curiously, while MFC has CEdit wrappers for Get/SetBannerCue, it doesn’t have wrappers for balloon tips.

The symbols EM_SHOW/HIDEBALLOONTIP and EM_GET/SETBANNERCUE are defined in commctrl.h, but not the version of commctrl.h that ships with Visual Studio® 2005. You have to download the latest Windows SDK. In general, if you want the latest features, you should always build your projects with the Windows SDK files, not the ones that ship with Visual Studio. I’ll show you how to implement balloon tips and banner cues in just a moment, but first I must digress to explain a little about manifests.

The most likely reason you couldn’t get your balloon tips to work is that your app is loading an older version of comctl32.dll. Including the right header file is only half the story; you also must ensure your app loads the latest DLL. Balloon tips and banner cues require Common Controls version 6.0. By default, the version of comctl32.dll that’s installed in the \WINDOWS directory is version 5.82, which is a redistributable version. Version 6.0 is not redistributable. Users can only get it by installing Windows XP. The 6.0 version lives all by itself in its very own side-by-side directory, one with a long nasty name like C:\windows\WinSxS\x86_Microsoft.Windows.Common-Controls_6595b64144ccf1df_6.0.0.0_x-ww_1382d70a\. On my machine, I have four—count ‘em, four!—such directories, one each for versions 6.0.0.0, 6.0.10.0, 6.0.2600.1579, and 6.0.2600.2180. So now we’ve progressed from DLL Hell to folder madness!

So now that you know where version 6.0 of comctl32.dll lives—how do you get your app to use it? This is where the manifest comes in. As you all know by now, a manifest is an XML file that describes your app’s dependencies—that is, which versions of which DLLs it needs to run. The manifest lets you say, "My app needs version 6.0 of comctl32.dll." You can specify which version of any DLL your app requires, and Windows will try to load it. Figure 1 shows the manifest I wrote for my test program, with a dependency that calls for version 6.0 of comctl32.dll. Note the public key token, which is required.

Figure 1 Manifest Using Common Controls V.6.0

<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> <assemblyIdentity version="1.0.0.0" processorArchitecture="X86" name="MSDN.DiLascia.TestForm" type="win32" /> <description>TestForm -- Test program for RegexForm</description> <dependency> <dependentAssembly> <assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="X86" publicKeyToken="6595b64144ccf1df" language="*" /> </dependentAssembly> </dependency> </assembly>

Writing the manifest is the easy part. The hard part is getting Windows to use it. There are two ways to add a manifest to your app. You can install a separate application.exe.manifest file along with your app.exe; or you can embed the manifest in your exe as a resource. Embedding is better because you don’t have to worry about the exe and manifest becoming separated. The simplest way to add a manifest to your app is to add a line in your resource file:

IDR_MAINFRAME RT_MANIFEST "res\\MyApp.manifest"

Another way is to use the manifest tool, mt.exe, which you can control from your project settings under Manifest Tool. For example, you can specify additional manifests to embed under Manifest Tool | Input and Output (see Figure 2). By default, Visual Studio generates an embedded manifest for whatever libraries you link with. The linker generates the manifest and the Manifest Tool embeds it in your exe. That’s why Omid couldn’t get his balloon tips to show: the default manifest stipulated an earlier version of Common Controls.

Figure 2 Specifying Additional Manifests

If you’re not sure which version of a particular DLL your app actually loads, there’s an easy way to find out: call the DLL’s DllGetVersion function. Not all DLLs provide DllGetVersion, but most modern DLLs do, and fortunately ComCtl32.dll does. You can call GetProcAddress to see if the DLL implements DllGetVersion and, if so, call it. Way back in April 1998, I wrote a little class CModuleVersion with a static function DllGetVersion that encapsulates the process and can be used as follows:

DLLVERSIONINFO dvi; CModuleVersion::DllGetVersion(_ T("comctl32.dll"), dvi);

TestForm uses CModuleVersion to display the Common Controls version number in its main dialog (see Figure 3). Displaying version numbers in your About dialog is a good idea because it facilitates debugging and support. When a user calls to say your app isn’t working, the support folks can ask her to run the About dialog to quickly rule out DLL version problems.

Figure 3 TestForm Page

Figure 3** TestForm Page **

And speaking of TestForm, now I can finally return to my main topic, which is how to implement balloon tips and banner cues. For my sample app, I modified the RegexForm program from my April and August 2005 columns. RegexForm uses the Active Template Library (ATL) Regex class (CAtlRegExp) to implement a table-driven form-validation system. For details, read the columns. FormTest uses my own home-grown CPopupText class to display pop-up tips that prompt the user what to type. For this month’s column, I modified CPopupText to use balloon tips. Figure 4 shows the before and after screen shots, and Figure 3 shows TestForm running with banner cues and balloon tips displayed.

Figure 4 Showing Balloon Tips

Figure 4** Showing Balloon Tips **

Setting banner cues is straightforward. Just call MFC’s CEdit::SetBannerCue. RegexForm::Init does it when you initialize your regex form. CRegexForm already has the names of all the fields in the descriptor table you give it, so it’s easy to set each banner cue to "Enter [field-name]" where field-name is the actual field name. For example, "Enter Zip Code" or "Enter Phone Number." RegexForm::Init initializes the banner cues automatically from your form table if it detects version 6.0 common controls:

// in CRegexForm::Init for (/* each fld=FLDINFO */) { ... if (m_iComCtlVer>=6) { CEdit* pEdit = (CEdit*)pDlg->GetDlgItem(fld.id); CString s; s.Format(_T(" Enter %s here"), fld.name); pEdit->SetCueBanner(s); } }

CRegexForm gets m_iComCtlVer from CModuleVersion::DllGetVersion as I already described. You don’t actually need to perform this check because older edit controls will ignore EM_SETBANNERCUE.

So much for banner cues—what about balloon tips? They’re slightly more work, but not much. The proper time to show the balloon tip is when your edit control gains focus. That is, when it receives WM_SETFOCUS or the main dialog receives WM_EN_SETFOCUS. Likewise, you should hide the tip when your edit control loses focus. For FormTest, I added a one-second delay before showing the balloon. If the user types immediately, why pester him with annoying screen junk?

CRegexForm and CPopupText already have all the logic to trap WM_EN_SET/KILLFOCUS and do delayed prompting with timeouts, so instead of implementing a new CBalloonTip class, I decided to ride on the back of my already-working CPopupText class. Why write code you don’t have to? Almost everything is exactly the same as before, except when it comes time to actually display the tip. This happens in CPopupText::OnTimer, when my show-the-tip alarm rings:

// in CPopupText::OnTimer if (m_bUseBalloonTip) { CString text; GetWindowText(text); EDITBALLOONTIP bt; bt.cbStruct = sizeof(bt); bt.pszText = text; bt.pszTitle = NULL; bt.ttiIcon = TTI_NONE; bRet = m_pWndEdit->SendMessage(EM_SHOWBALLOONTIP, 0, (LPARAM)&bt); } if (!bRet) // display popup text

CPopupText gets a new flag, m_bUseBalloonTip (default=TRUE). If the flag is set, CPopupText attempts to display a balloon tip. It gets the tip text from the pop-up text window, which CRegexForm has already set to the proper prompt. If the balloon tip fails (bRet=FALSE), CPopupText degrades gracefully back to its old-style popup tips. Piggy-backing on CPopupText has another benefit, too: I can route the WM_TIMER messages through the pop-up text window even though this window is hidden if balloon tips are operating. Figure 5 shows CPopupText with balloon tip support. The banner cues are in RegexForm.cpp, which you can download.

Figure 5 CPopupText with Balloon Tip Support

puptext.h

#pragma once ////////////////// // Get NONCLIENTMETRICS info: ctor calls SystemParametersInfo. // class CNonClientMetrics : public NONCLIENTMETRICS { public: CNonClientMetrics() { cbSize = sizeof(NONCLIENTMETRICS); SystemParametersInfo(SPI_GETNONCLIENTMETRICS,0,this,0); } }; ////////////////// // Popup text window, like tooltip. // class CPopupText : public CWnd { public: CSize m_szMargins; // extra space around text: change if you like CFont m_font; // font: change if you like UINT m_msecTimeout; // auto-timeout BOOL m_bUseBalloonTip; // use balloon tip CWnd* m_pWndEdit; // edit control for balloon tip, if any CPopupText(); virtual ~CPopupText(); BOOL Create(CPoint pt, CWnd* pParentWnd, UINT nID=0); void ShowDelayed(UINT msec, UINT msecTimeout=0, CWnd* pWndEdit=NULL); void Cancel(); void UseBalloonTip(BOOL bUseBalloonTip) { m_bUseBalloonTip = bUseBalloonTip; } protected: virtual void PostNcDestroy(); virtual BOOL PreCreateWindow(CREATESTRUCT& cs); void DrawText(CDC& dc, LPCTSTR lpText, CRect& rc, UINT flags); afx_msg void OnPaint(); afx_msg void OnTimer(UINT nIDEvent); afx_msg LRESULT OnSetText(WPARAM wp, LPARAM lp); DECLARE_DYNAMIC(CPopupText); DECLARE_MESSAGE_MAP(); };

puptext.cpp

#include "stdafx.h" #include "puptext.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif enum { TIMER_SHOW = 1, TIMER_CANCEL, }; IMPLEMENT_DYNAMIC(CPopupText,CWnd) BEGIN_MESSAGE_MAP(CPopupText,CWnd) ON_WM_PAINT() ON_MESSAGE(WM_SETTEXT, OnSetText) ON_WM_TIMER() END_MESSAGE_MAP() CPopupText::CPopupText() { CNonClientMetrics ncm; m_font.CreateFontIndirect(&ncm.lfMenuFont); m_szMargins = CSize(4,4); m_pWndEdit = NULL; // Default: use balloon tip. Note this will gracefully fail to normal tip // if commctl32 v6 is not installed. m_bUseBalloonTip = TRUE; } CPopupText::~CPopupText() { } ... ////////////////// // Show window with delay. No delay means show now. // Can also specify an auto-timeout to cancel tip. // void CPopupText::ShowDelayed(UINT msec, UINT msecTimeout, CWnd* pWndEdit) { m_msecTimeout = msecTimeout; m_pWndEdit = pWndEdit; if (msec==0) { OnTimer(1); // no delay: show it now if (msecTimeout>0) { SetTimer(TIMER_CANCEL, msecTimeout, NULL); // auto-cancel } } else { SetTimer(TIMER_SHOW, msec, NULL); // delay: set timer } } ////////////////// // Cancel: kill timer and hide window // void CPopupText::Cancel() { if (m_hWnd) { KillTimer(TIMER_SHOW); KillTimer(TIMER_CANCEL); LRESULT bRet = FALSE; if (m_bUseBalloonTip && m_pWndEdit) { bRet = m_pWndEdit->SendMessage(EM_HIDEBALLOONTIP); } if (!bRet) { ShowWindow(SW_HIDE); } } } ////////////////// // Timer popped: display myself and kill timer // void CPopupText::OnTimer(UINT nTimer) { if (nTimer==TIMER_SHOW) { LRESULT bRet = FALSE; if (m_bUseBalloonTip && m_pWndEdit) { // use balloon tip CString text; GetWindowText(text); EDITBALLOONTIP bt; bt.cbStruct = sizeof(bt); bt.pszText = text; bt.pszTitle = NULL; bt.ttiIcon = TTI_NONE; bRet = m_pWndEdit->SendMessage( EM_SHOWBALLOONTIP, 0, (LPARAM)&bt); } if (!bRet) { ShowWindow(SW_SHOWNA); Invalidate(); UpdateWindow(); } KillTimer(TIMER_SHOW); if (m_msecTimeout>0) { SetTimer(TIMER_CANCEL, m_msecTimeout, NULL); // auto-cancel } } else if (nTimer==TIMER_CANCEL) { Cancel(); } else { ASSERT(FALSE); // should never happen } }

Upgrade Update

Last month I reported some of my experiences upgrading to Visual Studio 2005. Well wouldn’t you know it, the minute I shipped the column I ran into a whole new category of compiler changes I have to tell you about: the Safe C/C++ libraries. Even though we’re well into the new millennium, it appears some of my code still calls such old-world routines as strcpy and fopen. Well cover me in tar and drop me in a barrel of feathers! strcpy and fopen are just soooo retro.

To help folks like me get off their duffs and embrace modernity, Visual Studio 2005 introduces something called the Safe C/C++ libraries. These libraries provide replacement functions for many of the old C runtime libraries (CRT) functions you know and love. And why are they bad? Because they make it so easy to write unsafe code:

char mybuf[64]; strcpy(mybuf,otherstring);

What happens if otherstring is longer than 64 characters? Oops. Buffer overruns not only cause bugs, they open your program to exploits by malicious crackers. And what about fopen—why is fopen unsafe? Because it opens files in shared mode, which can lead to collisions if two programs write to the same file at the same time.

Starting with Visual Studio 2005, the unsafe functions are officially deprecated. Which is a fancy way to say don’t use them! If you do, the compiler will politely complain. Instead, the CRT library in Visual C++ ®2005 provides alternatives. The Safe C library has functions like strcpy_s and fopen_s, safe versions to replace the old equivalents:

char buf[64]; strcpy_s(buf, _countof(buf), src);

The Safe C functions do the sort of bounds-checking your mother always insisted on but you were too lazy to write. Of course, you have to modify your code to use them—but if you’re writing in C++, life is easy. You can #define a special symbol:

#define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES 1

And the compiler will use C++ function-overloading to generate safe templates that override the offending functions. There are only a few situations where template overloading fails and you have to explicitly call a safe function. And if you just want the compiler to shut up, you can always write:

#define _CRT_SECURE_NO_DEPRECATE

This turns off all Safe-C/C++ warnings. It’s a quick way to make your code compile if you’re in a hurry or you have millions of lines of legacy code you can’t bother to fix. I don’t recommend it. I know, when the compiler doesn’t like your code, it’s cause to gripe. But safe warnings really are your friends. You ignore them at your own peril. The warnings make it really easy to write safer code. Instead of searching every nook and cranny in your code for places where something naughty may happen, the friendly Redmontonians have done it for you. All you have to do is compile, then fix the warnings one by one. For C++ programmers, _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES may be all you need.

I poked around in crtdefs.h, which is where all the _DEPRECATE warnings are defined, to see what else I could find. Visual Studio 2005 has lots of deprecated functions of all different kinds. For example, the compiler now complains of non-standard C names like strnicpy (it should be _strnicpy), and there are managed-heap deprecations for functions like _heapwalk. In all cases, the pattern is the same: there’s a macro the Redmondtonians use to declare the function and a symbol you can #define to suppress the warnings.

The Safe C/C++ libraries are, in my opinion, one of the best features in Visual Studio 2005. They’re not sexy, but they offer real benefits. They prod you to write better code, without forcing you to do so. To learn more about Safe C/C++, read Martyn Lovell’s excellent article in the May 2005 issue.

Triple-Click Update

In my April 2006 column, I showed how to implement a CTripleClick class to handle triple-clicks for your app. My implementation used the C standard library clock() function. But as reader Chris Depetris pointed out to me, the clock function is unreliable when there’s a background process that’s hogging the CPU. A better strategy is to use GetMessageTime. Right you are, Chris! GetMessageTime is more reliable. It reports the clock time of the current Windows message—that is, MSG::time for the current MSG. For the purposes of triple-click, that’s exactly what you want: the time when the mouse event occurred. You can find a revised version of Click3 in this month’s download.

As always, happy programming!

Send your questions and comments for Paul to cppqa@microsoft.com.

Paul DiLascia is a freelance software consultant and Web/UI designer-at-large. He is the author of Windows++: Writing Reusable Windows Code in C++ (Addison-Wesley, 1992). In his spare time, Paul develops PixieLib, an MFC class library available from his Web site, www.dilascia.com.