C/C++ Q&A

Paul DiLascia

Paul DiLascia is a freelance software consultant specializing in training and software development in C++ and Windows. He is the author of Windows++: Writing Reusable Code in C++ (Addison-Wesley, 1992).

Click to open or copy the SCBMED1 project files.

Click to open or copy the SCBMED2 project files.

Click to open or copy the SCBSAMP project files.


 class CObject {
.
.
.

   virtual void AssertValid() const;
};

The problem is that I'm using an in-house class library that has non-const functions, which I want to call from my AssertValid function. Here's a simplification of my problem:


 class CMyObject : public CObject {
   CTable m_table;            // CTable is from library
   virtual void AssertValid() const;
.
.
.
};

void CMyObject::AssertValid() const
{
.
.
.
   ASSERT(m_table.Lookup("foo") == TRUE);
}

CTable::Lookup is not declared const (even though it really is), so the compiler complains when I try to call it from AssertValid. I know the right way to fix it would be to declare CTable::Lookup const, but when I tried this, it created other problems in the library, so I decided to leave the library alone and just forget about the Assert check. Isn't there some way I can fool the compiler into thinking my Lookup function is const? I tried different kinds of casts but none of them worked.

Andrew Filbrook

AWell, you must not have tried the right one! It's not obvious, I admit, but one simple cast will remove all your const-ernation. But first, let me give you a two-bit spiel on const-ness.

While const can be a useful tool—the kind of thing that helps you find bugs at compilation time, which is always a good thing—const always sneaks up and bites you when you least expect it. The problem is that const works well only when every function in your C++ universe uses it consistently. As soon as some function or group of functions—like your in-house library—fails to follow the rules of const-ness, everything breaks down, as you found out. I'll bet, when you tried to make Lookup const, you found yourself making all the functions it calls const, and the functions they call, and on and on. const has a way of rippling throughout your entire system.

I can think of at least two common situations where you need to overcome the const-ness of a member function by casting it away. The first is when you have a member function like Lookup that really is const, but someone just forgot to declare it that way. The second situation is when you really want to let a const member function change the object, but you don't want it to "count" as a change.

Suppose you have a Get function that counts how many times someone calls it.


 FOO* CMyObject::GetFoo(String s) const
{
   m_refCount++;     // Oops: not allowed in const fn
   return m_pFoo;
}

The ref count is used purely for record keeping—say, to charge applications for calling your function. It doesn't "change" the object conceptually, but strictly speaking it does, which const-itutes a violation of const-ness. Another example is when a Get function caches a value to speed up subsequent Get operations.


 FOO* CMyObject::GetFoo(String s) const
{
   if (!m_pFoo)
      m_pFoo = ComputeTheRealFoo();
   return m_pFoo;
}

ComputeTheRealFoo presumably is a const function that comes up with the FOO pointer. GetFoo stores this in m_pFoo as a performance feature, so it won't have to call ComputeTheRealFoo the next time someone wants to GetFoo. Once again, GetFoo doesn't change the object conceptually, even though it does modify m_pFoo. In Figure 1, the Width and Height functions show a real example of this.

Figure 1 Const

CONST.CPP


 ////////////////////////////////////////////////////////////////
// Copyright 1996 Microsoft Systems Journal. 
//
// This program illustrates how to cast away const-ness
// When you compile, you will get three errors (See below).
//
// Microsoft:     cl  /c const.cpp
// Borland:       bcc /c const.cpp

//////////////////
// A class where functions that are really const are not declared such.
//
class Size {
private:
   int cx;
   int cy;
public:
   Size(int w, int h)   { cx=w; cy=h; }
   int Width()          { return cx; }       // should be const
   int Height()         { return cy; }       // should be const
};

//////////////////
// A class that tries to use const where appropriate
//
class Window {
private:
   int   m_bSizeKnown;     // whether size is known or not
   Size  m_size;           // known size
   void  FigureOutSize()   { /* pretend this actually does something */ }
public:
   Window();
   int Width() const;      // getting the width doesn't change the object
   int Height() const;     // ..ditto for height
};

