Click here to Skip to main content
15,868,141 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 94.9K   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 
Ramon Casellas wrote:
LRESULT OnSetFocus(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
m_view.SetFocus();
return 0;
}

It assumes that your CChildFrame has a m_view member... What are the drawbacks of SetFocus(m_hWndClient) ? Is the Wizard generated file always using m_view ?


This was just an example of what you could do in CChildFrame. SetFocus(m_hWndClient) would work just as well, unless you've customized the frame to not use m_hWndClient for some reason (which I have seen before).

Ramon Casellas wrote:
ps: Regarding your CoolTab work, in general terms excellent, I'm experiencing some sporadic (I haven't been able to reproduce it ) behaviour. After a random amount of time, it just disappears. Have you noticed this ?

If you can reproduce it, be sure and send me a simple project demonstrating the problem! I'm pretty far on some enhancements to the CoolTab/CustomTab stuff. I have the VS.Net style of the scroll buttons and close button working. I've also done quite a bit of overhauling to make it act more like a common control (style bits, no need for REFLECT_NOTIFICATIONS, custom drawing like it's suppose to work, etc.). I'm trying to squeeze working on it between all of my other obligations. I'm working on the codeproject article at the moment!

Ramon Casellas wrote:
ps: How old is *your* CChildFrame ?

It's coming up on 3 years Smile | :)

-Daniel

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.