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
Collapse
Copy
Code
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
Collapse
Copy
Code
#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
Collapse
Copy
Code
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
Collapse
Copy
Code
BEGIN_MESSAGE_MAP(CChildFrame, CMDIChildWnd)
ON_MESSAGE(WM_SIZEPARENT, OnSizeParent)
END_MESSAGE_MAP()
Collapse
Copy
Code
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
ucancode.net 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
Collapse
Copy
Code
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
Collapse
Copy
Code
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.