October 1998
Visual C++ 6.0 Brings Home a Full Bag of Tricks and Treats for MFC Developers |
MFC 6.0 is backward-compatible with existing applications without requiring re-compilation. You can plop the new MFC42 DLLs onto any machine and all the existing MFC apps will run just fine. Youll also be relieved to learn that MFC is now compatible with the new versions of SDK header files. |
This article assumes youre familiar with C++ and MFC. |
Paul DiLascia is the author of Windows++: Writing Reusable Windows Code in C++ (Addison-Wesley, 1992) and a freelance consultant and writer-at-large. He can be reached at askpd@pobox.com or http://www.pobox.com/~askpd..
|
The newest
release of Microsoft® Visual Studio® spans six CDs and several entire development systems: Visual C++®, Visual Basic®, Visual J++, Visual FoxPro®, and Visual InterDev, plus Visual SourceSafe. Visual Studio 6.0 also includes some interesting new tools like
Visual Modeler and Visual Studio Analyzer, new AppWizard projects, and distributed-database stuff for corporate coders. While it's great to get all these goodies, it's a brain strain for us poor developers trying to keep up. Not to mention your hard disk! I mean, installing Visual Studio is a little like letting Godzilla poop on your PC. Plop300 megabytes. And I chose what I thought was the tiniest possible C++ installation! If you want the works, including on-disk docs, be prepared to wheel in the external disk array.
Naturally, everyone wants to know what's new. Since I'm no Basic or FoxPro guru, I'll focus my attention on just one wee corner of the Visual Universe: MFC. Even so, there's much to cover, so this will be a survey, not a graduate course, guaranteed not to hurt your head. There's just one caveat, the usual new-product-review disclaimer: I'm using a preview release, so any discrepancies between my article and the final package must be due to last-minute changes, not any blunders on my part.
The Big Picture
While the version number for Visual Studio has taken a backwards plummet from 97 to 6.0, MFC's version, as defined by the preprocessor symbol _MFC_VER, has skipped a beat from 0x0421 to 0x0600. And yet while the symbolic version skipped a grade, the DLL got left back; instead of MFC60.DLL, Visual Studio 6.0 ships with the same old MFC42.DLL (and all the other MFC42 DLLs) as before. So MFC 6.0 could more accurately be called the 6.0 release of MFC 4.2. This may seem silly, but it's no trivial matter! It means MFC 6.0 is backward-compatible with existing apps without requiring recompilation. That is, you can plop the new MFC42 DLLs onto any machine and all the existing MFC apps will run just fine. You'll also be relieved to learn that MFC is now compatible with the newest versions of SDK header files (the ones with _WIN32_IE = 0x0400) such as propsht.h and commctrl.h. Previously, if you tried to use the newer files in MFC projects, you could experience some horrible nasties (see my discussion of property sheets below for details). How backward-compatible is the new release? I tested it with a handful of apps from my PixieLib library and they all worked fine, except for one display glitch I had with toolbars. This is hardly a thorough test, but at least it's a good sign. Of course, backward-compatibility doesn't come without a cost. There are precisely 6464 exported functions in MFC42.DLL (which you can discover yourself by looking at MFC42.DEF). To work with existing apps, the new 6.0 version of MFC42.DLL must have, and has, exactly the same 6464 functions, with exactly the same signatures and export ordinals. This limits change considerably. You won't find any new virtual functions or data members in MFC 6.0 classes! Adding a virtual function alters the structure of an object's vtable, while adding a data member alters its memory layouteither of which would suffice to send existing apps straight to the debug doctor. That said, the Redmondtonians made some minor changes to existing functions, and added some entirely new ones. The 6.0 version of MFC42.DLL has 6930 exports, which sophisticated mathematical calculations reveal is 466 more than in the previous version. Not to mention inline functions. So let's go exploring to see what's new.
Encapsulating Common Controls
This is not to disparage MFC; on the contrary, brainless is good! The best code is always brainless. MFC is fulfilling its primary role in life: to convert C-speak into C++-speak. It's when they add stuff that I begin to worry.
In any case, the point is that MFC 6.0 provides nothing new in the way of common control functionality that you can't already access right now with MFC 4.2 using SendMessage. To describe what's new for common controls in MFC 6.0 is really just to describe what's new in common controls, and technically it doesn't belong in this article. But since it's new stuff many of you may not have seen before, why not go over some of the fun features? Consider it a bonus for reading this far. (If you haven't already done so, check out Mark Finocchio's ControlSpy series in the July and September 1998 issues of MSJ.)
Common Control Overview
|
![]() |
Figure 2 Extended Combobox |
|
![]() |
Figure 3 Date Time Control |
you can achieve the same result more simply by writing:
CIPAddressCtrl is a new class
for editing an IP addressyou know, those four numbers with dots between them (see Figure 4). This class is derived from CWnd, not from CEdit.
|
![]() |
Figure 4 IP Address Control |
I don't personally expect to use work areas, but another new feature, virtual list controls, is pretty cool. A virtual list control lets you support a humongous number of itemsup to a DWORD's range worthwithout having to load them all into memory. To create a virtual list control, you set the LVS_OWNDERDATA style. The list control then sends you LVN_GETDISPINFO notifications when it needs display info about an item, such as the item's image, text, and state info. The list control doesn't store any of this information; rather, it calls you every time it needs it. It's up to you to do caching (highly recommended), but the list control helps by giving you an LVN_ODCACHEHINT notification with the following WM_NOTIFY struct:
LVN_ODCACHEHINT warns you that the list control is about to request display info about a range of items from iFrom to iTofor example, because the user has scrolled. LVN_ODCACHEHINT is useful in situations where it's more efficient to retrieve a bunch of items in one fell swoop than one-by-one, like in a database app where you might have to launch a SQL query to retrieve records. It may be almost as fast to get 15 records as to get one, in which case you can use LVN_ODCACHEHINT. There are also LVN_ODFINDITEM and LVN_ODSTATECHANGED notifications to let you know when the list control wants
to find an item, or when the state of one or more items has changedperhaps because the user clicked the mouse on an item.
CMonthCalCtrl is a calendar control that's exactly the same as the dropdown calendar in the date/time control (see Figure 3), only it's always displayed. CProgressCtrl has a new SetRange32 function (PBM_SETRANGE32) to support 32-bit ranges. I scratched my head when I saw this one; it seems to me if you need more than 16 bits to show progress, you must not be making any! I mean, how many pixels do you have? Curiously, MFC still has no wrapper for the much more useful PBM_SETBARCOLOR message. If you want to change the color of your progress bar, you'll have to call SendMessage. CReBar and CRebarCtrl wrap the rebar (also known as coolbar) control. (I showed you how to do this in the August and October 1997 issues of MSJ, with my CCoolBar class.) A rebar is an Internet Explorer 4.0-style toolbar within which you can create other child toolbars and windows. Users can slide these windows around inside the rebar. The rebar takes care of all the sizing and sliding; all you have to do is create and manage the child windows. Following the normal MFC paradigm, there are two classes: CRebarCtrl is the bare API wrapper around ReBarWindow32; CReBar is the CToolBar-derived class that adds the usual extra framework stuffcommand routing, automatic sizing, and so on. AppWizard has a new option to let you choose a normal toolbar or rebar. Figure 6 shows the relevant sections of code generated when you select the rebar option. You just create the rebar, then call AddBar to add your child windowsin this case a toolbar and a dialog bar (see Figure 7). |
![]() |
Figure 7 Rebar Child Bars |
Well, the first problem is that the app icon is the standard 32 X 32 pixels, which doesn't fit in the status bar as you can see from the top half of Figure 8. Since the status bar doesn't stretch the icon, I have to do it myself. The easiest way is to use ::LoadImage.
This fixed the size problem, but the other snag turned out to be slightly more painful. By default, MFC creates a status bar with SBARS_SIZEGRIP, which draws the little box with the diagonal grippers. And unfortunately, Windows® doesn't use your background color for the gripper; all it knows is grey. Sigh.
|
![]() |
Figure 8 Status Bar Tricks |
That is, turn off SBARS_SIZEGRIP. But no matter where I placed this line of code, the gripper refused to disappear! Apparently, the status bar only looks at the style when you first create the window.
But whatever style you pass to Create, MFC ignores your flags and automatically sets SBARS_SIZEGRIP if your frame window has a thick frame:
Of course, a normal frame window has WS_THICKFRAME and is sizable. So the only way to get rid of the pesky size gripper is to derive a new class from CStatusBar and override PreCreateWindow to turn off the gripper style.
It's not a big deal; it's just a pain in the typing fingers. The bottom half of Figure 8 shows the final screen shot with the status bar fixes for the icon and size gripper.
CTabCtrl has a new SetMinTabWidth function (TCM_SETMINTABWIDTH) that does what its name implies. It also has a couple of new extended styles: TCS_EX_FLATSEPARATORS for drawing separators in flat-button style tab controls, and TCS_EX_ REGISTERDROP, which makes the tab control call you with a TCN_GETOBJECT notification when the user drags an object over one of the tab items. Perhaps the strangest new function/message of all the new Internet Explorer 4.0 common controls is CTabCtrl::SetItemExtra (TCM_SETITEMEXTRA). This feature lets you allocate extra bytes for your own application-defined data for the tabs in a tab control. This seems strange because the TCITEM structure used for tab info already has an lParam member you can use, and the normal thing to do in Windows when you need more than an LPARAM of data is to make lParam point to a structure that has more stuff. But for some reasonperhaps some particular app demanded itthe Redmondtonians decided that the extra data should be stored inline for tab controls. CToolBar , my favorite love-hate class, now works correctly with flat-style toolbars, including a fix for the size problem I described in the August 1998 issue of MSJ. Reading the source code, I discovered the source of an elusive problem I'd been having while trying to get a zero-border toolbar (see Figure 9). My toolbar would start out with zero-size borders, but then the border would mysteriously grow in certain circumstances that were not clear. |
![]() |
Figure 9 Zero-border toolbar |
MFC sets TBSTYLE_TRANSPARENT and TBSTYLE_FLAT before passing the message to the toolbar, then restores the original style. This produces consistent behavior that depends only on the version of comctl32.dll, not what style bits are set. But if, like me, you've already written code to correct the problem, your toolbars will come out misaligned when you install the new MFC42.DLL. This is the only breakage I discovered in MFC 6.0 in my limited testing. Also, since
the fix is in CToolBar, not CToolBarCtrl, you must make your own provisions if you're using CToolBarCtrl as your toolbar class.
CToolBarCtrl , the "pure" wrapper for ToolbarWindow32 (as opposed to CToolBar, which does all the extra MFC stuff), has wrapper functions for most of the new toolbar messages in commctrl.h. You can use Get/SetHotImageList (TB_GET/SETHOTIMAGELIST) to implement Internet Explorer 4.0-style toolbars with black and white buttons that turn color when you move the mouse over them. You can get and set the "hot" button for mouse fly-bys. There's a HitTest (TB_HITTEST) function to find out which button lies under a point. And there's Get/SetButtonInfo to get or set all the button info at once using a TBBUTTON struct. There's also SetDrawTextFlags (TB_SETDRAWTEXTFLAGS) to control which DrawText flags (DT_XXX) the toolbar uses to draw button text, so now you can make your button text left-aligned instead of centered, control underlining, or whatever. |
![]() |
Figure 10 Insertion mark |
The idea was good, but when I ran it, the assertion bombed. (Good thing I used ASSERT!) A little investigation revealed that MFC doesn't actually create the tooltip control until it needs one. This happens in CWnd::FilterToolTipMessage, a special function called from CWnd::PreTranslateMessage.
Reading the code revealed yet another problem: FilterToolTipMessage doesn't create just one single CToolTipCtrl for the life of your app; if the window requiring the tooltip has a different owner than the current tooltip, MFC destroys the tooltip and creates a new one with the new owner. So, it's not enough to set the color once; you have to set it every time MFC recreates the tooltip control. The natural thing to do would be to override CWnd::FilterToolTipMessage, but this function isn't virtual. Arrgh! There is hope, however. CWnd::PreTranslateMessage doesn't call FilterToolTipMessage directly, but through a function pointer stored in the module state.
When you enable tooltips by calling EnableTooltips (or create your toolbar with the CBRS_TOOLTIPS style), MFC points m_pfnFilterToolTipMessage in your module's state data to CWnd::_FilterToolTipMessage, a static function that calls CWnd::FilterToolTipMessage, which creates the tooltip, calls OnToolHitTest, and so on. So one way to get the tooltip control would be to write your own static _FilterToolTipMessage function and wire it into AFX_MODULE_
STATE::m_pfnFilterToolTipMessage, making sure to call the CWnd version when you're done. But that's really, really yucky. As the Redmontonians always admonish: don't rely on undocumented internals. And besides, there's an easier way: all you have to do is intercept TTN_NEEDTEXT.
I'm relying on the fact that MFC's tooltip implementation uses LPSTR_TEXTCALLBACK, so the tooltip sends TTN_
NEEDTEXT whenever it needs text. My override uses the opportunity to check for a new tooltip control, and if there is one, set its color, margin, maximum text width, and time displayed before passing the notification along to CFrameWnd. The only trick is that you must handle both TTN_NEEDTEXTW and TTN_NEEDTEXTA if you want your app to work in Unicode and ASCII modes, and you have to use ON_NOTIFY_EX_
RANGE to trap all TTN_NEEDTEXT notifications regardless of the control ID. Figure 11 shows my final lime tooltip with wide borders and text wrap.
|
![]() |
Figure 11 Fancy Tooltip |
Wizard97 Property Sheets
New PSH_ flags control whether and how to use the new fields, which lets you specify the bitmaps as either handles (HBITMAP) or resource name/IDs. PROPSHEETPAGE also has new fields for the header title and subtitle strings.
Windows displays the new strings in the header area.
All this is pretty straightforward, but the expansion of PROPSHEETHEADER and PROPSHEETPAGE caused serious problems for MFC developers trying to compile their apps with the new Internet Explorer 4.0 versions of prsht.h with these expanded structures. Why? Because MFC's CPropertySheet and CPropertyPage have inline instances of these structures embedded within them.
When PROPSHEETPAGE and PROPSHEETHEADER grew, they altered the layout of CPropertyPage and CPropertySheet. You might compile your app with the new property sheet definitions, but MFC42.DLL still has its original idea of how big the structures were. Crash.
To support the newer Wizard97- enhanced structures in prsht.h, MFC 6.0 requires some code gyrations. To retain backward-compatibility, the Redmondtonians had to replicate the old PROPSHEETPAGE and PROPSHEETHEADER structs as AFX_OLDPROPSHEETPAGE and AFX_OLDPROPSHEETHEADER, which MFC 6.0 uses in CPropertyPage and CPropertySheet, so these classes still have exactly the same layout they always did. To let you access the new Wizard97 features, MFC provides two new classes, CPropertyPageEx and CPropertySheetEx, which you must use if you want watermarks or headers.
In the case of CPropertyPageEx, MFC adds the header title and subtitle as CStrings, and uses them internally to fill a PROPSHEETPAGE with the right info. With CPropertySheetEx, MFC instantiates the whole PROPSHEETHEADER, which means that if the Redmondtonians ever decide to increase the size of PROPSHEETHEADER yet again, MFC will need another Ex at the end of the class name. This is not something to write home about. In theory, there's no reason MFC should have to create new classes just to support new property sheet features. Practice is a different story.
There are a couple of lessons here. First, while inline objects are definitely more convenient (you don't have to worry about allocation and cleanup), sometimes it's wiser to allocate members from the heap and store them as pointers, especially if you think those structures may grow and you don't want a size increase to require global recompilation. If the original CPropertySheet and CPropertyPage had used pointers instead of inline structs, there'd be no need to introduce new Ex classes nowexcept for backward OS compatability. The other lesson is that while C++ goes a long way toward the OO ideal, it's still C and can be frustratingly confining at times. All too often, a small change somewhere requires recompiling the whole universe, or else working around C++ with something like a new Ex class. For more details on how to use CPropertySheetEx and CPropertyPageEx, take a look at the Wizard97 sample app.
Docs That Bind
|
![]() |
Figure 13 Active Document Containment |
|
![]() |
Figure 14 MFCBIND |
|
![]() |
Figure 15 Viewing a Spreadsheet in MFCBIND |
CHtmlView: Browser In a Box
MFC 6.0 has a new MFCIE sample app, a 1400-line program that uses CHtmlView to implement a low-functional browser. Figure 16 shows it open to the MSJ home page. Note the MFC logo instead of the e-globe in the upper-right corner. It spins while loading a new pagecool! Of course, MFCIE is nothing compared to Internet Explorer or Netscape Navigator, but it's amazing how much it accomplishes with very little coding. If you want to add basic browsing to your MFC app, CHtmlView makes it easy by translating the COM-speak of IWebBrowser2 into MFC doc/view-speak. If you can deal with views, you can use CHtmlView. Visual Studio 6.0 also has a new HTML resource type, so you can compile HTML pages right into your app.
|
![]() |
Figure 16 MFCIE |
Likewise, when Internet Explorer has finished loading the page, the Web browser fires a DocumentComplete event that CHtmlView translates into a call to the virtual function OnDocumentComplete.
OnDownloadBegin might be a better place to start the animation than OnBeforeNavigate2 because the user might cancel. And the direct calls and casts to CMainFrame from the view could be better implemented by having the frame give the view a pointer to the animation. But you can see how CHtmlView converts IWebBrowser2 ActiveX events, which are defined by the DWebBrowserEvents2 interface, into virtual function calls. All you have to do is implement the virtual function to handle the event. Figure 19 shows the Web browser events and their CHtmlView counterparts.
Other Goodies
This general-purpose function converts COM-style SCODEs into MFC exceptions. You can use it wherever you want to avoid tedious error checking:
Of course, if you use AfxCheckError, be prepared to handle the exceptions!
There's a new AfxDumpStack function that dumps a stack image as TRACE output or to the clipboard or debugger. It works in both release and debug builds, but unless your program has symbolic info, all you get is a bunch of hex gobbleygook. Remember, as John Robbins (also known as Mr. Bugslayer) has pointed out, there's no reason you can't use symbolic info in a release build. AppWizard has several new options. It can generate a non-doc/view app, a console app, or an Explorer-style app (a splitter view with list view in the left pane). You can also select whether you want the old-style CToolBar docking/floating toolbar or the newfangled Internet Explorer-style rebar. (Personally, I'd go for the rebar.) There's a new tech note, TN071, that explains how MFC implements IOleCommandTarget, one of the COM interfaces used in active documents. IOleCommandTarget is used to implement standard commands such as Open, Save, Print, Cut, Copy, Paste, and so on, using IDs instead of the dreadly IDispatch. IOleCommandTarget has just two methods: QueryStatus to find out the status of commands, and Exec to execute a command. IOleCommandTarget uses command IDs (UINTs) to identify commands. The SDK #include file docobj.h defines a bunch of built-in command IDs like OLECMDID_OPEN, OLECMDID_SAVE, OLECMDID_PRINT, and so on. In typical MFC style, MFC uses macros to generate dispatch tables that map these built-in command IDs to MFC command IDs.
This maps the OLE command IDs to your app's WM_COMMAND IDs. When the container calls IOleCommandTarget::Exec to execute a command, MFC automatically translates it into the corresponding MFC ID (for example, OLECMD_COPY into ID_EDIT_COPY).
When MFC gets an IOleCommandTarget::QueryStatus call, it creates a special COleCmdUI object and routes it around the system, so all you have to do to implement QueryStatus is supply the OLECMD map as shown previously, then do your normal ON_UPDATE_COMMAND_UI thing. This is really cool: the MFC maps convert IOleCommand speak into normal MFC speak. All you have to do is create the table (map). This is definitely good coding! Of course, this stuff exists in MFC 4.2, but now you can read about it in TN071.
Conclusion
From the October 1998 issue of Microsoft Systems Journal.
|