Window::Window() : m_size(0,0)
{
   m_bSizeKnown = 0;
}

//////////////////
// This function generates two compiler errors.
//
int Window::Width() const
{
   if (!m_bSizeKnown) {
      FigureOutSize();       // Error #1: FigureOutSize is non-const
      m_bSizeKnown = 1;      // Error #2: I'm modifying a member!
      
      // I don't want either of these to "count" as a change, since I'm 
      // not really modifying the Window. m_bSizeKnown is just an 
      // implementation hack so I only have to compute the size once.
      //
   }

   // Error #3: The author of the Size class forgot to declare Size::Width
   // const. It really is const, but the compiler doesn't know that, so it
   // complains:
   return m_size.Width();
}

//////////////////
// This similar function shows how to work around the errors.
// Window::Height compiles with no errors.
//
int Window::Height() const
{
   if (!m_bSizeKnown) {

      // Error #1,#2 1 workaround: The way the compiler handles const 
      // functions is by effectively declaring "this" a const pointer. 
      // In other words, "this" is really "const Window*", not "Window*". 
      // So all you have to do is cast away the const-ness.
      //
      ((Window*)this)->FigureOutSize();
      ((Window*)this)->m_bSizeKnown = 1;
   }

   // Error# 3 workaround: Likewise, in a const member function, the
   // compiler considers all class members const, so it's as if m_size
   // were "const Size m_size" instead of "Size m_size". So to defeat the
   // compiler, you can again cast the const-ness away:

   return ((Size&)m_size).Height();   // cast to (non-const) reference
}

Whether you are merely working around someone else's stupid oversight (YOU would never forget to declare a const function const), or you want to change an object without "changing" it (which makes you too clever for your own good), the question remains: how do you get around the compiler's obsessive insistence that you obey the rules of const-ness? By casting, of course.

To figure out the right cast, you just have to understand how the compiler does const. When you declare a function const, the compiler compiles the function as if the "this" pointer were declared const.


 void* CMyObject::GetFoo(String s) const
   const CMyObject* this;  // pseudo make-believe   
{
   this->m_refCount++;     // Oops: modifies this
   return foo;
}

Knowing this, you can probably guess how to subvert the compiler and overcome the second const problem—the ref count problem—by casting away the const-ness of "this".


 void* CMyObject::GetFoo(String s) const
{
   // Nyah nyah, defeat the compiler...
   ((CMyObject*)this)->m_refCount++; 
   return foo;
}

It works for calling any non-const member function.


 ((CMyObject*)this)->SomeNonConstFunction();

A similar trick will work to solve your CTable Lookup problem, where you want to call m_table.Lookup inside AssertValid (which is const), even though Lookup is non-const.


 void CMyObject::AssertValid() const
{

.

.

.


    // Oops: Lookup is non-const
   ASSERT(m_table.Lookup("foo") == TRUE);
}

The compiler handles this situation by treating all the class members that const-itute CMyObject as if they were const. Thus, inside CMyObject::AssertValid, it's as if m_table were declared


 const CTable m_table;

All you have to do get rid of the const-ness is cast it away.


 ASSERT( ((CTable*)&m_table)   Lookup("foo") == TRUE );

If you really want to show off, it can be written slightly more succinctly as


 ASSERT( ((CTable&)m_table).Lookup("foo") == TRUE );

which casts m_table to a (non-const) CTable reference. Both forms are equivalent and generate exactly the same code. Indeed, const-ness almost never affects the generated code; it only generates compiler errors. The exception is when you have const and non-const overloads of the same function. In that case the compiler obviously generates a call to whichever function is appropriate for the const-ness of the object.

So the moral is, while one function with the wrong const declaration can sometimes trigger a const-ellation of difficulties, you should not const-rue const-ness as unworkably const-rictive. One cast is all it takes to escape const-raint and avoid all const-ernation. Sheesh.

