When BCGSoft created the classes that Microsoft incorporated as the “MFC Feature Pack” in VS 2008 (and included in SP1), it seems their primary intent was to provide a nearly hands-free implementation of several popularly-requested user interface features.  Especially with the “Visual Manager” classes (derived from CMFCVisualManager), it’s clear that the intent was to easily change the appearance of your MFC application to mimic the visual styles of popular Microsoft products like Office and Visual Studio.

It’s also clear that very little effort was spent to make these classes customizable or overridable.  If you want your app to look just like Visual Studio 2005 or Office 2007, for instance, you’re in luck – they make it extremely easy to do.  If you want your app to be “a lot like” one of these MS apps, but you need to change a couple of appearances or behaviors, you may be in for a lot of pain and frustration.

The Problem Description

I recently lost some more of what little hair I have helping a customer to customize his application that used the Office 2007 Visual Manager (CMFCVisualManagerOffice2007).  Specifically, he needed to simply enable a menu item that would remove (or restore) the caption on his main frame window.

This sounds at first blush to be a fairly simple task, right?  Just remove the WS_CAPTION style from the window, right?
      ModifyStyle( WS_DLGFRAME, 0, SWP_FRAMECHANGED ) ;

Wrong.  Not with the Office 2007 Visual Manager, you don’t.  Go ahead…. Try it!

The Cause

You see, the folks in the Office product group made the 2007 version so pretty, it apparently requires all sorts of magical incantations and love potions to emulate it.  The source code for this class (and its supporting classes) is provided with MFC, so I encourage you to browse through it a bit.  It’s pretty elaborate and even includes reading and parsing an XML file that describes the visual style.  And the most important (for today’s discussion) point to note is that this visual manager draws the frame caption itself (see IsOwnerDrawCaption and DrawNcCaption), unless Desktop Window Manager (DWM) Composition is on or the ribbon bar is set to replace the frame caption.  You can see already that this isn’t going to be as easy as flipping a bit.

But I did it. And I lost hair in the experimentation process.  Blogging about it today isn’t going to regrow my hair, but it’ll make me feel a little better. J

The Resolution

First of all, since the visual manager class doesn’t provide a switch to flip the caption on or off (it just assumes there’s always a caption), I had to override the class just to provide this.  Let’s see… what should I call my new class? ... CMyVisualManagerOffice2007, of course.

class CMyVisualManagerOffice2007 : public CMFCVisualManagerOffice2007

{

       DECLARE_DYNCREATE(CMyVisualManagerOffice2007)

public:

       CMyVisualManagerOffice2007();

       virtual ~CMyVisualManagerOffice2007();

 

       virtual BOOL IsOwnerDrawCaption() { return m_bCaptionOn && CMFCVisualManagerOffice2007::IsOwnerDrawCaption(); }

 

       bool m_bCaptionOn;

};

 

So, as you see, I added a Boolean variable to indicate whether or not to draw a caption, and I overrode the method to answer the question of whether or not to “owner draw” it.

Of course, I had to tell the app to use my new class.  This means #include-ing the header file in MainFrm.cpp and replacing all instances of “CMFCVisualManagerOffice2007” with “CMyVisualManagerOffice2007” in that file (in the OnApplicationLook method, by default).

Next, I had to add a similar variable to the CMainFrame class and add some methods to implement the toggleable menu item to toggle this variable in the frame class:

// CMainFrm.h

class CMainFrame : public CMDIFrameWndEx

{

 

// Attributes

public:

       bool m_bHasCaption;

       afx_msg void OnViewWindowCaption();
       afx_msg void OnUpdateViewWindowCaption(CCmdUI *pCmdUI);
};

// CMainFrm.cpp

void CMainFrame::OnUpdateViewWindowCaption(CCmdUI *pCmdUI)

{      pCmdUI->SetCheck( m_bHasCaption ); }

 

void CMainFrame::OnViewWindowCaption()// toggle it

{      SetFrameCaption( !m_bHasCaption ); }

 

You see that I called something that I named “SetFrameCaption”.  This is the function where most of the magic happens to put all the pieces together.  Its role is to synchronize the visual manager’s state with the toggle switch we just put into the main frame.

 

void CMainFrame::SetFrameCaption( bool bCaptionOn )

{

       m_bHasCaption = bCaptionOn ;

 

       CMFCVisualManager * pVM = CMFCVisualManager::GetInstance();

       if ( pVM->IsKindOf( RUNTIME_CLASS(CMyVisualManagerOffice2007) ) )

       {

              CMyVisualManagerOffice2007 * pMyVM =

static_cast<CMyVisualManagerOffice2007 *>(pVM) ;

              pMyVM->m_bCaptionOn = m_bHasCaption ;

              if ( m_bHasCaption )

                     ModifyStyle( 0, (afxGlobalData.DwmIsCompositionEnabled() ?

    WS_CAPTION : WS_BORDER), SWP_FRAMECHANGED ) ;

              pMyVM->RedrawAll();

       }

       else if ( m_bHasCaption )

              ModifyStyle( 0, WS_CAPTION, SWP_FRAMECHANGED ) ;

       if ( ! m_bHasCaption )

              ModifyStyle( WS_DLGFRAME, 0, SWP_FRAMECHANGED ) ;

       SetWindowPos( NULL, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER

   | SWP_NOACTIVATE | SWP_FRAMECHANGED ) ;

}

 

Note that if DWM composition is on, the visual manager doesn’t try to draw the caption itself and our override attempts just screw things up.  Fortunately, the DWM does honor the WS_CAPTION style, so we can just toggle it off and let the DWM do the drawing.

I found that the RedrawAll() method, although somewhat radical, is required to make the visual manager redraw with the caption when we’re turning it back on.  Also note that WS_CAPTION is really a combination of WS_BORDER | WS_DLGFRAME.  Sometimes, I had to use only one of those styles when turning on or off the caption, in order to get the correct behavior.

 

I also found that the call to SetFrameCaption needed to happen in a couple of other places to make things work smoothly throughout. The OnCreate needs to set it initially, before any toggling will occur later:

 

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

      SetFrameCaption( (lpCreateStruct->style & WS_CAPTION) != 0 );

}

 

And I needed to override OnChangeVisualManager, because this app allowed the visual styles to be changed from the menu:

 

LRESULT CMainFrame::OnChangeVisualManager(WPARAM wParam, LPARAM lParam)

{

      SetFrameCaption( m_bHasCaption ) ;

 

      return CMDIFrameWndEx::OnChangeVisualManager( wParam, lParam );

}

 

Of course, this is just a way to work around one of the many obstacles in the design of the Feature Pack classes.  The CFrameImpl class is yet another example of a class that is very difficult to override.  There are others.  I'd like to see some serious effort go into revising these classes to allow customization.  Of course, this would then open the possibility of introducing breaking changes to code that uses the current classes.  I'd say it would be worth the risk.

 

- Scot Brennecke, Escalation Engineer, VC++ support