Introduction
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:
First, 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) )
return FALSE;
return TRUE;
}
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)
{
CRect Rect;
GetClientRect(&Rect);
pDC->FillSolidRect(&Rect,::GetSysColor(COLOR_APPWORKSPACE));
return TRUE;
}
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;
CWnd::OnSize(nType,cx,cy);
m_bInRecalcLayout = TRUE;
RepositionBars(0, 0xffff, 0, reposExtra, &m_rectBorder);
m_bInRecalcLayout = FALSE;
if (GetActiveView() != NULL)
{
CRect Rect;
GetClientRect(Rect);
GetActiveView()->SetWindowPos(NULL,Rect.left +
100,Rect.top + 100,Rect.Width()-200,
Rect.Height()-200,SWP_SHOWWINDOW);
}
}
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 CWnd::OnSize
. Since 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.
Have fun :)