Introduction
Have you ever wondered how to get the MDI Child Frame windows in your MDI application to appear centered in the client area of the main frame window? Have you tried using CenterWindow()
, but found that the window still isn't centered properly? This article shows you how, and we also talk about the ways of keeping child windows centered, even if the user moves or resizes the main window, or shows or hides the toolbar(s) and status bar.
For the code below, I will assume you are familiar with MFC Doc/View architecture and are using the said architecture in your app. The starter project for the sample application included with this article was created using Visual C++ 6 and the MFC AppWizard. The code in this article has been tested on Windows 98 all editions and Windows NT, 2000, and XP both Home and Professional.
If you have any questions, please feel free to post a message below or email me (see the sample program for the email address). Now, on to centering windows!
The Proper Way to Call CenterWindow()
In this article, we will look more carefully at CenterWindow()
and ensure it's called properly. Many thanks go out to the posters to this article's message board (below) for the update. The key to using CenterWindow()
is to ensure that the CWnd*
pointer you're passing in is the correct pointer, and that the CenterWindow()
function is called for the proper window (view, frame, MDI child window etc.) that you mean to center.
Assuming you're using MFC's doc/view architecture, you override CView::OnInitialUpdate
and put in the code shown below in bold:
Listing 1: Override of CView::OnInitialUpdate
void CCenterMDIWndView::OnInitialUpdate()
{
CView::OnInitialUpdate();
GetParentFrame()->CenterWindow(AfxGetMainWnd());
}
Remember, this code should go in the view enclosed in the frame you want to center. So we see that we call CMDIChildWnd::CenterWindow
(which is inherited from CWnd
) and pass a pointer to the main frame window of the application, as returned by AfxGetMainWnd()
. Thanks go out to Ravi Bhavnani and Michael Zhao for pointing out this simple solution.
Keeping Your Windows Centered
But what about when you want to keep your newly-centered child window centered, say, if the user re-sizes the main frame window or moves the child window outside the client area? Or if the main frame window is minimized and maximized? We need to handle the so-called 'MFC private message', WM_SIZEPARENT
. WM_SIZEPARENT
is a so-called 'user message,' and it's defined in <afxpriv.h>. The framework sends this message to child windows of the main frame window in response to the main frame window being resized (and hence receiving a WM_SIZE
message from Windows).
So I came up with the following algorithm to handle the case of the main frame window being moved or sized by the user (the WM_SIZE
message is sent by Windows in both cases):
Figure 2: Algorithm to re-center child windows when user resizes or moves the main frame window of the application.
Let's work from the end of the flowchart up to the handling of the WM_SIZE
message. Working this way, my first step is to add a handler to my CChildFrame
(derived from CMDIChildWnd
) class for the WM_SIZEPARENT
message. The code for this message is declared in <afxpriv.h>, so the best policy is to add a #include
line for it to STDAFX.H:
Listing 2: Including the Header for WM_SIZEPARENT
#include <afxpriv.h> // MFC private messages
Next, we have to add a line in the DECLARE_MESSAGE_MAP
section of the CHILDFRM.H file (where my CChildFrame
class is declared):
Listing 3: The Message Map Declaration in CHILDFRM.H
protected:
afx_msg LRESULT OnSizeParent(WPARAM wParam, LPARAM lParam);
DECLARE_MESSAGE_MAP()
Next we'll go to the CHILDFRM.CPP file and add a message map entry and handler for the WM_SIZEPARENT
message. Notice that the code you type is in bold, and we have to add the entire handler implementation from scratch:
Listing 4: Adding a Message Map Entry and Handler Implementation
BEGIN_MESSAGE_MAP(CChildFrame, CMDIChildWnd)
ON_MESSAGE(WM_SIZEPARENT, OnSizeParent)
END_MESSAGE_MAP()
LRESULT CChildFrame::OnSizeParent(WPARAM, LPARAM)
{
CenterWindow(AfxGetMainWnd());
return 0;
}
The WM_SIZEPARENT
message gets sent by the handler we'll be adding to CMainFrame
for the WM_SIZE
message. But first, a preliminary. Notice how I mentioned above that the centering of a given MDI child window can be thrown off if, say, the user hides or shows the toolbar(s) or status bar? Let's also account for this. First, an aside on the structure of the main window.
Window Handles and the MDIClient
Window handles exist to denote windows, and they are useful to pass to various Windows API functions when we want to do something to the corresponding window. It turns out that the client area of the main window of a typical MFC application is filled by a window called the MDIClient
. It is actually the MDIClient
which is the parent of all the MDI child windows currently open in the main window. The window handle of the MDIClient
is stored in the CMDIFrameWnd::m_hWndMDIClient
member variable.
In order to get the WM_SIZEPARENT
message sent out to all the MDI child windows (since we never know a priori just which MDI child windows are open at any given time), we are going to ask the MDIClient
to worry about delivering the said message to all its children. We employ CWnd::FromHandle
, which is a static function, to get a CWnd
pointer to the MDIClient
, and then use CWnd::SendMessageToDescendants
to send the WM_SIZEPARENT
message to the MDI child windows. As may be guessed from its name, CWnd::SendMessageToDescendants
simply sends the specified messages to the given CWnd
's child windows, whichever windows those are.
Override CFrameWnd::RecalcLayout() to Send WM_SIZEPARENT
When the user hides or shows the toolbar(s) and the status bar, the framework calls CFrameWnd::RecalcLayout
to handle the repositioning of child windows and other elements of the main window. RecalcLayout
is a virtual function, which we may override. Since, resizing the main window also amounts to modifying its 'layout'. So let's make our override of RecalcLayout
be in charge of sending the WM_SIZEPARENT
messages to the MDI child windows currently in the main window.
Open up ClassWizard, and select the CMainFrame
class, and override RecalcLayout
. We are going to employ the approach mentioned above. Fill in the code shown below in bold:
Listing 5: Overriding CMDIFrameWnd::RecalcLayout
void CMainFrame::RecalcLayout(BOOL bNotify)
{
CMDIFrameWnd::RecalcLayout(bNotify);
if (::IsWindow(m_hWndMDIClient)) {
CWnd* pClientWnd = CWnd::FromHandle(m_hWndMDIClient);
pClientWnd->SendMessageToDescendants(WM_SIZEPARENT,
0, 0, FALSE, FALSE);
}
}
Note how we call IsWindow
to check that the m_hWndMDIClient
handle is valid, before proceeding. This is critical if your application automatically creates, e.g., a new document initially on startup. The first time RecalcLayout
is called by the framework is before the MDIClient
is created, so at this point, CWnd::FromHandle
will cause an exception if you try to get a CWnd*
pointer from the (now invalid) m_hWndMDIClient
handle.
Handle the WM_SIZE Message in CMainFrame
OK, so now if the user hides or shows the toolbar(s) or status bar, we are covered. What about if they move, resize, minimize, maximize, or restore the main window? Odds are nothing will happen. To update the centering of the MDI child window(s) open in the main window, we need to handle the WM_SIZE
message. In the message handler, we'll call our RecalcLayout
override to do the dirty work. Here's the code (you add the code shown in bold after using ClassWizard to create the handler):
Listing 6: Handling WM_SIZE
void CMainFrame::OnSize(UINT nType, int cx, int cy)
{
CMDIFrameWnd::OnSize(nType, cx, cy);
RecalcLayout();
}
That's it, we're done. The program should now center its MDI child window(s) upon creation. And resizing/moving etc. the window, or hiding or showing the toolbar(s) or status bar should leave the open MDI child window(s) unscathed.
Copyright
This article originally appeared in Visual C++ Developer, a monthly magazine produced by Pinnacle Publishing, in October 1998. This article content is under copyright of the author and may not be reprinted without express written permission.