Click here to Skip to main content
16,017,261 members
Articles / Desktop Programming / WTL
Article

Creating a new MDI child: maximization and focus issues

Rate me:
Please Sign up or sign in to vote.
5.00/5 (8 votes)
4 Apr 20023 min read 96K   1.3K   22   3
Issues and solutions when creating a new MDI child in a WTL application when the last active child was maximized

Sample Image - WTLMDIChildMax.png

Introduction

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 from CMDIChildWindowImpl (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 update CMDIChildWindowImpl'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

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


Written By
Architect
United States United States
Daniel Bowen used to work as a Software Engineer for Evans & Sutherland in Salt Lake City, Utah working on the modeling tools for high end flight simulators. He then worked for the startup company WiLife Inc. in Draper, Utah working on the software portion of an easy to use and affordable digital video surveillance system. WiLife Inc. is now part of Logitech Inc.

Comments and Discussions

 
Generalit's just what i want! thx! Pin
iamduyu11-May-07 21:48
iamduyu11-May-07 21:48 
GeneralMinor detail Pin
Ramon Casellas6-Jun-02 5:29
Ramon Casellas6-Jun-02 5:29 
GeneralRe: Minor detail Pin
Daniel Bowen6-Jun-02 7:55
Daniel Bowen6-Jun-02 7:55 

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

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