Click Here to Install Silverlight*
United StatesChange|All Microsoft Sites
MSDN
|Developer Centers|Library|Downloads|Code Center|Subscriptions|MSDN Worldwide
Search for


Advanced Search
MSDN Home > MSJ > October 1998
October 1998

Microsoft Systems Journal Homepage

Visual C++ 6.0 Brings Home a Full Bag of Tricks and Treats for MFC Developers

Paul DiLascia

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. You’ll also be relieved to learn that MFC is now compatible with the new versions of SDK header files.

This article assumes you’re 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. Plop—300 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

Here's the executive bullet list of what's new in MFC:
  • Improved support for Microsoft Internet Explorer 4.0 common controls, both new classes and additions to old ones Support for Wizard97-style property sheets
  • Support for active document containment
  • A new HTML view that encapsulates a Web browser
  • Several new AppWizard options
  • Bug fixes, new tech notes, and sundry other minor changes

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 layout—either 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

In case you've been living on another planet for the last year, or just fell behind in your MSJ reading, Microsoft shipped a new version of comctl32.dll along with Internet Explorer 4.0. comctl32.dll is the DLL that implements toolbars, list views, header controls, and other common controls. New releases of comctl32.dll have caused much consternation among developers trying to chase the ever-moving Win32
® platform, which seems to shift every time Microsoft lets a new Internet Explorer out the hatch. (I've heard rumors that the folks at Microsoft have promised not to be bad any more, but don't hold me to it.) If you've been suffering from common control blues, don't feel bad. Even the Redmondtonians can't keep up with themselves: MFC has also been behind the rapidly advancing common controls. But now with 6.0, MFC has caught up with the OS folks, at least for now.
Figure 1 shows MFC classes that have been enhanced or added to support common controls. Much of MFC 6.0 is devoted to common controls; MFC has seven control modules now—winctrl1.cpp through winctrl7.cpp—up from four in the previous release. Of course, all it means to say that MFC supports common controls is that it now contains a bunch more "brainless" wrapper functions—inline functions that do no more than call SendMessage—for the corresponding control messages. MFC abounds with functions like this:
 inline BOOL
 CToolBarCtrl::SetIndent(int i)
 {
   ASSERT(::IsWindow(m_hWnd));
   return (BOOL)::SendMessage(m_hWnd,
     TB_SETINDENT, i, 0L);
 }
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

Following is a list of Internet Explorer 4.0 common control features and classes, with some of my own random observations. Once again, the functionality is not new, only the ability to access them through MFC. Feel free to skim.

CComboBoxEx wraps the new Internet Explorer 4.0 extended combobox. An extended combobox lets you have images next to items. Internet Explorer 4.0 uses an extended combobox for its recent URL list (see Figure 2). The newfangled combobox also lets you do drag and drop, and you can use the LPSTR_TEXTCALLBACK trick (as in list views and other common controls) to have the control call you with CBEN_GETDISPINFO notifications when it needs display data. CComboBoxEx makes OWNERDRAW comboboxes much easier.
Figure 2 Extended Combobox
Figure 2 Extended Combobox


CDateTimeCtrl supports the date/time control (shown in Figure 2), which lets users pick a date or time.

Figure 3 Date Time Control
Figure 3 Date Time Control


CHeaderCtrl has new functions called CreateDragImage and SetHotDivider for doing drag and drop in header controls. CreateDragImage creates the transparent image you need to drag. SetHotDivider highlights a divider, and has overloaded versions that accept either the index of the divider or mouse coordinates.
CImageList has new functions that make it easier to copy image lists. For example, there's a new Create function that takes a pointer to another image list, so instead of writing

 CImageList* pil =
    pListCtrl->GetImageList();
 HIMAGELIST hil = pil->Detach();
 hil = ImageList_Duplicate(hil);
 m_myImageList.Attach(hil);

you can achieve the same result more simply by writing:
 m_myImageList.Create(
    pListCtrl->GetImageList()); 
CIPAddressCtrl is a new class for editing an IP address—you 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
Figure 4 IP Address Control


