Creating a new MDI child: maximization and focus issues
Issues and solutions when creating a new MDI child in a WTL application when the last active child was maximizedIntroduction
When you run a plain vanilla WTL wizard generated MDI application (WTL 3.1 and 7.0), you might have noticed that when you maximize an MDI child, then create a new MDI child, the children all go back to their "restored" state. What you'd typically expect as a user is that the new child would be in the "maximized" state just like the last active child had been.
Keeping maximization state for new MDI children
MFC apps deal with this deep in the framework for creating a new frame window. But the MFC implementation suffers from a common "flicker" problem where you catch a glimpse of the "restored" position of the new MDI child before its maximized.
There's lot's of ways to deal with this (such as mimicking MFC's behavior), but here's a very simple solution that works well, and eliminates the flicker during creation. The update really wants to live in CMDIChildWindowImpl::Create
. Until a future release of WTL adds this, you can override "Create" in your derived class (such as CChildFrame
). Also note that CMDIChildWindowImpl::CreateEx
calls pT->Create
, so an updated "Create
" in either CMDIChildWindowImpl
or your derived class will get called.
Override "Create"
In your class that derives fromCMDIChildWindowImpl
(such as CChildFrame
), override "Create", but still call CMDIChildWindowImpl::Create
:
typedef CMDIChildWindowImpl< ... > baseClass;
HWND Create(HWND hWndParent, _U_RECT rect = NULL, LPCTSTR szWindowName = NULL, DWORD dwStyle = 0, DWORD dwExStyle = 0, UINT nMenuID = 0, LPVOID lpCreateParam = NULL) { // NOTE: hWndParent is going to become m_hWndMDIClient // in CMDIChildWindowImpl::Create ATLASSERT(::IsWindow(hWndParent)); BOOL bMaximized = FALSE; HWND hWndOld = (HWND)::SendMessage(hWndParent, WM_MDIGETACTIVE, 0, (LPARAM)&bMaximized); if(bMaximized == TRUE) { ::SendMessage(hWndParent, WM_SETREDRAW, FALSE, 0); } HWND hWnd = baseClass::Create(hWndParent, rect, szWindowName, dwStyle, dwExStyle, nMenuID, lpCreateParam); if(bMaximized == TRUE) { ::ShowWindow(hWnd, SW_SHOWMAXIMIZED); ::SendMessage(hWndParent, WM_SETREDRAW, TRUE, 0); ::RedrawWindow(hWndParent, NULL, NULL, RDW_INVALIDATE | RDW_ALLCHILDREN); } return hWnd; }
Replace CMDIChildWindowImpl::Create
This approach is appropriate if you were going to updateCMDIChildWindowImpl
's version of Create, but can also be used in a deriving class.
HWND Create(HWND hWndParent, _U_RECT rect = NULL, LPCTSTR szWindowName = NULL, DWORD dwStyle = 0, DWORD dwExStyle = 0, UINT nMenuID = 0, LPVOID lpCreateParam = NULL) { ATOM atom = T::GetWndClassInfo().Register(&m_pfnSuperWindowProc); if(nMenuID != 0) m_hMenu = ::LoadMenu(_Module.GetResourceInstance(), MAKEINTRESOURCE(nMenuID)); dwStyle = T::GetWndStyle(dwStyle); dwExStyle = T::GetWndExStyle(dwExStyle); dwExStyle |= WS_EX_MDICHILD; // force this one m_pfnSuperWindowProc = ::DefMDIChildProc; m_hWndMDIClient = hWndParent; ATLASSERT(::IsWindow(m_hWndMDIClient)); if(rect.m_lpRect == NULL) rect.m_lpRect = &TBase::rcDefault; BOOL bMaximized = FALSE; HWND hWndOld = (HWND)::SendMessage(m_hWndMDIClient, WM_MDIGETACTIVE, 0, (LPARAM)&bMaximized); if(bMaximized == TRUE) { ::SendMessage(m_hWndMDIClient, WM_SETREDRAW, FALSE, 0); } HWND hWnd = CFrameWindowImplBase<TBase, TWinTraits >::Create( hWndParent, rect.m_lpRect, szWindowName, dwStyle, dwExStyle, (UINT)0U, atom, lpCreateParam); if(hWnd != NULL && ::IsWindowVisible(m_hWnd) && !::IsChild(hWnd, ::GetFocus())) ::SetFocus(hWnd); if(bMaximized == TRUE) { ::ShowWindow(hWnd, SW_SHOWMAXIMIZED); ::SendMessage(m_hWndMDIClient, WM_SETREDRAW, TRUE, 0); ::RedrawWindow(m_hWndMDIClient, NULL, NULL, RDW_INVALIDATE | RDW_ALLCHILDREN); } return hWnd; }
Focus for the new maximized MDI child
Focus for the new MDI child window also is an issue if the child is going to start out life maximized. When a new MDI child window is created in a "restored" state, the child frame receives the focus. The frame then turns around and gives the "view" or "client" window the focus:(CFrameWindowImplBase in atlframe.h) LRESULT OnSetFocus(UINT, WPARAM, LPARAM, BOOL& bHandled) { if(m_hWndClient != NULL && ::IsWindowVisible(m_hWndClient)) ::SetFocus(m_hWndClient); bHandled = FALSE; return 1; }
However, when the MDI child frame is maximized, ::IsWindowVisible(m_hWndClient) returns FALSE. So SetFocus(m_hWndClient) doesn't get called, and the child window doesn't get the focus like it wants. Having an edit control as the view window demonstrates the focus problem well.
Handle focus yourself
An easy solution is to not depend on CFrameWindowImplBase
handling WM_SETFOCUS
, and handle it in your derived class. For example, with a wizard generated MDI application, you could add to the message map of CChildFrame
:
MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus)and add the method:
LRESULT OnSetFocus(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/) { m_view.SetFocus(); return 0; }A more permanent solution would be to handle WM_SETFOCUS in
CMDIChildWindowImpl
or even replace the CFrameWindowImplBase
version. Instead of checking for ::IsWindowVisible(m_hWndClient)
, you could just check for ::IsWindow(m_hWndClient)
, like so:
LRESULT OnSetFocus(UINT, WPARAM, LPARAM, BOOL& bHandled) { if(m_hWndClient != NULL && ::IsWindow(m_hWndClient)) ::SetFocus(m_hWndClient); bHandled = FALSE; return 1; }
Note: In the "Replace CMDIChildWindowImpl::Create
" implementation above, there's also a call to ::IsWindowVisible
that should probably be changed to ::IsWindow
. It doesn't seem to be necessary though if you're already handling WM_FOCUS
for the child frame as listed above.
Try it out
The demo project was created with the WTL 7.0 wizard in Visual C++ 6.0, choosing "MDI Application" as the type of application. CChildFrame
has been updated to address the new child maximization issue and the focus issue. I've wrapped the updates in two #define
's that you can comment out to see the old behavior - _USE_NEW_CHILD_MAXIMIZATION_UPDATE_
and _USE_FOCUS_UPDATE_
.
First try to run the application with the fixes.
- Start the application
- Create a child window either through File->New or clicking on the "New File" button on the toolbar.
- Maximize the child window
- Create a new child window
- The new child should be maximized, and the caret in the edit control (the child's "view" window) should be blinking
Now comment out "#define _USE_FOCUS_UPDATE_
" in ChildFrm.h, and recompile.
- Start the application
- Create a child either through File->New or clicking on the "New File" button on the toolbar.
- Maximize the child window
- Create a new child window
- The new child should be maximized, but the edit control does not have focus
Now comment out "#define _USE_NEW_CHILD_MAXIMIZATION_UPDATE_
" in ChildFrm.h, and recompile.
- Start the application
- Create a child either through File->New or clicking on the "New File" button on the toolbar.
- Maximize the child window
- Create a new child window
- Both the new child and the first child are in their "restored" state, and the new child's view (edit control) has the focus