QI'm writing a form-based application with Visual C++Ò 4.0 and MFC. My app uses the document/view model to fill out the form and save the data in my document object. Currently I have a number of sample documents that come with the app, which I use in help and "tutorial" mode to provide examples of how to fill out the forms in various circumstances. The actual data is not important except to show an example. For example, the name in one of the forms is "John Doe" and the social security number is "123-45-6789".

Currently, I store these documents on disk as separate document files that can be opened like any other document, but I would prefer to store them in the executable itself, along with the dialogs and other resources, so they can't get separated from the program. I gather I can put any file I want into my resource file, but how can I read the data using Serialize and the normal document/view stuff? I was able to get it to work by copying the resource data to a temporary file on disk, then opening this file the usual way. Is there some way I can trick MFC into reading the "file" from the resource data directly?

AYes. MFC provides a class that does just what you need: CMemFile. This class lets you treat any array of bytes in memory as a file. The basic solution to your problem is to read the resource data into memory, set up CMemFile for this buffer, and let the normal MFC archiving mechanism read the bytes from CMemFile. To show all the gory details, I modified the SCRIBBLE program—from the Visual C++ tutorial—by adding a File New Sample command. It works like File New, except that instead of creating an empty document, it creates one that's initialized with resource data. Figure 2 shows the code. Since Scribble is too long (and boringly familiar) to fit in the magazine, I'm showing only my modifications, not the entire program.

Figure 2 Scribble

Figure 2 Scribble//////////////////

SCRIBBLE.H


 #include "resource.h"       // main symbols

class CScribbleApp : public CWinApp {
public:
.
.
.
   //{{AFX_MSG(CScribbleApp)
.
.
.
   afx_msg void OnFileSample();  // new command handler
   //}}AFX_MSG
.
.
.
};

SCRIBBLE.RC


 .
.
.
/////////////////////////////////////////////////////////////////////////////
//
// SCRIBDOC
//
IDR_MAINFRAME SCRIBDOC MOVEABLE PURE  "res\\sample.scb"
.
.
.

SCRIBBLE.CPP


 ////////////////////////////////////////////////////////////////
// Modified SCRIBBLE Copyright 1996 Microsoft Systems Journal. 
//
// This modified SCRIBBLE program shows how to store a document in the
// resource file and then load it using the normal serialization mechanism.
// For brevity, only the changes to the original Scribble files are shown.
// 
#include "stdafx.h"

.
.
.

BEGIN_MESSAGE_MAP(CScribbleApp, CWinApp)
.
.
.
   ON_COMMAND(ID_FILE_SAMPLE, OnFileSample)  // New command
END_MESSAGE_MAP()

.
.
.

//////////////////
// Handle "File New Sample"
//
void CScribbleApp::OnFileSample() 
{
   OnFileNew();   // create new (empty) doc
   CScribbleDoc* pDoc = (CScribbleDoc*)
      ((CFrameWnd*)m_pMainWnd)->GetActiveFrame()->GetActiveDocument();
   ASSERT_VALID(pDoc);
   ASSERT_KINDOF(CScribbleDoc, pDoc);

   // Use new fn to load resource doc IDR_MAINFRAME
   VERIFY(pDoc->OnOpenDocument(IDR_MAINFRAME));

   // give it a name
   pDoc->SetPathName(_T("Sample.SCB"));
}

SCRIBDOC.H


 .
.
.
class CScribbleDoc : public CDocument {
protected:
.
.
.
   virtual BOOL OnOpenDocument(UINT uIDRes); // new overloaded function
.
.
.
};
.
.
.

SCRIBDOC.CPP


 #include "stdafx.h"
.
.
.

