如何把菜单栏和标题栏合为一体

你会发现,现在越来越多的桌面应用程序将菜单栏和标题栏合为一体。要实现这种效果,一般有两种方案:

  1. 将菜单绘制到标题栏上
  2. 移除标题栏,把菜单栏当标题用

众所周知,标题栏和菜单栏都是典型的非客户区,而在Windows平台上,非客户区的自绘对很多程序猿来说,真的是痛苦不堪啊。因此方案一自然而然就被淘汰了。既然如此,那么如何用方案二实现我们要的效果呢?

这里,先把“效果”简单说下:应用程序的界面上,标题栏和菜单栏在同一个区域显示,用户点击“菜单区域”,弹出对应的菜单;用户点击其他空白区域,如同点击了标题栏 (表述不清,不过我想大家都理解了吧……)。

方案二里,实际上包含了两个要点:

  1. 移除标题栏
  2. 用菜单栏模拟标题栏

如何移除标题栏,这个简单,移除WS_CAPTION窗口风格即可。不过要注意的是,不要忘记调用SetWindowPos并传入SWP_FRAMECHANGED标志位。那么如何让菜单栏模拟标题栏的功能呢?说到这里,要先补充一点基础知识。

在Windows上,你如何知道鼠标所在的点位于当前窗口的哪一个部分?这要借助一个消息:WM_NCHITTEST。通过这个消息,我们知道一个窗口被细分出了20多个区域,其中有两个就是我们现在感兴趣的,HTMENU和HTCAPTION。现在想法来了,在自己处理WM_NCHITTEST消息时,如果能把系统返回的部分HTMENU转化成HTCAPTION,是不是就能达到模拟标题栏的效果了呢?事实告诉我们,基本上是达到了。代码如下:

   1: UINT Cls_OnNCHitTest(HWND hwnd, int x, int y)
   2: {
   3:     UINT uNcHitResult = FORWARD_WM_NCHITTEST(hwnd, x, y, DefWindowProc);
   4:  
   5:     if (HTMENU == uNcHitResult)
   6:     {
   7:         HMENU hMenu = GetMenu(hwnd);
   8:         int nMenuItemCount = GetMenuItemCount(hMenu);
   9:  
  10:         RECT rcTheLastMenu = { 0, 0, 0, 0 };
  11:         if (GetMenuItemRect(hwnd, hMenu, nMenuItemCount - 1, &rcTheLastMenu))
  12:         {
  13:             // 当点击在菜单空白区域时,模拟成标题栏行为
  14:             if (rcTheLastMenu.right < x)
  15:             {
  16:                 uNcHitResult = HTCAPTION;
  17:             }
  18:         }
  19:     }
  20:  
  21:     return uNcHitResult;
  22: }

经过这样的处理后,单击/双击菜单栏空白区域,以及拖拽等都好像是在操作标题栏一样。

写完这些代码之后,你会发现几个问题:

  1. 右键单击菜单栏空白区域看不到系统菜单
  2. 最大化后全屏了,窗口覆盖了任务栏

所以说,这种模拟方式只是基本实现了功能,还没有完全达到要求。现在我们来一步一步解决这些问题。先看系统菜单。

要解决系统菜单的问题,首先要知道怎么获得系统菜单。这个可以通过GetSystemMenu的方式来实现。

接下来,在什么时候弹出菜单?这个问题也简单,一般我们选用WM_NCRBUTTONUP。怎么弹的问题交给TrackPopupMenuEx。代码如下:

   1: void Cls_OnNCRButtonUp(HWND hwnd, int x, int y, UINT codeHitTest)
   2: {
   3:     if (HTCAPTION == codeHitTest)
   4:     {
   5:         HMENU hSysMenu = GetSystemMenu(hwnd, FALSE);
   6:         int nMenuAlign = GetSystemMetrics(SM_MENUDROPALIGNMENT);
   7:  
   8:         // 特别注意TPM_RETURNCMD,我们需要知道用户到底点中了哪一个系统菜单项
   9:         BOOL bRes = TrackPopupMenuEx(hSysMenu, nMenuAlign | TPM_RETURNCMD, x, y, hwnd, NULL);
  10:         return FORWARD_WM_SYSCOMMAND(hwnd, bRes, x, y, DefWindowProc);
  11:     }
  12:  
  13:     FORWARD_WM_NCRBUTTONUP(hwnd, x, y, codeHitTest, DefWindowProc);
  14: }

到目前为止,我们看似已经把菜单的问题全部解决了。但事实上,问题还是有一点的。系统菜单项的状态好像有点问题。要解决这个问题,我们要借助WM_INITMENU消息,通过当前窗口的最大化或最小化状态,挑战对应的菜单项状态。请看代码:

   1: void Cls_OnInitMenu(HWND hwnd, HMENU hMenu)
   2: {
   3:     if (GetSystemMenu(hwnd, FALSE) == hMenu)
   4:     {
   5:         UINT uSysCmds[] = {SC_SIZE, SC_MOVE, SC_MINIMIZE, SC_MAXIMIZE, SC_CLOSE, SC_RESTORE};
   6:         if (IsIconic(hwnd))
   7:         {
   8:             for (int i = 0; i < _countof(uSysCmds); ++i)
   9:             {
  10:                 switch (uSysCmds[i])
  11:                 {
  12:                 case SC_MAXIMIZE:
  13:                 case SC_RESTORE:
  14:                 case SC_CLOSE:
  15:                     EnableMenuItem(hMenu, uSysCmds[i], MF_ENABLED);
  16:                     break;
  17:  
  18:                 default:
  19:                     EnableMenuItem(hMenu, uSysCmds[i], MF_DISABLED | MF_GRAYED);
  20:                     break;
  21:                 }
  22:             }
  23:         }
  24:         else if (IsZoomed(hwnd))
  25:         {
  26:             for (int i = 0; i < _countof(uSysCmds); ++i)
  27:             {
  28:                 switch (uSysCmds[i])
  29:                 {
  30:                 case SC_RESTORE:
  31:                 case SC_MINIMIZE:
  32:                 case SC_CLOSE:
  33:                     EnableMenuItem(hMenu, uSysCmds[i], MF_ENABLED);
  34:                     break;
  35:  
  36:                 default:
  37:                     EnableMenuItem(hMenu, uSysCmds[i], MF_DISABLED | MF_GRAYED);
  38:                     break;
  39:                 }
  40:             }
  41:         }
  42:         else
  43:         {
  44:             for (int i = 0; i < _countof(uSysCmds); ++i)
  45:             {
  46:                 switch (uSysCmds[i])
  47:                 {
  48:                 case SC_RESTORE:
  49:                     EnableMenuItem(hMenu, uSysCmds[i], MF_DISABLED | MF_GRAYED);
  50:                     break;
  51:  
  52:                 default:
  53:                     EnableMenuItem(hMenu, uSysCmds[i], MF_ENABLED);
  54:                     break;
  55:                 }
  56:             }
  57:         }
  58:     }
  59:     else
  60:     {
  61:         FORWARD_WM_INITMENU(hwnd, hMenu, DefWindowProc);
  62:     }
  63: }

大功告成啦。唉,好像还有点不对,第一次弹出的系统菜单状态好像有问题,再弹出来,好像就对了,咋回事?这要怪GetSystemMenu了。要解决这个问题,只要在没有显示系统菜单前就先调用下GetSystemMenu做一份拷贝先。

至此,系统菜单的问题应该全部都解决了。下面就是要解决全屏的问题了。

未完。。。。。

原文地址:https://www.cnblogs.com/wpcockroach/p/2441487.html