DUILIB创建菜单窗口

转自:http://blog.163.com/zwei3666@126/blog/static/993128052012101272027399/

http://blog.163.com/zwei3666@126/blog/static/99312805201210198427794/

先发一张效果图。支持多级扩展,支持菜单表头,支持文本居中/左对齐/右对齐三种方式,支持字体颜色设置,支持添加自定义控件,支持基本属性(背景颜色,图片,图标,宽高等)的自定义设置。

DuiLib创建菜单窗口 - 諾ベ撧 - zwei3666的博客
 
漂亮不? 咳~一般般吧~
其实DuiLib里已经有个MenuDemo 不过那个是加载xml布局文件的。当然直接加载xml对谁来说都方便很多。可继承来继承去,还用到了模板,好难呐!!!!。。。。。。。。
  刚写好,粗糙了些,勉强用吧

 菜单吧也就那么回事。

主要功能类:

*  CMenuUI菜单窗口类。用于创建菜单窗口,显示菜单选项。
*   MenuElementUI选项类。创建菜单选项
*   WndsVector窗口队列。用于管理所有菜单窗口的显示和隐藏

先看框架流程:

DuiLib创建菜单窗口 - 諾ベ撧 - zwei3666的博客

整体流程并不复杂,但是如何处理窗口选项的扩展窗口(即二级菜单)的显示与隐藏的问题呢?仔细想一下,不难发现问题关键就在于这两个消息循环。

先罗列出涉及窗口创建与销毁、显示与隐藏的地方:

1. 创建菜单时,只显示主菜单,所有扩展窗口全部隐藏

2.当鼠标进入有扩展窗口的选项时,显示扩展窗口(消息:UIEVENT_MOUSEENTER)

3.当鼠标离开选项时,隐藏扩展窗口(UIEVENT_MOUSELEAVE)

4.当鼠标点击选项时,如果选项没有扩展窗口,则销毁整个菜单。并传出该选项对应的ID。(UIEVENT_BUTTONDOWN)

5.当用户在菜单窗口之外的任何地方点击鼠标时,销毁整个菜单。(窗口消息:WM_KILLFOCUS)

以上五点是一种很正常的思路,但是后来在处理鼠标离开选项的时候我发现鼠标离开的时候它可能是离开菜单窗口也可能只是进入下一选项,如何判断着实让我狠狠地纠结了一番。。后来仔细看了demo才幡然醒悟鼠标离开菜单窗口的时候我是不需要做处理的。。。。。。而对于另一种,鼠标离开这个选项必然进入下一选项,只处理一个就已经非常合适了。。于是2和3合二为一了~~~~即变成“当鼠标进入选项时,关闭扩展窗口,再判断它是否有扩展,有的话再重新显示”,这样讲,好懂不?

为了方便处理窗口一切活动,我将创建的所有窗口的指针压入队列Vector:WndVector中便于后续处理。~~~当然,是创建一个压入一个,不会漏了~~~也不会重了~~~因为这个队列与用户无关(不是用户创建也不是用户清理)的,它是专门管理窗口类的,它是惟一的,所以这里将这个类设成了静态类~所有方法均为静态~这样无需创建实例就可以直接使用~

这个为了降低不同类之间的耦合度,也为了方便处理窗口的显示与消失。。。咳。。总之我创建了一个信号数据结构体:

 struct ContextMenuParam
 {
  HWND hWnd;
  WPARAM wParam; // 取值为1时,表示销毁整个菜单, 取值为2时,表示只将hwnd及其所有父窗口显示
 };

在需要的地方给队列发这么一个信号然后自己该干嘛还干嘛是不是挺干脆利落的?反正我是这么觉得的。。嘿嘿~~

贴一下这个管理类的代码`~~

 class WndsVector
 {
 private:
  typedef std::vector<CMenuUI*> ReceiversVector;
  static ReceiversVector receiverWnd;
 public:
  static HWND rootHwnd;//专门记录创建菜单的主窗口,为了方便在鼠标点击选项时直接传给主窗口ID号,免得计算。。。。

  /*****
  * 功    能:将窗口指针压入窗口队列
  * 参    数:ptr窗口指针
  * 返 回 值:
  ******/
  static inline void AddWnd(CMenuUI* ptr)
  {
   receiverWnd.push_back(ptr);
  }

  /*****
  * 功    能:将窗口信号发送给队列中的每一个窗口
  * 参    数:param窗口信号
  * 返 回 值:
  ******/
  static void BroadCast(ContextMenuParam &param)
  {
   if (receiverWnd.empty())
    return;
   ReceiversVector::reverse_iterator it = receiverWnd.rbegin();
   for (; it != receiverWnd.rend(); it ++)
   {
    if ( *it != NULL )
     (*it)->ReceiveCloseMsg(param);
   }
  }

  /*****
  * 功    能:查找窗口队列中包含该句柄的窗口类指针
  * 参    数:hwnd需要查找的句柄
  * 返 回 值:找到返回对应窗口类指针,否则返回null
  ******/
  static CMenuUI* FindWndClass(HWND hwnd)
  {
   if (receiverWnd.empty())
    return NULL;
   ReceiversVector::iterator it = receiverWnd.begin();
   for (; it != receiverWnd.end(); it ++)
   {
    if (*it != NULL && (*it)->GetHWND() == hwnd)
     return *it;
   }
   return NULL;
  }


  /*****
  * 功    能:清空队列
  ******/
  static inline void RemoveAll()
  {
   receiverWnd.clear();
  }
 };

好了,说一下对于这四个问题的处理思路:

1. 创建菜单时,只显示主菜单,所有扩展窗口全部隐藏

           唔~这个只要让主窗口在创建完成的时候不设置所有扩展窗口的位置和大小,那么它们的大小就会默认为0~~位置也是在0,0

2.当鼠标进入选项时,关闭扩展窗口,再判断它是否有扩展,有的话再重新显示        

        进入选项时先判断一下当前选择是否跟上次的选项相同~~避免鼠标进入选项然后离开窗口转一下再直接进入这个选项~因为这种情况完全不用做任何处理嘛~~~如果不相同,给管理队列发送信号{pManager.GetPaintWindow(), 2},调用WndsVector.BroadCast(信号),将管理类负责把信号传给所有窗口,让它们自己判断是不是pManager.GetPaintWindow()的爸爸或爷爷~不是就隐藏,否则不处理~~~接着判断它有没有扩展窗口,有就显示就可以了

3.当鼠标点击选项时,如果选项没有扩展窗口,则销毁整个菜单。并传出该选项对应的ID。(UIEVENT_BUTTONDOWN)

        发送信号{NULL, 1}。这里都要全部销毁了,给个自杀信号就可以了,没必要同去传句柄啦

        传ID。PostMessage(WndsVector::rootHwnd, uID, event.wParam, event.lParam);之所以不用sendmessage是因为这里只管发送这个ID,发送完自己它就要自我销毁,postMessage不用返回,将消息加入进程的消息队列等行即可

4.当用户在菜单窗口之外的任何地方点击鼠标时,销毁整个菜单。(窗口消息:WM_KILLFOCUS)

           这个做个检测就可以了


  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值