//////////////////
// Overloaded version of OnOpenDocument loads SCRIBBLE doc from 
// resource data instead of disk file. Arg is ID of resource.
//
BOOL CScribbleDoc::OnOpenDocument(UINT uID)
{
   // Load the resource into memory
   //
   HINSTANCE hinst = AfxGetInstanceHandle();
   HRSRC hRes = FindResource(hinst, (LPCSTR)uID, "SCRIBDOC");
   if (hRes==NULL) {
      TRACE("Couldn't find SCRIBDOC resource %d!\n", uID);
      return FALSE;
   }
   DWORD len = SizeofResource(hinst, hRes);

   // Note: Under Win16, you have to do Lock/UnlockResource, but
   // in Win32 these are no-ops and you can just cast the HGLOBAL
   // returned by LoadResource to a BYTE*.
   //
   BYTE* lpRes = (BYTE*)LoadResource(hinst, hRes);
   ASSERT(lpRes);

   // Create mem file
   // 
   CMemFile file(lpRes, len);

   // Create archive and load from it. 
   // This is just like CDocument::OnOpenDocument
   //
   BOOL bRet = FALSE;      // assume failure
   DeleteContents();

   CArchive ar(&file, CArchive::load);
   ar.m_pDocument = this;
   ar.m_bForceFlat = FALSE;
   TRY {
      CWaitCursor wait;    // hourglass
      Serialize(ar);       // do normal Serialize thing
      ar.Close();          // done
      bRet = TRUE;         // success
      SetModifiedFlag(FALSE);

   } CATCH_ALL(e) {
      TRACE("Unexpected exception loading SCRIBDOC %d\n", uID);
      DeleteContents();
         
   } END_CATCH_ALL

   FreeResource((HANDLE)lpRes);
   return bRet;
}
                                             .
                                             .
                                             .

The sample data shown in Figure 3 is just an ordinary Scribble document, Sample.scb, that's compiled into Scribble.exe. To compile any disk file into your executable as resource data, all you have to do is add a line to your resource file—in this case Scribble.rc.


 IDR_MAINFRAME SCRIBDOC MOVEABLE PURE "res\\sample.scb"

As with any other resource, IDR_MAINFRAME is the resource ID used to identify the sample document. SCRIBDOC is a resource type I made up.

Figure 3 Sample.SCB

Normally, you would use a predefined type like MENU or ICON here, but you can use any name you want and it will compile. Whatever string you use as the resource type must be the same string you use to load the resource.


 // Load resource type "SCRIBDOC"
HRSRC hRes = FindResource(hInst,
                    (LPCSTR)IDR_MAINFRAME, "SCRIBDOC");

So much for getting Sample.scb compiled into Scribble. The next step is to load it from within your program. To do this, I wrote an overloaded version of CScribbleDoc::OnOpenDocument that takes an ID instead of a pathname (it's part of ScribDoc.cpp). This function uses ::FindResource and ::LoadResource to load the resource into memory. Once the resource is in memory, OnOpenDocument sets up a CMemFile for it, then does the normal archiving thing using the CMemFile as the file (see Figure 4). (The real code has TRY/CATCH blocks that I've stripped to highlight the main points.)

Figure 4 How to use CMemFile


 BOOL CScribbleDoc::OnOpenDocument(UINT uID)
{
   BYTE* lpRes = // load resource uID
.
.
.
   // Create memfile. lpRes points to resource 
   // in memory; len is size in bytes. 
   //
   CMemFile file(lpRes, len);

   // Create archive and Serialize. This is just 
   // like CDocument::OnOpenDocument(LPCSTR).
   //
   BOOL bRet = FALSE;      // assume failure
   DeleteContents();

   CArchive ar(&file, CArchive::load);
   ar.m_pDocument = this;
   ar.m_bForceFlat = FALSE;
   Serialize(ar); // do it
}

The beauty of this is that the loading still goes through the normal mechanism, through CScribbleDoc::Serialize, because the CMemFile looks just like an ordinary CFile disk file. All it does is fetch bytes from memory instead of from disk. By using CMemFile, I've assumed that the entire resource can fit in memory at once, which is usually a safe assumption. If not, you could implement your own CMemFile-derivative that reads resource data in pieces.

Once serialization is complete, OnOpenDocument frees the resource from memory and terminates. By the way, you may have noticed that I don't use Lock/UnlockResource anywhere. That's because locking/unlocking is a thing of the past, and these functions are implemented in Win32Ò as no-ops for backward compatibility only.

