【Windows编程】系列第七篇:Menubar的创建和使用

上一篇我们学习了利用windows API创建工具栏和状态栏,与上一篇紧密联系的就是菜单栏,菜单栏是一个大多数复杂一些的Windows应用程序不可或缺的部分。比如下图就是Windows自带的记事本的菜单栏:

菜单一般都是在标题栏下,工具栏以上,常常叫主菜单或顶级菜单(top-level menu),顶级菜单可能还会有弹出菜单(popup menu)或子菜单(submenu)。弹出菜单还有被“选中”(checked)状态,各菜单还有启用、禁用状态。

每一个菜单都有一个ID与之对应,当某个菜单被点击是,程序在WM_COMMAND消息中把菜单ID传给应该消息处理函数,就能知道哪个菜单被按下。

菜单栏的创建最常见是利用VS的菜单资源编辑器,然后加载该资源。比如下面的代码片段在创建主窗体时使用了LoadMenu函数加载菜单资源编辑的菜单:

hMenu = LoadMenu(hInstance, MAKEINTRESOURCE(ID_MENU));
hWnd = CreateWindow(TEXT(“myclass”), TEXT(“mytitle”), WS_OVERLAPPENDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 400, 300, NULL, hMenu, hInstance, NULL);

另外一种办法是在处理WM_CREATE消息是调用SetMenu函数来设置菜单:

SetMenu(hWnd, hMenu);

本文一直秉承一个原则,就是采用API的方式来创建而不是资源。因为用API来创建虽然麻烦一点,但是更加独立,比如如果不是用VS环境,那就可能没有资源编辑了,要是把我们的源程序在非VS的环境下编译就能显示出通用性和可移植性了。

  • 菜单消息

当用户选择一个菜单时,会产生WM_INITMENU消息和WM_MENUSELECT,WM_INITMENU消息使得我们有机会在菜单的选中之前做一些事情,而WM_MENUSELECT消息是在菜单被选中或者光标移到该菜单时被发送,我们可以利用这个消息进行菜单选中时的处理。

WM_INITMENUPOPUP消息在一个弹出菜单显示前发送,可以用来修改一些菜单显示。

最重要、最常用的就是上面我们提到的WM_COMMAND消息,当菜单被点击时就会产生这个消息。上面的消息对应的参数意义请参考MSDN。

  • 菜单创建

菜单相关的API有好几十个,我们这里只用一些常用的API函数,这几个函数基本可以完成菜单的基本功能,更多的菜单函数和功能的请参考MSDN。

函数CreateMenu可以创建一个菜单,CreatePopupMenu创建一个下拉式或弹出是菜单。函数AppendMenu可以追加一个菜单项,函数InsertMenu可以插入一个菜单项,TrackPopupMenu函数将在指定的位置显示一个弹出菜单。这几个菜单原型如下:

HMENU CreateMenu(VOID);
HMENU CreatePopupMenu(VOID);
BOOL AppendMenu(HMENU hMenu, UINT uFlags, UINT_PTR uIDNewItem, LPCTSTR lpNewItem);
BOOL InsertMenu(HMENU hMenu, UINT uPosition, UINT uFlags, PTR uIDNewItem, LPCTSTR lpNewItem);
BOOL TrackPopupMenu(HMENU hMenu, UINT uFlags, int x, int y, int nReserved, HWND hWnd, HWND prcRect);

其实菜单的常用部分大都是用这几个函数完成的,并不复杂。不说了,直接一边上代码一边解释更直接,由于篇幅,这里仅仅给出关键代码,完整的demo请查看文章底部的原文链接:

HMENU CreateMenuBar(void)
{
     //总菜单
     HMENU hMenu = CreateMenu();

     //文件菜单
     HMENU hFileMenu = CreateMenu();
     AppendMenu(hFileMenu, MF_STRING, IDM_FILE_NEW, TEXT("&New"));
     AppendMenu(hFileMenu, MF_SEPARATOR, 0, NULL); //插入一条横条,请看运行效果
     AppendMenu(hFileMenu, MF_STRING, IDM_FILE_SAVE, TEXT("&Save"));
     AppendMenu(hMenu, MF_POPUP, (UINT_PTR)hFileMenu, TEXT("File(&F)"));

     //文件的二级子菜单
     HMENU hSubMenu = CreateMenu();
     AppendMenu(hSubMenu, MF_STRING, IDM_FILE_OPEN_SOLUTION, TEXT("So&lution"));
     AppendMenu(hSubMenu, MF_STRING, IDM_FILE_OPEN_PROJECT, TEXT("Pro&ject"));
     //将该二级菜单插入到第二条的位置
     InsertMenu(hFileMenu, 1, MF_BYPOSITION|MF_POPUP, (UINT_PTR)hSubMenu, TEXT("Open"));

     //编辑菜单
     hFileMenu = CreateMenu();
     AppendMenu(hFileMenu, MF_STRING, IDM_EDIT_COPY, TEXT("&Copy"));
     AppendMenu(hFileMenu, MF_STRING, IDM_EDIT_PASTE, TEXT("&Paste"));
     AppendMenu(hFileMenu, MF_SEPARATOR, 0, NULL);
     AppendMenu(hFileMenu, MF_STRING|MF_CHECKED, IDM_EDIT_HL, TEXT("&Update"));//增加一个check选项
     AppendMenu(hMenu, MF_POPUP, (UINT_PTR)hFileMenu, TEXT("Edit(&E)"));

     //缩放菜单
     hFileMenu = CreateMenu();
     AppendMenu(hFileMenu, MF_STRING, IDM_VIEW_HALF, TEXT("&Half"));
     AppendMenu(hFileMenu, MF_SEPARATOR, 0, NULL);
     //设置一个灰色不可选的菜单,该菜单可以用EnableMenuItem函数修改可选状态
     AppendMenu(hFileMenu, MF_STRING|MF_GRAYED, IDM_VIEW_PART, TEXT("P&art"));
     AppendMenu(hMenu, MF_POPUP, (UINT_PTR)hFileMenu, TEXT("Zoom(&Z)"));
     return hMenu;
}

