zoukankan      html  css  js  c++  java
  • 新版本MenuDemo——使用Duilib模拟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消息。

                怎么样。这样一分析。菜单的实现是不是变得豁然开朗了?在实现级联菜单时再也不用考虑焦点的问题了,键盘事件的增加也变得易如反掌了。菜单效果实现的好固然重要。但更重要的是要好用。

    封装的好用不要用就看Duilib给不给力啦。经Skilla測试Duilib还是很给力的。哈哈,开个玩笑。

    Duilib里面的CContainerUI是个控件容器。作为显示使用的,我们就利用CContainerUI做我们的菜单容器。仅仅只是不直接显示了,我们就当做一个普通的数组来使用。为什么呢?当然是由于解析方便了。

    由于有级联的需求。菜单自然不是一个,我们就用CContainerUI作为我们的容器把,用CDialogBuilder解析出来。自然就是一个菜单数组了。

    这是菜单解析xml时的最外层的tag。为了给它加两个属性,我们继承CContainerUI。又一次弄一个MenuContainer。重写SetAttribute。加两个属性。一个是interval(两级菜单的间隔。假设不明确,自己改动试试),还有一个是keyevent这是一个bool属性(是否开启键盘)。

              MenuContainer里面就是我们的一个一个的菜单了,为了方便绘制,我们使用CListUI作为菜单的UI,以CListUI为基类派生CMenuUI,我们又能够重写SetAttribute给它加入我们须要的属性。这里我还是给他加入了两个属性。一个major(bool类型,是否为主菜单),一个bktrans(bool类型。是否开启透明),其它属性沿用CListUI的属性。

             CMenuUI里面就是一个一个的菜单项了,为了满足各种需求。Skilla设计了两种菜单项,一种叫做MenuItem(普通菜单项),还有一种叫做StaticMenuItem(静态菜单项),顾名思义,MenuItem是这真正的菜单项。鼠标指上去会有悬浮事件,按键盘上下。可以选中的菜单项;StaticMenuItem就是那些和菜单无关的控件,比方菜单分隔线,酷狗任务栏菜单上面的播放button,滑动条等。不偏不倚,Skilla相同赋予了MenuItem两个属性,一个comand(字符串,菜单消息循环退出时的返回值。这是菜单命令响应的根据),还有一个extendmenu(字符串,子菜单的name,这个是各个菜单级联的关键)。

              使用本次的MenuUI仅仅需引入两个文件,一个MenuUI.h和一个MenuUI.cpp。

    还有须要简单地改动一下源代码,1.依照skilla的上一篇文章。改动CPaintManagerUI。让它支持无焦点窗体   2.改动AttachDialog,由于CPaintManagerUI在析构时会把绑定到上面的控件数也析构掉。由于Menu的全部控件都是我们自己调用CDialogBuilder创建的,自然要我们自己销毁,所以为了不让 CPaintManagerUI干涉,我们加一个bool m_bAutoDeleteControls属性,并初始化为false。给AttachDialog加一个bool參数,默认值为true。函数体内赋值给m_bAutoDeleteControls,最后给CPaintManagerUI的析构函数里面的delete m_pRoot加上一个if (m_bAutoDeleteControls)推断。

    改动完以后,引入MenuUI.h和一个MenuUI.cpp这两个文件就能够使用了。

             最后谈一下Skilla对Windows界面编程的一些看法,如今开源的界面库尽管非常多,随便拿出一个来就能做出非常炫的界面。可是。我们不能过分迷恋不论什么界面库。既然玩的是Windows就不能忘本,你用的是Duilib也好,SOUI也罢。都须要细致去揣摩Windows API,由于我们的不论什么控件都是在模拟。还是那句话,没有哪款界面库是全然符合全部需求的,要想尽善尽美,还得自己动手。


       不好意思,因为上传之前没有严格測试,上次上传的Release版本号存在bug(原因是因为。加入SetUnfocusPaintWindow函数时。函数体写在头文件了,编译成Release库后出现了问题)。如今已经改动并又一次上传了一份,须要的朋友前来下载。

              源代码及Demo下载(假设有建议或者发现bug请直接留言。或者联系QQ:848861075(Skilla)谢谢!

            

             

    版权声明:本文博客原创文章,博客,未经同意,不得转载。

  • 相关阅读:
    centos 编码问题 编码转换 cd到对应目录 执行 中文解压
    centos 编码问题 编码转换 cd到对应目录 执行 中文解压
    centos 编码问题 编码转换 cd到对应目录 执行 中文解压
    Android MVP 十分钟入门!
    Android MVP 十分钟入门!
    Android MVP 十分钟入门!
    Android MVP 十分钟入门!
    mysql备份及恢复
    mysql备份及恢复
    mysql备份及恢复
  • 原文地址:https://www.cnblogs.com/gcczhongduan/p/4731923.html
Copyright © 2011-2022 走看看