If you're wondering how my overloaded OnOpenDocument gets called, it happens in the command handler for File New Sample.


 void CScribbleApp::OnFileSample() 
{
   OnFileNew();   // create new (empty) doc
   CScribbleDoc* pDoc = (CScribbleDoc*)
      ((CFrameWnd*)m_pMainWnd)->
                 GetActiveFrame()->GetActiveDocument();
   ASSERT_VALID(pDoc);
   ASSERT_KINDOF(CScribbleDoc, pDoc);

   // load resource "file" IDR_MAINFRAME
   VERIFY(pDoc->OnOpenDocument(IDR_MAINFRAME));

   // give it a name
   pDoc->SetPathName(_T("Sample.SCB"));
}

I should point out that CMemFile has nothing to do with Win32 memory-mapped files, which is a mechanism that lets two or more Win32 processes share data by creating a "file" in memory. CMemFile is an MFC thing that lets you read and write data to a memory block as if it were a file.

QI'm implementing an app with MFC 4.0 and I have a status line indicator that displays "MOD" if the current document is modified. I use the normal ON_UPDATE_COMMAND_UI mechanism to do it.


 BEGIN_MESSAGE_MAP(CMyFrameWnd, CFrameWnd)
.
.
.
   ON_UPDATE_COMMAND_UI(ID_INDICATOR_MOD,
                        OnUpdateModifiedIndicator)
END_MESSAGE_MAP()

// In my frame class implementation
void CMyFrameWnd::OnUpdateModifiedIndicator(
                                        CCmdUI* pCmdUI)
{
   CDocument* pDoc = GetActiveDocument();
   pCmdUI->Enable(pDoc && pDoc->IsModified());
}

This works fine, but I also want to display an asterisk next to the file name in the title bar if the document has been modified. This is so users can see if the document is modified even when the status bar is turned off. Is there something like ON_UPDATE_COMMAND_UI for the title bar, or should I change the title whenever the document's modified status changes?

Phillipe Bernous

AWhen I first saw this question, one solution came to mind, then another, and then yet another. I'll show you the first two now and save the surprise ending for next month.

There's nothing like ON_UPDATE_COMMAND_UI for window titles, but adding an asterisk in the title bar is fairly easy. The first solution I'll show you implements the asterisk solely within the document class by overriding CDocument::SetModifiedFlag. The second solution does it within the child frame window by handling WM_IDLEUPDATEUI. I implemented these solutions as SCBMOD1 and SCBMOD2, modifications to the MFC Scribble tutorial program.

You may not know it, but documents already have titles. CDocument::GetTitle and CDocument::SetTitle let you get and set a document's title, which is what MFC displays in the frame window title bar by default. If the document has a file name, the title is just the file name minus the directory. If the document has no file name—if the user just created it with File New and hasn't saved it yet—MFC makes up a name like Scrib1 or Scrib2, as determined from the document type name you specify in the IDR_MYDOCTYPE resource string. The simplest way to add an asterisk is to change the document title whenever someone changes the document's modified flag. The easiest way to do that is to override CDocument::SetModifiedFlag.

Figure 5 shows my SCBMOD1 modifications to CScribbleDoc. The entire feature is implemented in one function, CScribbleDoc::SetModifiedFlag, which adds or removes the asterisk depending on the modified flag. The implementation is pretty straightforward. CDocument::SetTitle immediately updates the document's frame's title bar by calling an undocumented MFC function, CDocument::UpdateFrameCounts. (You can call this function any time you want to update the title bar for a frame associated with a document.)

Figure 5 Changes for SCBMOD1


 ////////////////////////////////////////////////////////////////
// Modifications to CScribbleDoc for solution #1
// (In SCRIBBLE\ScribDoc.cpp)

