在开发使用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;
}
如果打算更新CMDIChildWindowImpl
的Create
版本,或者也可以在派生类中使用这种方法。
在处理新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
,那么这似乎不是必要的。