MFC Ribbon UI 弹出菜单实现分析
微软推出的Ribbon UI界面在应用程序开发上完全颠覆了以往的菜单+工具条的应用模式,非常有新颖性。Ribbon UI有一个弹出窗口功能让人感觉很疑惑,窗口截图如下:
通过spy4win工具抓取窗口信息发现这几个窗口都具有WS_POPUP属性,也就是说它是一个弹出窗口。支持鼠标、键盘按键,也有弹出窗口的特性“点窗口里面不会关闭,点外就会关闭”,让人疑惑的是这个弹出窗口还不会抢窗口焦点。理论上一个具有WS_POPUP属性的窗口一定要激活才能支持按键及点窗口外关闭这两个功能的,于是花了点时间研究了一下MFC 自带的Ribbon界面,分析了一下这个弹出窗口的实现原理,疑惑解决了。主要解决方法如下:
第一步:创建一个具有WS_POPUP属性的窗口并以不激活的方式显示创建的窗口
BOOL CMFCPopupMenu::Create(CWnd* pWndParent, int x, int y, HMENU hMenu, BOOL bLocked, BOOL bOwnMessage)
{
……
UINT nClassStyle = CS_SAVEBITS;
CString strClassName = ::AfxRegisterWndClass(nClassStyle, ::LoadCursor(NULL, IDC_ARROW), (HBRUSH)(COLOR_BTNFACE + 1), NULL);
……
DWORD dwStyle = WS_POPUP;
if (m_pMenuCustomizationPage != NULL)
{
dwStyle |= (WS_CAPTION | WS_SYSMENU);
}
……
CRect rect(x, y, x, y);
BOOL bCreated = CMiniFrameWnd::CreateEx(pWndParent->GetExStyle() & WS_EX_LAYOUTRTL, strClassName, m_strCaption,
dwStyle, rect, pWndParent->GetOwner() == NULL ? pWndParent : pWndParent->GetOwner());
……
SetWindowPos(&wndTop, -1, -1, -1, -1, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
……
return TRUE;
}
第二步:设置鼠标点击时不激活窗口,实现了即支持鼠标事件又不强焦点的效果
int CMFCPopupMenu::OnMouseActivate(CWnd* /*pDesktopWnd*/, UINT /*nHitTest*/, UINT /*message*/)
{
return MA_NOACTIVATE;
}
第三步:通过应用程序激活事件来关闭弹出窗口
void CMFCPopupMenu::OnActivateApp(BOOL bActive, DWORD /*dwThreadID*/)
{
if (!bActive && !CMFCToolBar::IsCustomizeMode() && !InCommand())
{
if (m_bTrackMode)
{
m_bTobeDstroyed = TRUE;
}
PostMessage(WM_CLOSE);
}
}
第四步:通过主窗口向弹出窗口发送键盘事件,实现了弹出窗口在不激活的时候还能处理按钮,在哪之前要确保弹出窗口时它的主窗口是激活的
BOOL CFrameImpl::ProcessKeyboard(int nKey, BOOL* pbProcessAccel)
{
……
if (CMFCPopupMenu::m_pActivePopupMenu != NULL && ::IsWindow(CMFCPopupMenu::m_pActivePopupMenu->m_hWnd))
{
……
CMFCPopupMenu::m_pActivePopupMenu->SendMessage(WM_KEYDOWN, nKey);
……
}
……
}
通过这四步就实现了可接收鼠标、键盘事件,又不抢焦点的弹出窗口。希望这个对一直被弹出窗口抢焦点这个问题困惑着的朋友有用。