CListCtrl has several new functions, including Get/SetWorkAreas (TVM_GET/SETWORKAREAS), which let you manipulate work areas. A work area is a rectangular region within which list items are arranged. Figure 5 shows a list view with four work areas. SetColumnOrderArray is a handy function that lets you change the display order of columns without reconstituting all your data. There are also new GetFirstSelectedItemPosition and GetNextSelectedItemPosition functions that let you navigate the selected list items using MFC-style POSITIONs. For example:

 CListCtrl& lc = m_myListCtrl;
 POSITION pos =
     lc.GetFirstSelectedItemPosition();
 if (pos == NULL)
     // no items selected
 else {
     while (pos) {
         int nItem =
             lc.GetNextSelectedItem(pos);
         // nItem is index of next selected
         // item
     }
 }
Figure 5 Work Areas
Figure 5 Work Areas

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 items—up to a DWORD's range worth—without 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:

 struct NMLVCACHEHINT {
   NMHDR hdr;
   int iFrom;
   int iTo;
 };
LVN_ODCACHEHINT warns you that the list control is about to request display info about a range of items from iFrom to iTo—for 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 changed—perhaps 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 stuff—command 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 windows—in this case a toolbar and a dialog bar (see Figure 7).
Figure 7 Rebar Child Bars
Figure 7 Rebar Child Bars


CsliderCtrl supports "buddy" controls—up to two companion windows that will be lined up horizontally or vertically with the slider, similar to spin control buddy windows.
CSpinButtonCtrl has the same 32-bit range enhancement as CProgressCtrl.
CStatusBarCtrl has a new function SetBkColor (SB_SETBKCOLOR) to set the background color. There's also SetIcon to set the icon for each pane. When I tried using these functions, I stumbled into some peculiarities. Here's how I naively tried to do it:

 // in CMainFrame::OnCreate
 CStatusBarCtrl& sb =
     m_wndStatusBar.GetStatusBarCtrl();
 sb.SetBkColor(RGB(128,255,128));
 sb.SetIcon(0,
     AfxGetApp()->LoadIcon(IDR_MAINFRAME));
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.
 sb.SetIcon(0,
     (HICON)::LoadImage(AfxGetResourceHandle(),
     MAKEINTRESOURCE(IDR_MAINFRAME),
     IMAGE_ICON,
     16,16, // <=== 16 x 16 small icon
     0)
 );
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
Figure 8 Status Bar Tricks


If you don't want your colorized status bar to look cruddy, you'll either have to do some non-client painting or, much easier, get rid of the size gripper. Unfortunately, this too turns out to be more difficult than you'd think. My first attempt was the obvious:

 sb.ModifyStyle(SBARS_SIZEGRIP, 0);
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:
 // MFC Source
 BOOL CStatusBar::CreateEx(...)
 {
 •
 •
 •
     if (pParentWnd->GetStyle() &
         WS_THICKFRAME)
         dwStyle |=
             SBARS_SIZEGRIP;
 •
 •
 •
 }
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.
 BOOL CMyStatusBar::PreCreateWindow(
     CREATESTRUCT& cs)
 {
     BOOL bRet =
         CStatusBar::PreCreateWindow(cs);
     cs.style &= ~SBARS_SIZEGRIP;
     // no gripper!
     return bRet;
 }
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 reason—perhaps some particular app demanded it—the 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
Figure 9 Zero-border toolbar


It turns out there's a bug in comctl32.dll, so you must set TBSTYLE_FLAT and TBSTYLE_TRANSPARENT before changing the size of the bitmap or buttons if you want to have zero-size borders. The new CToolBar fixes this with TB_SETBUTTONSIZE and TB_SETBITMAPSIZE handlers to fake out comctl32.

 // edited for simplification
 LRESULT CToolBar::OnSetBitmapSize(...)
 {
     dwStlye = GetStyle();
     ModifyStyle(0,
                TBSTYLE_TRANSPARENT|TBSTYLE_FLAT);
     LRESULT lr = Default();
     SetWindowLong(m_hWnd, GWL_STYLE,
                   dwStyle);
     return lr;
 }
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
Figure 10 Insertion mark


