I have seen this problem posted on the MFC newsgroup a few times, so I thought that it would be a good idea to post a solution here. The question is "How do I make an SDI view smaller than its parent frame window?". The quick answer is, catch the
WM_SIZE message in the
CFrameWnd and resize the view accordingly. But there is a catch, and it's called flicker!
What happens is that when the view is resized, parts of the frame window around the view is suddenly exposed.
CFrameWnd, by default, does not erase its background, so there will be a lot of junk left over on the screen. To fix this,
WM_ERASEBKGND must be handled in
CFrameWnd in order to fill that area.
Wait, there is more.
CFrameWnd::OnSize(...), by default, fills its entire client area with the view. That is done in the
CFrameWnd::RecalcLayout() method. This means that
CFrameWnd::OnSize can't be called. This, in turn, causes another problem, and that is the toolbar and status bar will no longer be resized after the frame is resized.
So, what's the solution already?
The solution comes in three parts:
WS_CLIPCHILDREN must be added to the frame window. That should be done in the
CMainFrame::PreCreateWindow() method override.
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
cs.style |= WS_CLIPCHILDREN;
if( !CFrameWnd::PreCreateWindow(cs) )
Next, the background has to be painted. This can be done by handling the
WM_ERASEBKGND message for the frame window.
BOOL CMainFrame::OnEraseBkgnd(CDC* pDC)
Last but not least, we have to handle the frame window's resizing. This involves a couple of little tricky moves.
void CMainFrame::OnSize(UINT nType, int cx, int cy)
m_nIdleFlags &= ~idleLayout;
m_bInRecalcLayout = TRUE;
RepositionBars(0, 0xffff, 0, reposExtra, &m_rectBorder);
m_bInRecalcLayout = FALSE;
if (GetActiveView() != NULL)
100,Rect.top + 100,Rect.Width()-200,
The first line of the function is clearing the
idelLayout flag from the
m_nIdleFlags variable. The frame window is constantly updating the UI elements such as toolbars during idle processing. One other thing that the frame window does during this time is to call
RecalcLayout, which as you already know will resize the view. Removing the
idleLayout flag will prevent the
OnIdleUpdateCmdUI method from resizing the view.
The next call is to
CFrameWnd::OnSize is not being called, its parent's
OnSize must be called in case
CWnd does something important.
Now comes the part where the toolbar and the statusbar have to be repositioned. The member variable
m_bInRecalcLayout is a
CFrameWnd internal variable that keeps the
RecalcLayout from reentering itself. That flag is set to
TRUE in case the call to
RepositionBars triggers something that would call
RecalcLayout. Don't forget to set it back to
FALSE. Next is the call to
RepositionBars. The only difference between this call to
RepositionBars and the one inside
RecalcLayout is the third parameter. The third parameter is called
nIDLeftOver which is the "ID of the pane that fills the rest of the client area".
CFrameWnd passes the ID of the view as this parameter, and since the view should not fill the frame, a 0 must be passed instead.
And finally, for the moment we have all been waiting for, resize the view to be smaller than the frame window. The code above simply makes the view smaller by 100 pixels from each side. But the possibilities are endless.