Skip to content

SubclassWindow() method issues in projects base on MFC Feature Pack

The Problem
Trying to paint a background image into client area of a MDI application build in VC++ 6.0 to VC++ 2005 IDE it’s not a difficult task.
In case you need, you can find easily good references. For instance, there are two references from Microsoft (KB129471 and KB103786) and one I prefer: a FAQ wrote by a friend of mine.

Unfortunately things are changing radically in case you’re following the same steps in a Visual C++ IDE that has MFC Feature Pack support. If you’re building from the scratch a VC++ 2008/VC++ 2010 a MDI project that has MFC Feature Pack support and you’re trying to apply sub-classing steps, you will have a big surprise in the moment you’re starting your application in debug mode. Effectively your application will crash in the moment you are trying to call SubclassWindow() in CMainFrame::OnCreate().

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) {
  if (CMDIFrameWndEx::OnCreate(lpCreateStruct) == -1)
    return -1;

  // ---- code ---

  // BANG! IN VS 2010 or VS 2008 with MFC Feature Pack
  // m_wndMDIClient.SubclassWindow(m_hWndMDIClient);
  // where m_wndMDIClient is an instance of CMDIClientWnd
  // (http://support.microsoft.com/kb/129471)

  // ------ code ----

  return 0;
}

Problem details
Starting with MFC Feature Pack CMDIFrameWndEx is the new CMainFrame’s parent class instead of CMDIFrameWnd and the problem acts inside of Attach() method:
because CWnd::FromHandlePermanent(HWND hWnd) looks up into a permanent handle map and in returns existing CWnd pointer.

Wnd* PASCAL CWnd::FromHandlePermanent(HWND hWnd) {
  CHandleMap* pMap = afxMapHWND();
  CWnd* pWnd = NULL;
  if (pMap != NULL) {
    // only look in the permanent map - does no allocations
    pWnd = (CWnd*)pMap->LookupPermanent(hWnd);
    ASSERT(pWnd == NULL || pWnd->m_hWnd == hWnd);
  }
  return pWnd;
}

CHandleMap is the wrapper that implements the mapping mechanism between the pointers of MFC wrapped classes and the Windows object handles. Internally, this class has to dictionaries (m_permanentMap and m_temporaryMap) implemented as CMapPtrToPtr, m_nHandles – the number of handles, m_nOffset – the offset of handles in the object and it has a m_pClass pointer of CRuntimeClass (a run time class associated with all MFC classes).
In case you’re interest in more details, you can find more information here.

We have a pointer to a CHandleMap instance that is assigned with the returned pointer of a handle map returned by afxMapHWND(). The returned pointer pWnd it’s assigned with the result returned by pMap->LookupPermanent(hWnd). LookupPermanet() effectively search into a the permanent hash map for exiting HANDLEs and in our case it find it.

inline CObject* CHandleMap::LookupPermanent(HANDLE h) {
  return (CObject*)m_permanentMap.GetValueAt((LPVOID)h);
}

where

void* CMapPtrToPtr::GetValueAt(void* key) const {
  // find value (or return NULL -- NULL values not different as a result)

  ENSURE(this);

  if (m_pHashTable == NULL)
    return NULL;

  UINT nHash = HashKey(key) % m_nHashTableSize;

  // see if it exists
  CAssoc* pAssoc;
  for (pAssoc = m_pHashTable[nHash]; pAssoc != NULL; pAssoc = pAssoc->pNext) {
    if (pAssoc->key == key)
      return pAssoc->value;
  }
  return NULL;
}

If the item having nHash key was found into m_pHashTable then the condition if (pAssoc->key == key) is TRUE because the attribute m_hWndMDIClient of CMDIFrameWnd is used yet.
So, effectively what LookupPermanent() has found in m_permanentMap map is m_hWndMDIClient. And because pMap->SetPermanent(m_hWnd = hWndNew, this) is one of the next call into Attach() method those ASSERTs are a must.
Even if those ASSERT() calls from Attach() are available only in debug mode (because of ASSERT() macro behavior) a release build would not save the situation. Soon or later you’ll get conflicts and the application will crash.

Trying to find where this has happened is not so complicated as long as we take in consider our CMainFrame class it’s derived from CMDIFrameWndEx a class that extends CMDIFrameWnd. If we are looking into CMDIFrameWndEx class implementation (AfxMDIClientAreaWnd.cpp) we will see that into this class SubclassWindow() method it’s called jet:

BOOL CMDIFrameWndEx::OnCreateClient(LPCREATESTRUCT lpcs,
                                    CCreateContext* pContext) {
  if (!CMDIFrameWnd::OnCreateClient(lpcs, pContext)) {
    return FALSE;
  }

  if (m_bDoSubclass) {
    m_wndClientArea.SubclassWindow(m_hWndMDIClient);  // this is it!
  }

  return TRUE;
}

Subclassing a CWnd derived instance that has already a mapped HWND item is an error and these ASSERTs try to avoid this from development moment. Having two different CWnd-derived objects with the same HWND is not possible – the only exception is CDC instances that have 2 HWNDs (m_hDC and m_hAttribDC).
Related to my issue, according to Steve Horne from Microsoft, “anything that uses the MFC Feature Pack will be using CMDIFrameWndEx which is a very different beast. It has this feature built it as you’ve found out”.
The worst part is that “If you were able to subclass the Ex client area, you’d probably end up breaking a lot of the FluentUI features.”
The VS 2008 / VS 2010 wizard generates and use a lot of Feature Pack FluendUI items.

