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





5.00/5 (8投票s)
2002 年 4 月 5 日
3分钟阅读

96798

1325
在 WTL 应用程序中创建新的 MDI 子窗口时,
引言
当您运行一个标准的 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_
”,然后重新编译。
- 启动应用程序
- 通过“文件”->“新建”或单击工具栏上的“新建文件”按钮创建子窗口。
- 最大化子窗口
- 创建新的子窗口
- 新的子窗口和第一个子窗口都处于“已还原”状态,并且新子窗口的视图(编辑控件)获得了焦点。