CTreeView has, among other things, a new "insertion mark" concept, which is a horizontal line you can use to indicate where something might be inserted. Figure 10 shows what it looks like. Get/SetInsertMark lets you manipulate the mark. SetInsertMarkColor lets you change its color.
CToolTipCtrl has new functions to get and set tooltip background and text colors, maximum width, and tooltip delay times. You can change the margins too, but MFC provides no functions for "tracking tooltips" (tooltips that move with the mouse), so you'll have to send TTM_TRACKACTIVATE and TTM_TRACKPOSITION yourself if you want tracking tooltips. Don't forget the TTF_TRACK flag in the TOOLINFO structure.
Many of the controls already discussed have new Get/SetToolTip functions, including CTreeCtrl, CSliderCtrl, CTabCtrl, and CReBarCtrl. However, they missed the one for CListCtrl, so you have to SendMessage(LVM_SET/GETTOOLTIPS) yourself. You don't need any of these functions if you're using OnToolHitTest, MFC's easy road to tooltips (see my March 1997 column), but the new functions are there in case you need to bypass MFC and roll your own tooltips.
Just for fun, I decided to try changing the tooltip color in one of my MFC apps. Oops—it was much more difficult than I expected. The problem is, where's the tooltip control? MFC does such a great job of implementing tooltips invisibly, you can't find the tooltip control when you need it! It's tucked away in a state variable called _AFX_THREAD_ STATE::m_pToolTip.
My first attempt at setting the color was to add the following lines to my main frame's OnCreate handler:

 // in CMainFrame::OnCreate
 CToolTipCtrl* ptt =
     AfxGetThreadState()
        ->m_pToolTip;
 ASSERT(ptt);
 ptt->SetTipBkColor(...)
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.
 BOOL CWnd::PreTranslateMessage(MSG* pMsg)
 {
     // handle tooltip
     // messages
     AFX_MODULE_STATE* pms
         = _AFX_CMDTARGET_GETSTATE();
     if (pms->m_pfnFilterToolTipMessage != NULL)
         (*pms->m_pfnFilterToolTipMessage)(pMsg, this);

     // no default processing
     return FALSE;
 }
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.
 BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
     ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF,
                        OnToolTipText)
     ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF,
                        OnToolTipText)
 END_MESSAGE_MAP()

 BOOL CMainFrame::OnToolTipText(...)
 {
     static CToolTipCtrl* pToolTip = NULL;
     CToolTipCtrl* ptt =
         AfxGetThreadState()->m_pToolTip;
     if (ptt != pToolTip) { // new tooltip
         // 4 pixel margin
         CRect rc(4,4,4,4);
         ptt->SetMargin(&rc);
         ptt->SetTipBkColor(
            RGB(128,255,128));
         ptt->SetMaxTipWidth(128);
         ptt->SetDelayTime(TTDT_AUTOPOP, SHRT_MAX);
         pToolTip = ptt;
     }
     return CFrameWnd::OnToolTipText(...);
 }
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
Figure 11 Fancy Tooltip

 

Wizard97 Property Sheets