//////////////////
// **MOD**
// Override SetModified flag to notify view when doc has changed,
// so the view can tell the frame to update the title.
//
void CScribbleDoc::SetModifiedFlag(BOOL bModified)
{
   if (IsModified() != bModified) {
      CDocument::SetModifiedFlag(bModified);
      UpdateFrameCounts();
      CString title = GetTitle();
      int asterisk = title.GetLength()-1;
      if (asterisk >=0 && title.GetAt(asterisk)=='*') {
         if (!bModified)
            title.SetAt(asterisk,0);
      } else if (bModified)
         title += '*';
      SetTitle(title);
   }
}

The second approach is entirely different. It implements the asterisk in CChildFrame by handling WM_IDLEUPDATEUI. This MFC-private message is defined in <afxpriv.h>. Technically, it's a private message, but you can use it as it's not likely to disappear soon. MFC broadcasts WM_IDLEUPDATEUI to the main frame and all its descendants as part of its normal idle duties. (See "Meandering Through the Maze of MFC Message and Command Routing," MSJ, July 1995). The default handler sends CN_UPDATE_COMMAND_UI messages to update all your UI components, like menu items, toolbar buttons, and status bar panes, which is the perfect time to update the title bar as well. CChildFrame::OnIdleUpdateCmdUI in Figure 6 shows how.

Figure 6 Changes for SCBMOD2


 ////////////////////////////////////////////////////////////////
// Modifications to CChildFrame for solution #2
// (In SCRIBBLE\ChildFrm.cpp)

#include <afxpriv.h>    // for WM_IDLEUPDATECMDUI
.
.
.

BEGIN_MESSAGE_MAP(CChildFrame, CMDIChildWnd)
   //{{AFX_MSG_MAP(CChildFrame)
   ON_MESSAGE(WM_IDLEUPDATECMDUI, OnIdleUpdateCmdUI) // handle idle UI msg
   //}}AFX_MSG_MAP
END_MESSAGE_MAP()

CChildFrame::CChildFrame()
{
   m_bModLast = -1;        // initialize modified-state memory
}
//////////////////
// Override to add asterisk if doc is modified.
//
void CChildFrame::OnUpdateFrameTitle(BOOL bAddToTitle)
{
   CMDIChildWnd::OnUpdateFrameTitle(bAddToTitle);
   CDocument* pDoc = GetActiveDocument();
   if (pDoc && pDoc->IsModified()) {
      // if doc modified, append * to normal title
      CString title;
      GetWindowText(title);
      title += '*';
      SetWindowText(title);
   }
}

//////////////////
// **MOD**
// Handle WM_IDLEUPDATECMDUI to update modified indicator if necessary.
//
LRESULT CChildFrame::OnIdleUpdateCmdUI(WPARAM wParam, LPARAM)
{
   // Only update the title if the modified state has changed.
   // Otherwise, the title bar will flicker.
   // 
   CDocument* pDoc = GetActiveDocument();
   if (m_bModLast != pDoc->IsModified()) {
      // mod state has changed: set flag telling MFC to update title
      m_nIdleFlags |= idleTitle; 
      m_bModLast = pDoc->IsModified();
   }

   // do the default thing
   CMDIChildWnd::OnIdleUpdateCmdUI();

   return 0L;
} 

The basic idea is to override CFrameWnd::OnUpdateFrameTitle to append an asterisk to the title if the active document is modified. MFC calls this virtual function whenever it needs to update a frame's title. (For example, when the user opens a new document or when you call CDocument::SetTitle.) Implementing OnUpdateFrameTitle is sort of like writing an ON_UPDATE_COMMAND_UI for the title bar, but there's one very important difference: MFC doesn't call OnUpdateFrameTitle continuously during idle processing like it does to update other UI components. You have to do it yourself. That's where WM_IDLEUPDATEUI comes in.

There're only two tricks that deserve further explanation. First, instead of calling OnUpdateFrameTitle directly, my OnIdleUpdateCmdUI handler sets an undocumented flag, idleTitle, in CFrameWind::m_nIdleFlags. If this flag is set, CFrameWnd updates the title in its default OnIdleUpdateCmdUI handler.


 // in CFrameWnd::OnIdleUpdateCmdUI
if (m_nIdleFlags & idleTitle)
   OnUpdateFrameTitle(TRUE);

