65.9K
CodeProject 正在变化。 阅读更多。
Home

创建新的 MDI 子窗口:最大化和焦点问题

starIconstarIconstarIconstarIconstarIcon

5.00/5 (8投票s)

2002 年 4 月 5 日

3分钟阅读

viewsIcon

96798

downloadIcon

1325

在 WTL 应用程序中创建新的 MDI 子窗口时,当最后一个活动子窗口最大化时出现的问题和解决方案。

Sample Image - WTLMDIChildMax.png

引言

当您运行一个标准的 WTL 向导生成的 MDI 应用程序(WTL 3.1 和 7.0)时,您可能会注意到,当您最大化一个 MDI 子窗口,然后创建一个新的 MDI 子窗口时,所有的子窗口都会恢复到“已还原”状态。作为用户,您通常期望新创建的子窗口会像最后一个活动子窗口一样处于“最大化”状态。

为新的 MDI 子窗口保留最大化状态

MFC 应用程序在框架中深入处理了创建新框架窗口的问题。但是 MFC 的实现存在一个常见的“闪烁”问题,即在新的 MDI 子窗口最大化之前,您会看到它“已还原”位置的一瞥。

有很多方法可以解决这个问题(例如模仿 MFC 的行为),但这里有一个非常简单的解决方案,它效果很好,并且消除了创建过程中的闪烁。此更新实际上应该放在 CMDIChildWindowImpl::Create 中。在 WTL 的未来版本添加此功能之前,您可以在派生类(如 CChildFrame)中重写“Create”。另外请注意,CMDIChildWindowImpl::CreateEx 调用 pT->Create,因此在 CMDIChildWindowImpl 或您的派生类中更新的“Create”将被调用。

重写“Create”

在派生自 CMDIChildWindowImpl 的类(例如 CChildFrame)中,重写“Create”,但仍然调用 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;
}

替换 CMDIChildWindowImpl::Create

如果您打算更新 CMDIChildWindowImpl 的 Create 版本,这种方法是合适的,但也可以在派生类中使用。
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;
}

新最大化 MDI 子窗口的焦点

如果子窗口要以最大化状态开始生命周期,那么新的 MDI 子窗口的焦点也是一个问题。当一个新的 MDI 子窗口以“已还原”状态创建时,子框架会接收焦点。然后框架会反过来将焦点交给“视图”或“客户区”窗口。
(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;
}

但是,当 MDI 子框架最大化时,::IsWindowVisible(m_hWndClient) 返回 FALSE。因此 SetFocus(m_hWndClient) 不会被调用,子窗口也不会获得所需的焦点。将编辑控件作为视图窗口可以很好地演示焦点问题。

自行处理焦点

一个简单的解决方案是不要依赖 CFrameWindowImplBase 来处理 WM_SETFOCUS,而是在您的派生类中自行处理。例如,对于向导生成的 MDI 应用程序,您可以向 CChildFrame 的消息映射添加:

MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus)
并添加方法
LRESULT OnSetFocus(UINT /*uMsg*/, WPARAM /*wParam*/, 
                   LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
    m_view.SetFocus();
    return 0;
}
一个更永久的解决方案是在 CMDIChildWindowImpl 中处理 WM_SETFOCUS,甚至替换 CFrameWindowImplBase 的版本。不是检查 ::IsWindowVisible(m_hWndClient),而是可以只检查 ::IsWindow(m_hWndClient),如下所示:
LRESULT OnSetFocus(UINT, WPARAM, LPARAM, BOOL& bHandled)
{
    if(m_hWndClient != NULL && ::IsWindow(m_hWndClient))
        ::SetFocus(m_hWndClient);

    bHandled = FALSE;
    return 1;
}

注意:在上面的“替换 CMDIChildWindowImpl::Create”实现中,还有一个对 ::IsWindowVisible 的调用,可能应该改为 ::IsWindow。但是,如果您已经像上面列出的那样为子框架处理了 WM_FOCUS,这似乎并不是必需的。

试用

演示项目是用 Visual C++ 6.0 中的 WTL 7.0 向导创建的,选择了“MDI Application”作为应用程序类型。CChildFrame 已更新以解决新的子窗口最大化问题和焦点问题。我已将更新包装在两个 #define 中,您可以注释掉它们以查看旧行为 - _USE_NEW_CHILD_MAXIMIZATION_UPDATE__USE_FOCUS_UPDATE_

首先尝试运行带有修复程序的应用程序。

  • 启动应用程序
  • 通过“文件”->“新建”或单击工具栏上的“新建文件”按钮创建子窗口。
  • 最大化子窗口
  • 创建新的子窗口
  • 新创建的子窗口应最大化,并且编辑控件(子窗口的“视图”窗口)中的插入符号应在闪烁。

现在,在 ChildFrm.h 中注释掉“#define _USE_FOCUS_UPDATE_”,然后重新编译。

  • 启动应用程序
  • 通过“文件”->“新建”或单击工具栏上的“新建文件”按钮创建子窗口。
  • 最大化子窗口
  • 创建新的子窗口
  • 新创建的子窗口应最大化,但编辑控件没有焦点。

现在,在 ChildFrm.h 中注释掉“#define _USE_NEW_CHILD_MAXIMIZATION_UPDATE_”,然后重新编译。

  • 启动应用程序
  • 通过“文件”->“新建”或单击工具栏上的“新建文件”按钮创建子窗口。
  • 最大化子窗口
  • 创建新的子窗口
  • 新的子窗口和第一个子窗口都处于“已还原”状态,并且新子窗口的视图(编辑控件)获得了焦点。
© . All rights reserved.