Another new-for-Internet-Explorer-4.0 feature based on Win32 is Wizard97 property sheets. The name suggests a more optimistic ship date for Windows 98, perhaps because it's pretty modest as features go. Wizard97 property sheets are normal wizard-style property sheets with the added features of headers and watermarks. (A watermark is just a highbrow name for a background bitmap.) The header is also a background bitmap, but it spans the top of the property page, not the whole page. Figure 12 shows the four steps in the MFC sample program Wizard97, with bitmap headers and watermarks controled on a per-page basis.
To support headers and watermarks, the PROPSHEETHEADER structure in prsht.h has sprouted three new members.
 struct PROPSHEETHEADER {
 // original stuff
 •
 •
 •
 #if (_WIN32_IE >= 0x0400)
     union {
         HBITMAP hbmWatermark;
         LPCTSTR pszbmWatermark;
     };
     HPALETTE hplWatermark;
     union {
         HBITMAP hbmHeader;
         LPCSTR pszbmHeader;
     };
 #endif
 };
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.
 struct PROPSHEETPAGE {
 // original stuff
 •
 •
 •
 #if (_WIN32_IE >= 0x0400)
     LPCTSTR pszHeaderTitle;
     LPCTSTR pszHeaderSubTitle;
 #endif
 };
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.
 class CPropertyPage : public CDialog {
 •
 •
 •
     PROPSHEETPAGE m_psp;
     CString m_strCaption;
 };

 class CPropertySheet : public CWnd {
 •
 •
 •
     PROPSHEETHEADER m_psh;
     CPtrArray m_pages;
     CString m_strCaption;
 };
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.
 class CPropertyPageEx : public CPropertyPage {
     CString m_strHeaderTitle;     // new
     CString m_strHeaderSubTitle;  // new
 };

 class CPropertySheetEx : public CPropertySheet {
     PROPSHEETHEADER m_psh;        // new
 };
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 now—except 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

Moving right along, the next major addition to MFC is support for active document containment. In case you're not a Webhead, active documents are the Microsoft developers' answer to the question "How do you use a Web browser like Internet Explorer to look at non-HTML files like word-processing docs and spreadsheets?" Figure 13 shows how. Just implement all those interfaces and you have an active document.
Figure 13  Active Document Containment
Figure 13  Active Document Containment


Active documents use many of the OLE interfaces, but an active document is conceptually quite different from an OLE object. In the OLE model, you have a document within another document. For example, your word-processing file might have a spreadsheet inside it. The entire thing forms a compound document with the container doc owning the storage for both.
Active documents, on the other hand, are designed to let a foreign application (typically a Web browser or binder app) display and edit the documents in-place, within the foreign app's own frame window. In effect, it's the in-place editing part of OLE without the container doc. The active document remains an independent thing with its own storage. As with OLE, the active document is implemented as a server app (such as Microsoft Excel or Word) that exposes the active document interfaces in Figure 13. The app with the frame window is called the active document container, and implements the container-side callback interfaces for the document/server.
MFC 4.2 supported active document servers with CDocObjectServer and CDocObjectServerItem; MFC 6.0 now adds COleDocObjectItem to support active document containment. The best way to understand how it works is to look at the new MFCBIND sample app. This 2000-line app implements a binder app like the Microsoft Office Binder. Like its big brother, the MFC binder lets you "bind" several active documents from different applications into a single entity.
For example, suppose you're the head of the multibillion-dollar Acme Corp. and you're working on the latest megamerger called Project M. This project includes documents, spreadsheets, slide presentations, and some database reports. Using the binder, you can lump them all into a single entity because, being the head of a multibillion-dollar corporation, you don't understand how to copy multiple files.

Figure 14 MFCBIND
Figure 14 MFCBIND


When you run MFCBIND, you get a blank screen (see Figure 14). You can use the Section|Add command to add documents to your project. Figure 15 shows MFCBIND with a Word document and a spreadsheet, complete with Mr. Bob the Assistant. The left-hand pane in MFCBIND is an owner-draw CListBox that displays an icon for each active document in the binder. When you select one of the icons, its document appears embedded in the right pane via active document containment.

Figure 15 Viewing a Spreadsheet in MFCBIND
Figure 15 Viewing a Spreadsheet in MFCBIND

CHtmlView: Browser In a Box