If you don't like using idleTitle this way (because it's undocumented), you can always call OnUpdateFrameTitle directly. Just replace the line


 m_nIdleFlags |= idleTitle; 

with


 OnUpdateFrameTitle(TRUE);

The second trick is that I save the previous state of the modified flag, so I update the title only if the document's modified flag has changed since the last time WM_IDLEUPDATEUI was sent. Otherwise, I'd be setting the title (to the same value) on every WM_IDLEUPDATEUI cycle and the title would flicker noticeably. (Trust me, I found out the hard way.) You might try using another undocumented function, AfxSetWindowText, which sets the window text only if it's actually different from the current text—but that won't work here because I use CMDIChildWnd::OnUpdateFrameTitle to first compute the "normal" title, then I append the asterisk. If I used AfxSetWindowText, I would end up continually removing my asterisk from the title bar and then adding it back again, so the title would still flicker. (I found that out the hard way, too.)

What MFC really needs here is some hook to let you modify the title before OnUpdateFrameTitle sets it, or else a separate function that computes the title without setting it in the window. Barring these, the best you can do would be to copy the MFC code from CMDIChildWnd::OnUpdateFrameTitle and then modify it. Unfortunately, you're out of luck if a new release of MFC changes this function. I opted to go ahead and set the title twice—once to compute the "base" title, and again to append the asterisk—and then do the flag thing with m_bModLast to cure the flicker problem. It's only two or three lines of code.

I implemented the change in CChildFrame and not CMainFrame because Scribble is an MDI app, so CChildFrame is the one that contains the document/view. One consequence is that the asterisk appears in the child frame only, not the main frame, unless the child is maximized (see Figure 7). With my first solution (the document approach), both frames get the asterisk since it's part of the document title. You could of course fix this by changing CMainFrame similarly.

Figure 7 Asterisk appears in ChildFrame

Now that you've seen the two solutions, the natural question is which one is better? Well, that depends on whether you consider the asterisk a property of the document or the frame! Personally, it seems more like a property of the frame to me, so I favor the WM_IDLEUPDATEUI approach. The first solution is certainly simpler, but consider this: what if your document is embedded in an OLE In-place frame? If you use the document solution, the IP frame gets the asterisk feature. Users might not like asterisks appearing in their title bar, or maybe they will and then wonder why other embedded document objects don't have the same neat feature. Either way, you'll be inconsistent.

So there you have it; one feature, two implementation philosophies. But the story doesn't end here! At the outset, you asked whether there was a way to change the title with ON_UPDATE_COMMAND_UI. In a way, my second solution is a bit like ON_UPDATE_COMMAND_UI, since WM_IDLEUPDATEUI is closely related to ON_UPDATE_
COMMAND_UI—it's the place from which CN_UPDATE_COMMAND_UI is sent, and all your ON_UPDATE_COMMAND_UI handlers get called. So why not go all the way? Why not implement a solution that does let you use ON_UPDATE_COMMAND_UI to change the title? This would be really neat, because it would let you add not just an asterisk to show the document is modified, but any kind of title bar "indicator" you want, just like the indicator panes in the status bar. You should be able to show the document's time and date of creation or the name of the current selected object in the title bar. Next month I'll show you how.

Have a question about programming in C or C++? You can mail it directly to C/C++ Q&A, Microsoft Systems Journal, 825 Eighth Avenue, 18th Floor, New York, New York 10019, or send it to MSJ (re: C/C++ Q&A) via:


Internet:


Internet:

Paul DiLascia
72400.2702@compuserve.com

Eric Maffei
ericm@microsoft.com


This article is reproduced from Microsoft Systems Journal. Copyright © 1995 by Miller Freeman, Inc. All rights are reserved. No part of this article may be reproduced in any fashion (except in brief quotations used in critical articles and reviews) without the prior consent of Miller Freeman.

To contact Miller Freeman regarding subscription information, call (800) 666-1084 in the U.S., or (303) 447-9330 in all other countries. For other inquiries, call (415) 358-9500.