当前位置:首页 > Windows程序 > 正文

使用Duilib模拟Windows本机菜单

2021-03-27 Windows程序

相信玩Duilib朋友已经开始期待一个很长的文章。由于我的文章在一周前公布——“无焦点窗体的实现”里面提到了无焦点窗体在菜单里面的应用,并承诺大家,写一个关于Menu实现的Demo分享给大家。

先上几张截图,看一下效果

技术分享

技术分享

技术分享


怎么样,Skilla这次的作品还能让你心动吧。没错。上面的菜单效果可不是酷狗里面的截图,就是我们熟悉的Duilib实现的。

说起菜单,我们都想起了原版的MenuDemo,凡是阅读过原版MenuDemo的朋友,应该非常清楚它的原理。它是利用焦点来控制菜单何销毁。焦点的介入给级联菜单的维护造增添了不少难度,并且无谓的焦点切换也是一种浪费。还有就是键盘事件怎样增加也是一个非常棘手的问题。

这次Skilla提供的MenuDemo是无焦点的,使它用起来更加自然,更加原生态。关于DirectUI的菜单。我们把重点放在“模拟”两个字上。在菜单实现时。不但要模拟出表象。更要模拟出本质。

我们在拿窗体去模拟菜单时。一定要先摸Windows原生菜单实现的内部原理,否则会走非常多弯路。

以下先介绍一下Windows原生菜单的机制,先上一段代码。


int CDuiMenu::RunMenu() { int nRet(-1); BOOL bMenuDestroyed(FALSE); BOOL bMsgQuit(FALSE); while(TRUE) { if(m_menuRet.bExit) { nRet = 0; break; } if(GetForegroundWindow() != m_hWndOwner) { break; } BOOL bInterceptOther(FALSE); MSG msg = {0}; if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { if(msg.message == WM_KEYDOWN || msg.message == WM_SYSKEYDOWN || msg.message == WM_KEYUP || msg.message == WM_SYSKEYUP || msg.message == WM_CHAR || msg.message == WM_IME_CHAR) { //transfer the message to menu window if (m_menus->IsKeyEvent()) { SKILL_ASSERT(m_pKeyEvent->GetMenuWnd()->GetHWND()); msg.hwnd = m_pKeyEvent->GetMenuWnd()->GetHWND(); } } else if(msg.message == WM_LBUTTONDOWN || msg.message == WM_RBUTTONDOWN || msg.message == WM_NCLBUTTONDOWN || msg.message == WM_NCRBUTTONDOWN ||msg.message ==WM_LBUTTONDBLCLK) { //click on other window if(!IsMenuWnd(msg.hwnd)) { DestroyMenu(); //为了和菜单再次的弹出消息同步 ::PostMessage(msg.hwnd,msg.message,msg.wParam,msg.lParam); bInterceptOther = true; bMenuDestroyed = TRUE; } }else if (msg.message == WM_LBUTTONUP ||msg.message==WM_RBUTTONUP ||msg.message==WM_NCLBUTTONUP ||msg.message==WM_NCRBUTTONUP ||msg.message==WM_CONTEXTMENU) { if(!IsMenuWnd(msg.hwnd)) { //防止菜单同一时候弹出多个 ::PostMessage(msg.hwnd,msg.message,msg.wParam,msg.lParam); break; } } else if(msg.message == WM_QUIT) { bMsgQuit = TRUE; } //拦截非菜单窗体的MouseMove消息 if (msg.message == WM_MOUSEMOVE) { if (!IsMenuWnd(msg.hwnd)) { bInterceptOther=TRUE; } } if (!bInterceptOther) { TranslateMessage (&msg); DispatchMessage (&msg); } } else { MsgWaitForMultipleObjects (0, 0, 0, 10, QS_ALLINPUT); } if(bMenuDestroyed) break; if(bMsgQuit) { PostQuitMessage(msg.wParam); break; } } if(!bMenuDestroyed) { DestroyMenu(); } return nRet; }
             我们都知道,焦点时键盘消息的“舞台”。要说菜单没有焦点。那么我们在按键盘上的,VK_UP,VK_DOWN,VK_LEFT,VK_RIGHT,VK_RETURN时,为什么菜单会有反应?可能你还不信。菜单在弹出时,内部是堵塞模式的,它会像DoModel一样,在里面开启一个while循环。这样我们就能够随心所欲地控制消息走向了。当菜单弹出时。消息循环建立。仅仅要是该进程的消息,无论是哪个窗体的都会到这里来,当收到键盘类消息时。无论是哪个窗体的,都分配给菜单窗体;当鼠标键按下时,推断是不是菜单窗体的,仅仅要不是,菜单销毁,把从消息队列取出的消息再次扔给消息队列,循环退出。鼠标键弹起时,仅仅要不是菜单窗体的,取出消息。扔回消息队列(就像漂流瓶一样,捞上来了发现不须要又给扔回去,事实上这样做的目的是为了,给菜单本次的销毁和下次再次弹出做一个过渡。否则上次的还没有销毁,这次的就弹出。就乱套了)。当有MouseMove消息时,仅仅要不是菜单窗体的,通通拦下,由于菜单弹出时,其它窗体的MouseMove事件会屏蔽掉。不信看一下Windows的原生菜单是不是有这个效果;当Owner窗体从前台切换到后台时,菜单相同要销毁,循环退出;当MenuItem收到点击事件时,获取菜单命令Id,并通知菜单的消息循环结束,返回菜单命令Id并转发给Owner窗体WM_COMMAD消息。

温馨提示: 本文由Jm博客推荐,转载请保留链接: https://www.jmwww.net/file/68424.html