If you want to add Web browsing to your app, then you'll love CHtmlView. This CFormView-derived class implements an HTML view—that is, a view for dynamic HTML docs, also known as a Web browser. As with the new common controls, MFC doesn't do any of the work; it merely wraps Internet Explorer into an MFC-style view using IWebBrowser2, which is the current replacement for IWebBrowser.
IWebBrowser2 is a COM interface that lets a browser expose itself through COM so another app can embed it in its own window. For example, IWebBrowser2 has functions Navigate (and Navigate2), GoBack, GoForward, GoHome, and GoSearch. These jump to another URL, the previous URL, the next URL, the home URL, or a search URL, respectively. CHtmlView has equivalent functions with the same names (CHtmlView::Navigate and so on). In general, the majority of functions in CHtmlView are thin wrappers around IWebBrowser2 that do little more than convert C++ objects such as CStrings into COM-style VARIANTs or BSTRs as necessary. Here's a typical function from CHtmlView:
 // m_pBrowserApp is pointer to IWebBrowser2
 CString CHtmlView::GetFullName() const
 {
     ASSERT(m_pBrowserApp != NULL);
     BSTR bstr;
     m_pBrowserApp->get_FullName(&bstr);
     CString retVal(bstr);
     return retVal;
 }
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 page—cool! 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
Figure 16 MFCIE


In theory, CHtmlView could be used to embed any browser that supports IWebBrowser2. In practice, CHtmlView:: Create has the class ID CLSID_WebBrowser, which is the Microsoft Web browser (Internet Explorer), hardwired into it. However, if you derive a new class and override the Create function, you should be able to use any browser that implements IWebBrowser2.
CHtmlView is more powerful than it might seem at first. The Redmondtonians should have called it CWebBrowser because you can use it to display many other kinds of files besides HTML, including GIF, JPEG, and AVI files. In fact, since Internet Explorer is an active document container, you can use CHtmlView to display any active document! By adding CHtmlView to your app, you gain the ability to view any kind of file Internet Explorer can view: Web pages, graphics, docs, spreadsheets, folders, presentations, whatever. If you think of the Windows 98 Active Desktop
model where the browser provides a single uniform view for all things on your PC, intranet or Web, then CHtmlView lets you embed this view in your app with minimal effort.
The technology still has a few rough edges, though. When I tried to open a sample spreadsheet, I got a Microsoft Excel warning that seemed to thoroughly confuse MFCIE. It displayed a "file is busy" dialog, and when I answered the Microsoft Excel dialog, then clicked the Retry button in the MFCIE dialog, the spreadsheet opened fine. The other strangeness is that when the browser launches Microsoft Excel to view the spreadsheet, the app starts up, opens the file as if you had launched Microsoft Excel, then the file quickly disappears from Microsoft Excel and reappears in MFCIE. It leaves Microsoft Excel there with an empty window. When you Go Back, Microsoft Excel remains open. It seems like the browser should launch Microsoft Excel invisibly and close it when it's no longer needed. Incidentally, the same behavior occurs with Internet Explorer 4.0, so don't blame MFC. The problem is in the active document handshaking (or lack thereof) between Internet Explorer and Microsoft Excel.
MFCIE also exhibits some other tricks, as well as some code in need of tables. Figure 17 shows what I mean. Figure 18 shows how to write the same code much more efficiently using a table.
Now the good stuff: MFCIE shows how to use CReBar, how to have black and white toolbar buttons that turn color when the mouse flies over them, and how to add an animation window like the spinning e-globe in Internet Explorer 4.0. The logo is implemented as a CAnimationCtrl. When the user navigates to a new URL using any of the Navigate, GoBack, GoSearch, or Refresh functions, MFC translates the IWebBrowser2 BeforeNavigate2 event into a virtual function call to CHtmlView:: OnBeforeNavigate2. MFCIE's implementation starts the animation.

 // in mfcievw.cpp
 void CMfcieView::OnBeforeNavigate2(...)
 {
     ((CMainFrame*)GetParentFrame())->StartAnimation();
 }

 // in mainfrm.cpp
 void CMainFrame::StartAnimation()
 {
     // Start the animation.  This is called when the
     // browser begins to navigate to a new location
     m_wndAnimate.Play(0, -1, -1);
 }
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.
 // in mfcievw.cpp
 void CMfcieView::OnDocumentComplete(LPCTSTR lpszUrl)
 {
     // make sure the main frame has the new URL. This
     // call also stops the animation
     ((CMainFrame*)GetParentFrame())->SetAddress(lpszUrl);
 }

 // in mainfrm.cpp
 void CMainFrame::SetAddress(LPCTSTR lpszUrl)
 {
     m_wndAddress.SetWindowText(lpszUrl);
     m_wndAnimate.Stop();
     m_wndAnimate.Seek(0);
 }
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

