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

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

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvU2tpbGxh/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">

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

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

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

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

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

  1. int CDuiMenu::RunMenu()
  2. {
  3.  
  4. int nRet(-1);
  5. BOOL bMenuDestroyed(FALSE);
  6. BOOL bMsgQuit(FALSE);
  7. while(TRUE)
  8. {
  9.  
  10. if(m_menuRet.bExit)
  11. {
  12. nRet = 0;
  13. break;
  14. }
  15.  
  16. if(GetForegroundWindow() != m_hWndOwner)
  17. {
  18. break;
  19. }
  20. BOOL bInterceptOther(FALSE);
  21. MSG msg = {0};
  22. if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
  23. {
  24. if(msg.message == WM_KEYDOWN
  25. || msg.message == WM_SYSKEYDOWN
  26. || msg.message == WM_KEYUP
  27. || msg.message == WM_SYSKEYUP
  28. || msg.message == WM_CHAR
  29. || msg.message == WM_IME_CHAR)
  30. {
  31. //transfer the message to menu window
  32. if (m_menus->IsKeyEvent())
  33. {
  34. SKILL_ASSERT(m_pKeyEvent->GetMenuWnd()->GetHWND());
  35. msg.hwnd = m_pKeyEvent->GetMenuWnd()->GetHWND();
  36. }
  37. }
  38. else if(msg.message == WM_LBUTTONDOWN
  39. || msg.message == WM_RBUTTONDOWN
  40. || msg.message == WM_NCLBUTTONDOWN
  41. || msg.message == WM_NCRBUTTONDOWN
  42. ||msg.message ==WM_LBUTTONDBLCLK)
  43. {
  44.  
  45. //click on other window
  46. if(!IsMenuWnd(msg.hwnd))
  47. {
  48. DestroyMenu();
  49. //为了和菜单再次的弹出消息同步
  50. ::PostMessage(msg.hwnd,msg.message,msg.wParam,msg.lParam);
  51. bInterceptOther = true;
  52. bMenuDestroyed = TRUE;
  53.  
  54. }
  55. }else if (msg.message == WM_LBUTTONUP
  56. ||msg.message==WM_RBUTTONUP
  57. ||msg.message==WM_NCLBUTTONUP
  58. ||msg.message==WM_NCRBUTTONUP
  59. ||msg.message==WM_CONTEXTMENU)
  60. {
  61. if(!IsMenuWnd(msg.hwnd))
  62. {
  63. //防止菜单同一时候弹出多个
  64. ::PostMessage(msg.hwnd,msg.message,msg.wParam,msg.lParam);
  65. break;
  66.  
  67. }
  68.  
  69. }
  70. else if(msg.message == WM_QUIT)
  71. {
  72.  
  73. bMsgQuit = TRUE;
  74. }
  75.  
  76. //拦截非菜单窗体的MouseMove消息
  77. if (msg.message == WM_MOUSEMOVE)
  78. {
  79. if (!IsMenuWnd(msg.hwnd))
  80. {
  81. bInterceptOther=TRUE;
  82. }
  83. }
  84.  
  85. if (!bInterceptOther)
  86. {
  87. TranslateMessage (&msg);
  88. DispatchMessage (&msg);
  89. }
  90.  
  91. }
  92. else
  93. {
  94. MsgWaitForMultipleObjects (0, 0, 0, 10, QS_ALLINPUT);
  95. }
  96.  
  97. if(bMenuDestroyed) break;
  98.  
  99. if(bMsgQuit)
  100. {
  101. PostQuitMessage(msg.wParam);
  102. break;
  103. }
  104. }
  105.  
  106. if(!bMenuDestroyed)
  107. {
  108. DestroyMenu();
  109. }
  110. return nRet;
  111. }

我们都知道,焦点时键盘消息的“舞台”。要说菜单没有焦点。那么我们在按键盘上的,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)谢谢!

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