A bad solution
An approach might be trying to adapt sub-classing idea directly into CMainFrame class. So, the steps might be:

  • No CMDIClientWnd instance is needed (as in existing tutorials). So no more SubclassWindow() call in CMainFrame::OnCreate().
  • Handle WM_ERASEBKGND, WM_SIZE and WM_PAINT on CMainFrame.
void CMainFrame::OnPaint() {
  CWnd* pWnd =
      CWnd::FromHandle(m_hWndMDIClient);  // returns a MFC object pointer
  // for the given handle if it is valid
  CPaintDC dc(pWnd);

  // the rest of the code
}

CWnd::FromHandle() acquires a pointer to an MFC object pointer from CHandleMap via afxMapHWND().

BOOL CMainFrame::OnEraseBkgnd(CDC* pDC) {
  return FALSE;
}

void CMainFrame::OnSize(UINT nType, int cx, int cy) {
  CMDIFrameWndEx::OnSize(nType, cx, cy);

  Invalidate();
}

At the very first time everything looked nice. But unfortunately I have to admit Steve Horne’s observations. In different situations (most on resizing or moving messages) some of the FluentUI items were not correctly painted (some Ribbon items painting issues – different cases).

So, a better solution is needed.

A good but not perfect solution
In my research, for projects base on MFC Feature Pack, there is no perfect solution for this issue. I mean something similarly with the good solutions that I mentioned in the beginning of this article but acts fine until the first IDE that use MFC Feature Pack.
As we have seen on top trying to subclass a window with an already mapped is not a good idea.
The solution is based on Joseph M. Newcomery’s idea, a well-known book writer and Microsoft Visual C++ MVP. Joe proposes “temporary” remapping only for the case we need – in my case painting actions. For the rest of the action the mapping process inside of framework continues in the classic way. It’s a “gross and ugly” solution but until having a better solution from Microsoft or others I consider it fine for my needs.

  • First step is to define a class CMDIClientWnd derived from CWnd and add WM_PAINT and WM_ERASEBKGND handle methods.
BOOL CMDIClientWnd::OnEraseBkgnd(CDC* pDC) {
  return FALSE;  // let OnPaint() to paint, only
}

void CMDIClientWnd::OnPaint() {
  CPaintDC dc(this);

  // effective painting stuff
}
  • Catch the WM_PAINT message in CMainFrame via PreTranslateMessage() before the message is dispatched for execution and calling our redraw method.
BOOL CMDIClientWnd::OnEraseBkgnd(CDC* pDC) {
  return FALSE;  // let OnPaint() to paint, only
}

void CMDIClientWnd::OnPaint() {
  CPaintDC dc(this);

  // effective painting stuff
}

Here is the RedrawClientArea() public method.

void CMainFrame::RedrawClientArea() {
  CMDIClientWnd wnd_cl;

  wnd_cl.Attach(m_wndClientArea.Detach());
  wnd_cl.Invalidate();
  wnd_cl.UpdateWindow();
  m_wndClientArea.Attach(wnd_cl.Detach());
}

So we create locally an instance of CMDIClientWnd and we attach it internally to ChandleMap::m_permanetMap via Attach(), not before detaching m_wndClientArea (an CMDIClientAreaWnd instance, attribute in CMDIFrameWndEx and as we have seen before it subclass the CMDIFrameWndEx in CMDIFrameWndEx::OnCreateClient()).

The idea is that our CMDIClientWnd instance temporary replace m_wndClientArea instance of CMDIClientAreaWnd right before effective WM_PAINT message is dispatched via PreTranslateMessage().

  • Include your new class header (ex. MDIClientWnd.h) in MainFrm.cpp and call RedrawClientArea() in CMainFrame::OnSize().
void CMainFrame::OnSize(UINT nType, int cx, int cy) {
  CMDIFrameWndEx::OnSize(nType, cx, cy);

  RedrawClientArea();  // repaint on WM_SIZE
}
  • If the child frames window is not tabbed style (when all client area is hidden) and the client area is still visible than we have to call RedrawClientArea() method from WM_MOVE and WM_SIZE handler of CChildFrame and we have to include MainFrm.h into ChildFrame.cpp.
void CChildFrame::OnMove(int x, int y) {
  CMDIChildWndEx::OnMove(x, y);

  CMainFrame* pMainFrame = (CMainFrame*)GetParentFrame();
  ASSERT(pMainFrame);

  pMainFrame->RedrawClientArea();
}

void CChildFrame::OnSize(UINT nType, int cx, int cy) {
  CMDIChildWndEx::OnSize(nType, cx, cy);

  CMainFrame* pMainFrame = (CMainFrame*)GetParentFrame();
  ASSERT(pMainFrame);

  pMainFrame->RedrawClientArea();
}
  • Additionally, in order to make sure the painting message is received by main frame at application’s starting moment and your image is correctly painted from the beginning, please call pMainFrame->Invalidate() after pMainFrame->UpdateWindow() in InitInstance() method of your application class. Otherwise, if your application it’s starting with no opened document (for instance new document), your picture will appear only in the moment a WM_PAINT message is generated in CMainFrame (for instance when you resize your application, select the menu, etc).

A disadvantage of this approach is that the interest message (WM_PAINT) is not handled inside the class of m_wndClientArea, but the good point is that the rest of the messages are left at the correct class of the framework and will work correctly.
Demo application (7196 downloads )

Silviu Ardelean

Software Engineer

More Posts - Website

Follow Me:
TwitterFacebookPinterest

1 thought on “SubclassWindow() method issues in projects base on MFC Feature Pack”

Leave a Reply to Silviu Ardelean Cancel reply

Your email address will not be published. Required fields are marked *

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.