MDI子窗口最大化与焦点处理

在开发使用WTL 3.1或7.0框架的多文档界面(MDI)应用程序时,可能会遇到一个问题:当最大化一个MDI子窗口后,再创建一个新的MDI子窗口,所有子窗口都会回到“还原”状态。通常情况下,用户期望新创建的子窗口应该像最后一个激活的子窗口那样处于“最大化”状态。

为了保持新MDI子窗口的最大化状态,需要在MFC应用程序的框架深处处理创建新框架窗口的问题。但MFC的实现存在一个常见的“闪烁”问题,即在新MDI子窗口最大化之前,用户会短暂看到其“还原”位置。

解决这个问题的方法有很多,比如模仿MFC的行为,但这里有一个简单且有效的解决方案,可以在创建过程中消除闪烁。这个更新应该位于CMDIChildWindowImpl::Create中。在WTL未来的版本中可能会添加这个功能,但在此之前,可以在派生类(如CChildFrame)中重写Create方法。同时注意,CMDIChildWindowImpl::CreateEx调用了pT->Create,所以在CMDIChildWindowImpl或派生类中更新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; }

如果打算更新CMDIChildWindowImplCreate版本,或者也可以在派生类中使用这种方法。

在处理新MDI子窗口最大化的同时,新MDI子窗口的焦点也是一个问题。当新MDI子窗口以“还原”状态创建时,子框架会接收到焦点。然后框架会将焦点交给“视图”或“客户端”窗口:

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,那么这似乎不是必要的。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485