新版本MenuDemo——使用Duilib模拟Windows本机菜单的更多相关文章

  1. PCB 工程系统 模拟windows域帐号登入

    一.需求描述: 对于PCB制造企业来说,基本都采用建立共享目享+域名管控权限,好像别的大多数行业都是这样的吧.呵呵 在实际应用中,经常会有这样的问题,自己登入的帐号没有共享目录的权限,但又想通过程序实 ...

  2. windows客户机连接gerrit的一个报错处理

    gerrit环境部署在linux服务器,windos客户机连接gerrit进行代码操作: 在windows客户机下载Git客户端 在“Git Bash”里使用 ”ssh-keygen -t rsa - ...

  3. 刚查了,Z3795不支持EPT,即WP8开发必须的SLAT,看来只能作为简单的WINDOWS备机了

    刚查了,Z3795不支持EPT,即WP8开发必须的SLAT,看来只能作为简单的WINDOWS备机了,也就只能做做文档编辑,脚本编写之类的. 数据来源 http://ark.intel.com/zh-C ...

  4. Ext js框架模拟Windows桌面菜单管理模板

    一款超炫的后台,Ext模拟Windows桌面,Ext经典浅蓝风格,功能非常强大,包括最大化.最小化.状态栏.桌面图标等,不过需要非常懂Ext脚本的才可驾驭它.​ 1.图片 ​2. [代码][HTML] ...

  5. PCB 模拟Windows管理员域帐号安装软件

    在我们PCB行业中,局域网的电脑一般都会加入域控的,这样可以方便集中管理用户权限,并可以对访问网络资源可以进行权限限制等. 由于加入了域控对帐号权限的管理,这样一来很多人都无权限安装软件,比如:PCB ...

  6. openstack环境-解决windows虚机重启后比当前时间晚8小时问题

    背景: 生产环境下,发现windows虚机每次重启,时间都会倒退到虚机的格林威治时间(+8小时才是北京时间),也就是比当前时间晚8小时.测试发现,windows虚机所用的镜像,缺少了一个os_type ...

  7. linux-Redhat7 windows物理机与虚拟机设置共享目录

    一                   windows物理机与虚拟机设置共享目录 1.1        WMware Workstation点击重新安装WMware Tools 此时会弹出在客户机装载 ...

  8. jQuery插件:模拟select下拉菜单

    没搞那么复杂,工作中,基本够用.. <!doctype html> <html> <head> <meta charset="utf-8" ...

  9. 如何定制Windows系统右键菜单

    今天心血来潮把几个自己常用的工具定制到了系统的右键菜单.包括notepad++,7zip,还有复制文件全路径和文件夹路径.下面简单介绍一下步骤. 1. Windows系统右键菜单对应的注册表位置 Wi ...

随机推荐

  1. [Angular2 Animation] Control Undefined Angular 2 States with void State

    Each trigger starts with an “undefined” state or a “void” state which doesn’t match any of your curr ...

  2. [CSS] Use Generated Content to Augment Information

    When you create a pseudo element, you have access to the parent HTML attributes. They can be used in ...

  3. Kinect for Xbox one(v2) + Ubuntu 14.04 +ROS 安装

    相比于kinect for xbox 360(v1)通过结构光来获取深度,Kinect for Xbox one(v2) 采用time flight技术,极大改善了深度图像的性能. kinect fo ...

  4. ZOJ 1203 Swordfish MST

    http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=1203 大意: 给出一些点,求MST 把这几天的MST一口气发上来. kru ...

  5. [Angular] Using useExisting provider

    Unlike 'useClass', 'useExisting' doesn't create a new instance when you register your service inside ...

  6. 【C++竞赛 G】Lines

    Time Limit: 3s Memory Limit: 64MB 问题描述 Ljr has several lines. The lines are covered on the X axis. L ...

  7. Android 软键盘监听事件

    Android软键盘的隐藏显示研究 Android是一个针对触摸屏专门设计的操作系统,当点击编辑框,系统自动为用户弹出软键盘,以便用户进行输入.     那么,弹出软键盘后必然会造成原有布局高度的减少 ...

  8. 微信小程序从零开始开发步骤(二)

    上一章注册完小程序,添加新建的项目,大致的准备开发已经完成,本章要分享的是要创建一个简单的页面了,创建小程序页面的具体几个步骤: 1. 在pages 中添加一个目录 选中page,右击鼠标,从硬盘打开 ...

  9. 【t030】数字构造

    Time Limit: 3 second Memory Limit: 256 MB [问题描述] 有这么一个游戏: 写出一个1-N的排列a[i],然后每次将相邻两个数相加,构成新的序列,再对新序列进行 ...

  10. MySQL经常使用的面试题

    1.怎样登陆mysql数据库 MySQL -u username -p 2.怎样开启/关闭mysql服务 service mysql start/stop 3.查看mysql的状态 service m ...