首页 > 其他分享 >Creating a new MDI child: maximization and focus issues

Creating a new MDI child: maximization and focus issues

时间:2022-12-13 14:15:35浏览次数:75  
标签:MDI Creating Create maximization window child new NULL

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 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

标签:MDI,Creating,Create,maximization,window,child,new,NULL
From: https://www.cnblogs.com/qzxff/p/16978590.html

相关文章