For corporate coders in the audience, COleDBRecordView is a new class that implements a CFormView directly connected to a CRowset. It uses special dialog data exchange (DDX) functions to exchange data between the database and form fields. The implementation uses built-in MFC command IDs for moving to the first, last, next, or previous record. All you have to do is implement a virtual function, OnGetRowset, to supply the CRowset. There is even a new AppWizard section that lets you generate an app that uses COleDBRecordView.
AfxCheckError is a really handy new OLE-error check function.
  void AFXAPI AfxCheckError(SCODE sc)
 {
     if (FAILED(sc)) {
         if (sc == E_OUTOFMEMORY)
             AfxThrowMemoryException();
         else
             AfxThrowOleException(sc);
     }
 }
This general-purpose function converts COM-style SCODEs into MFC exceptions. You can use it wherever you want to avoid tedious error checking:
  AfxCheckError(pPersistStream->Save(pStream, TRUE));
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.
  BEGIN_OLECMD_MAP(CMyServerView, CView)
     ON_OLECMD(NULL, OLECMDID_COPY, ID_EDIT_COPY)
     ON_OLECMD(NULL, OLECMDID_CUT,  ID_EDIT_CUT)
     ON_OLECMD(NULL, OLECMDID_PASTE,ID_EDIT_PASTE)
     ON_OLECMD(NULL, OLECMDID_PRINT,ID_FILE_PRINT)
 END_OLECMD_MAP()
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

Now that I've shown you what's new, it's time to step back and wax philosophical. Many MFC watchers have been wondering what the future holds for MFC. Has MFC passed its wonder years and graduated to retirement? Will the Redmondtonians continue enhancing it? Or have they put MFC into maintenance mode, the code equivalent of going out to pasture? The official answer is: MFC will continue to be developed. But it's clear that despite its quantum version number leap, MFC 6.0 is an evolutionary, not a revolutionary, release—which is good news for developers.
The really big new features in MFC 6.0 are active document containment and the CHtmlView class. Without a doubt, both features give MFC dazzling new capabilities. But once you get beyond the initial "gee whiz," it remains to be seen exactly how many apps need an embedded Web browser. On the other hand, HTML is rapidly becoming a de facto document standard for text-with-graphics, and it's already the standard for help. Plus, as I described, you can use CHtmlView to view any active document. So if you want to surf the Web (or just some HTML docs shipped with your app) from the comfort of your own window, CHtmlView is the easiest way to do it. Moreover, CHtmlView gives you total control over navigation, which you can't get by launching a page in Internet Explorer.
Some fans may lament the fact that MFC 6.0 doesn't have more features, but I don't. I hope MFC remains a thin wrapper around the Win32 API, and nothing more. The doc/view architecture and command routing are wonderful things, as is MFC's basic COM support. But too many times MFC gets itself into trouble by trying to go beyond the basics—as with CToolBar. Even message maps and doc/view can be more constraining than liberating, as discovered by anyone who finds themselves chasing the spaghetti code through the debugger because they tried to do something that doesn't exactly fit the MFC mold. Or worse yet, tried to use MFC macros in the class template. If MFC is to be the Windows API for C++, then it should provide only what's necessary to program Windows in C++, and nothing more. Any extra layers of functionality should be distinctly separate and optional. Judging from what's in MFC 6.0, the Redmondtonians may have rediscovered the beauty of thin.

From the October 1998 issue of Microsoft Systems Journal. Get it at your local newsstand, or better yet, subscribe.


For related information see: Controlling the Build Process with Custom Build Rules at http://premium.microsoft.com/msdn/library/techart/msdn_bldrul~1.htm.
Also check http://www.microsoft.com/msdn for daily updates on developer programs, resources and events.

© 1998 Microsoft Corporation. All rights reserved.
Terms of Use
.

© 2014 Microsoft Corporation. All rights reserved. Contact Us |Terms of Use |Trademarks |Privacy & Cookies
Microsoft