static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
     HDC         hDC;
     PAINTSTRUCT ps;

     switch (message)
     {
     case WM_CREATE:
          {
               HMENU hMenu = CreateMenuBar();
               SetMenu(hWnd, hMenu); //以上只是创建了菜单,需要设置
          }
          return 0;

     case WM_RBUTTONUP:
          {
               POINT point;
               point.x = LOWORD(lParam);
               point.y = HIWORD(lParam);
               ClientToScreen(hWnd, &point); //这里的坐标是相对于屏幕的,需要转换为客户坐标
               HMENU hSubMenu = GetSubMenu(GetMenu(hWnd), 0); //获取菜单的第0个子菜单,用这个菜单来演示弹出菜单
               TrackPopupMenu(hSubMenu, TPM_RIGHTBUTTON, point.x, point.y, 0, hWnd, NULL);
          }
          return 0;

     case WM_COMMAND:
          switch (LOWORD(wParam))
          {
               case IDM_FILE_NEW:
                    MessageBox(hWnd, TEXT("you click new file button"), TEXT("hint"), MB_OK);
                    break;
               default:
                    break;
          }
          return 0;

     case WM_PAINT:
          hDC = BeginPaint(hWnd, &ps);
          ;
          EndPaint(hWnd, &ps);
          return 0;

     case WM_DESTROY:
          PostQuitMessage(0);
          return 0 ;
     }

     return DefWindowProc (hWnd, message, wParam, lParam);
}

本demo运行后点击“文件”菜单如下:

点击“编辑”菜单如下:

鼠标右键弹出快捷菜单:

文篇只演示了常用的菜单,其他比如位图菜单、非客户区弹出菜单等更多内容有兴趣在讨论,也可以参考MSDN的相关函数自己进行测试。本文的菜单栏编程结合上一篇的创建工具栏和状态栏内容以及第二篇的创建常用控件部分,基本可以完成窗口应用程序的界面编程了。当然再次强调,我们这些都是基于Windows API函数完成的,可能很多人会说,我用MFC,资源编辑器,对话框下的控件面板、甚至VB、C#都可以很快编写出这些界面。没错,但是隐藏在这些的下面还是会回到我们这些基本的API上,这个才是根。

原文发布于微信公众号 - 程序员互动联盟(coder_online)

原文发表时间:2015-08-02

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏ytkah

Excel表格的35招必学秘技[配图]

一、让数据按需排序   如果你要将员工按其所在的部门进行排序,这些部门名称既的有关信息不是按拼音顺序,也不是按笔画顺序,怎么办?可采用自定义序列来排序。  ...

3938
来自专栏NetCore

打造属于自己的支持版本迭代的Asp.Net Web Api Route

    在目前的主流架构中,我们越来越多的看到web Api的存在,小巧,灵活,基于Http协议,使它在越来越多的微服务项目或者移动项目充当很好的service...

22010
来自专栏coding for love

JS常用参考文档整理

402
来自专栏数据小魔方

动态图表11|数值调节器(名称管理器+offset函数)

今天跟大家分享的是动态图表11——使用调节器控件制作动态图表! 本案例会将之前10篇的动态图表综合运用,会用到index函数、offset函数、数值调节器、名称...

3115
来自专栏hrscy

iOS 9 Storyboard 教程(二下)

现在你会忽视Game行,仅仅让用户输入玩家的名字. 当用户点击Cancel按钮的时候,这个控制器将会关闭并且不管你输了什么数据都不会保存.这个部分用unwin...

591
来自专栏逍遥剑客的游戏开发

Nebula3 in CLR

1123
来自专栏布尔

select元素的options.add 与 insertbefore的区别

之前提到如果想改变元素的视觉效果(checkbox.checked=true会打钩),请在把元素添加到页面上再为其赋值,否则赋值无效。下拉框元素也有这样的问题,...

1877
来自专栏walterlv - 吕毅的博客

迫不及待地体验了一把 C#8.0 中的可空引用类型(Nullable Reference)

发布于 2017-12-18 13:41 更新于 2017-12...

312
来自专栏hbbliyong

WPF备忘录(2)WPF获取和设置鼠标位置与progressbar的使用方法

一、WPF 中获取和设置鼠标位置   方法一:WPF方法 Point p = Mouse.GetPosition(e.Source as Framewo...

3277
来自专栏葡萄城控件技术团队

ActiveReports 报表应用教程 (8)---交互式报表之动态过滤

用户可以使用葡萄城ActiveReports报表参数 (Parameters)集合把数据提供给报表中的文本框或图表,也可以选择数据的一个子集显示到报表的特定区域...

1588

扫描关注云+社区