Click here to Skip to main content
Click here to Skip to main content

Center CMDIChildWnds and Other Tips

By , 6 Apr 2005
Rate this:
Please Sign up or sign in to vote.

Sample Image - CenterMDIWnd1.jpg

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() 
{
    // Call base class first
    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

// Generated message map functions
protected:
    //{{AFX_MSG(CChildFrame)
        // NOTE - the ClassWizard will add and remove member functions here.
        //    DO NOT EDIT what you see in these blocks of generated code!
    //}}AFX_MSG
    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)
    //{{AFX_MSG_MAP(CChildFrame)
        // NOTE - the ClassWizard will add and remove mapping macros here.
        //    DO NOT EDIT what you see in these blocks of generated code !
    //}}AFX_MSG_MAP
    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) 
{
    // Call base class first
    CMDIFrameWnd::RecalcLayout(bNotify);

    if (::IsWindow(m_hWndMDIClient)) {
        CWnd* pClientWnd = CWnd::FromHandle(m_hWndMDIClient);

        // Hopefully this should notify CMDIChildWnds that their
        // client area is changing size -- better re-center themselves!
        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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Brian C Hart
Software Developer (Senior) Corrugated Technologies, Inc.
United States United States
From Fridley, Minnesota and I like computer programming! When I got started, I was working mostly with Windows GUI programming in C/C++. Then later on I worked with COM/DCOM for a school internship. I used COM/DCOM to write an ad hoc cluster server and job-running environment for a cluster of 24 Windows-based high-end visualization workstations. I moved on to C# and have been working in C# and Windows Forms ever since. I have yet to embrace Silverlight Smile | :)
Follow on   Twitter

Comments and Discussions

 
Generalprinting from CScrollView Pinmembervikas amin3-Oct-05 3:09 
Generalcom PinmemberSantosh Kumar793-Oct-05 1:45 
GeneralRe: com PinmemberBankey Khandelwal10-Jan-06 19:38 
QuestionHow about maximize?? PinmemberTcpip200525-Apr-05 0:30 
AnswerRe: How about maximize?? Pinmembermhorowit26-Sep-07 18:54 
Generalhelp.i can't open the zip file. Pinmemberalidiedie25-Feb-03 2:42 
GeneralRe: help.i can't open the zip file. PinmemberBrian Hart7-Apr-05 11:07 
GeneralWow!!! PinmemberPJ Arends9-Apr-05 10:24 
Generalquestion, please help me out Pinsussquin21-Dec-02 18:26 
GeneralRe: question, please help me out PinmemberBrian Hart9-Apr-05 11:15 
GeneralAnother way... PinmemberRavi Bhavnani21-Oct-02 8:26 
GeneralRe: Another way... PinmemberFDogX31-Mar-04 20:43 
GeneralRe: Another way... PinmemberRavi Bhavnani1-Apr-04 1:34 
GeneralRe: Another way... PinmemberBrian Hart31-Mar-04 20:51 
GeneralRe: Another way... PinmemberRavi Bhavnani1-Apr-04 1:33 
Generalgood, ravi, simple and works fine ! Pinmembermichael zhao2-Apr-05 16:04 
GeneralRe: good, ravi, simple and works fine ! PinmemberBrian Hart3-Apr-05 16:15 
Generalok PinmemberStarodubtsev Sergey10-Feb-01 5:37 
ok. all good.
(but i can't read your zip-files (but this does mather))
Smile | :)

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140415.2 | Last Updated 7 Apr 2005
Article Copyright 2000 by Brian C Hart
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid