帅哥们,美女们,下午好,我又来误人子弟,请做好准备。

今天,我们的目的是,想要实现下图中的这种菜单效果。

就是一种类似单选按钮的菜单,多个菜单项中,同时只有一个会被选中。

首先,我们在资源编辑器中,设计一个菜单资源。这个资源编辑器在管理资源ID的时候,有些问题,有时候不同步更新,有时候会保存不到,反正就会混乱。如果遇到问题,你可以先把菜单设计好,接着打开resource.h,手动把这些ID和它的值改一下。为了使这三个菜单项能形成一个组,必须让它们的ID值是连续的,比如我这里让它们分别为501,502,503。

101指的是整个菜单资源,后三个都是子菜单项。如果想更保险的话,可以在【解决方案资源管理器】中右击资源文件(.rc结尾),选择【查看代码】,然后检查一下是否正确就可以了。

现在菜单弄好了,下面我们来了解一下把菜单添加到窗口的两个类型。

第一种是类级别的,也就是我们在设计窗口类时,直接指定给WNDCLASS结构的lpszMenuName成员,这样做意味着,在调用CreateWindow函数创建窗口时,无论你是否为窗口指定菜单,最终显示的窗口上都会有菜单,因为它是基于整个窗口类的。

  1. // 在这里把菜单附加上,成为类级别
  2. wc.lpszMenuName = MAKEINTRESOURCE(IDR_MAIN);//整个菜单资源的ID,不是菜单项
  1. HWND hm = CreateWindow(
  2. L"MainWd",
  3. L"我的应用程序",
  4. WS_OVERLAPPEDWINDOW,
  5. 25,
  6. 18,
  7. 380,
  8. 280,
  9. NULL,
  10. NULL,
  11. hthisInstance,
  12. NULL);

这样在我们创建窗口时,哪怕你把hMenu参数设为NULL,最后显示的窗口都会有菜单,因为菜单是属于窗口类本身的。

另一种方式,就是不设置为类级别的菜单,而是在调用CreateWindow时指定给hMenu参数。

  1. HWND hm = CreateWindow(
  2. L"MainWd",
  3. L"我的应用程序",
  4. WS_OVERLAPPEDWINDOW,
  5. 25,
  6. 18,
  7. 380,
  8. 280,
  9. NULL,
  10. LoadMenu(hthisInstance,MAKEINTRESOURCE(IDR_MAIN)),
  11. hthisInstance,
  12. NULL);

同时我们把设计窗口类时设置菜单的代码注释掉。

  1. // 在这里把菜单附加上,成为类级别
  2. //wc.lpszMenuName = MAKEINTRESOURCE(IDR_MAIN);//整个菜单资源的ID,不是菜单项

然后,我们运行这个程序,它还是有菜单的。

接着,我们把CreateWindow的hMenu参数设置为NULL,

  1. HWND hm = CreateWindow(
  2. L"MainWd",
  3. L"我的应用程序",
  4. WS_OVERLAPPEDWINDOW,
  5. 25,
  6. 18,
  7. 380,
  8. 280,
  9. NULL,
  10. /*LoadMenu(hthisInstance,MAKEINTRESOURCE(IDR_MAIN))*/
  11. NULL,
  12. hthisInstance,
  13. NULL);

看看这时候运行程序,还能不能看到菜单。

现在就看不到菜单了,这两种加载菜单的方式,就区别在这里。

要为菜单实现单选标记,调用CheckMenuRadioItem函数,第一个参数是要在其子项中设置的单选的菜单的句柄,第二个参数和第三个参数指定合并为一个组的ID范围,在这个范围内的菜单项被看人为同一组,这一组中,每一次只能有一项被checked,第四个参数就指定在这组项中哪一个被选中,最后一个参数决定是用ID来标识还用从0开始的索引。

但是,我们在改变菜单项单选状态前,必须获得【水果】弹出菜单的句柄。

我们先来看一下,一般菜单栏的层次结构。

它就像一个树形结构,一层一层往下展开,上图中,红色矩形画的部分是菜单的根,即整个菜单栏,蓝色矩形标注的是菜单栏的下一级,弹出菜单,如【文件】、【编辑】、【视图】这些,它们一般只负责弹出子项列表,自身不响应用户选择命令,这也是我们在资源编辑器中设计菜单时,不需要给它们ID号的原因。

在【文件】下面又有了项,如图中黄色矩形标注的地方,如【新建】、【打开】。

知道这个后,我们的思路就有了。

1、调用GetMenu( 窗口句柄 )获取窗口中菜单栏的句柄;

2、调用GetSubMenu(  菜单栏句柄,0 )获得【水果】弹出菜单的句柄,0表示菜单栏中的第一个元素,如果第二个就是1,我们的弹出菜只有【水果】一项;

3、调用CheckMenuRadioItem函来来Check菜单。

因为我们是在响应WM_COMMAND消息时作出响应的,所以这些代码应写在WindowProc中。

  1. LRESULT CALLBACK WindowMainProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
  2. {
  3. // 获取窗口上的整个菜单栏的句柄
  4. HMENU hmm = GetMenu(hwnd);
  5. // 获取第一个弹出菜单,即[水果]菜单
  6. HMENU hfmn = GetSubMenu(hmm, 0);
  7. switch(msg)
  8. {
  9. case WM_COMMAND:
  10. {
  11. .......

菜单句柄是HMENU类型,所以GetMenu和GetSubMenu函数都返回HMENU类型的值。其实,这里我给大家推荐一个技巧,就是使用auto关键字,我们无需管它函数什么,统一用auto关键字,它会根据代码上下文推断数据类型,就像C#里面的var声明变量一样。所以,我们上面的代码可以改为:

  1. LRESULT CALLBACK WindowMainProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
  2. {
  3. // 获取窗口上的整个菜单栏的句柄
  4. auto hmm = GetMenu(hwnd);
  5. // 获取第一个弹出菜单,即[水果]菜单
  6. auto hfmn = GetSubMenu(hmm, 0);
  7. switch(msg)
  8. {
  9. case WM_COMMAND:
  10. {
  11. ........

然后,我们响应命令消息。

  1. switch(msg)
  2. {
  3. case WM_COMMAND:
  4. {
  5. //判断用户选了哪个菜单
  6. switch(LOWORD(wParam))
  7. {
  8. case IDM_APPLE:
  9. CheckMenuRadioItem(hfmn, IDM_APPLE, IDM_BANANA, IDM_APPLE, MF_BYCOMMAND);
  10. MessageBox(hwnd,L"你选择了苹果。",L"提示",MB_OK);
  11. break;
  12. case IDM_PEAR:
  13. CheckMenuRadioItem(hfmn, IDM_APPLE, IDM_BANANA, IDM_PEAR, MF_BYCOMMAND);
  14. MessageBox(hwnd,L"你选择了梨子。", L"提示", MB_OK);
  15. break;
  16. case IDM_BANANA:
  17. CheckMenuRadioItem(hfmn, IDM_APPLE, IDM_BANANA, IDM_BANANA, MF_BYCOMMAND);
  18. MessageBox(hwnd, L"你选择了香蕉。", L"提示", MB_OK);
  19. break;
  20. }
  21. }
  22. return 0;

这样就得到单选菜单的效果了。下面是完整的代码清单。

    1. #include <Windows.h>
    2. #include "resource.h"
    3. // 声明消息处理函数
    4. LRESULT CALLBACK WindowMainProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
    5. //入口点
    6. int WINAPI WinMain(
    7. HINSTANCE hthisInstance,//当前实例句柄
    8. HINSTANCE hPrevInstance,//钱一个实例句柄,一般不使用
    9. LPSTR cmdline,//命令行参数
    10. int nShow)//窗口的显示方式
    11. {
    12. // 设计窗口类
    13. WNDCLASS wc = { };
    14. wc.lpszClassName = L"MainWd";
    15. wc.hInstance  = hthisInstance;
    16. wc.lpfnWndProc = WindowMainProc;
    17. wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    18. // 在这里把菜单附加上,成为类级别
    19. //wc.lpszMenuName = MAKEINTRESOURCE(IDR_MAIN);//整个菜单资源的ID,不是菜单项
    20. // 让窗口自动重绘
    21. wc.style = CS_HREDRAW | CS_VREDRAW;
    22. // 注册窗口类
    23. RegisterClass(&wc);
    24. // 创建窗口
    25. HWND hm = CreateWindow(
    26. L"MainWd",
    27. L"我的应用程序",
    28. WS_OVERLAPPEDWINDOW,
    29. 25,
    30. 18,
    31. 380,
    32. 280,
    33. NULL,
    34. LoadMenu(hthisInstance,MAKEINTRESOURCE(IDR_MAIN)),
    35. hthisInstance,
    36. NULL);
    37. if(hm == NULL)
    38. return 0;
    39. // 显示窗口
    40. ShowWindow(hm, SW_SHOW);
    41. // 消息循环
    42. MSG msg;
    43. while(GetMessage(&msg, NULL, 0, 0))
    44. {
    45. TranslateMessage(&msg);
    46. DispatchMessage(&msg);
    47. }
    48. return 0;
    49. }
    50. LRESULT CALLBACK WindowMainProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
    51. {
    52. // 获取窗口上的整个菜单栏的句柄
    53. auto hmm = GetMenu(hwnd);
    54. // 获取第一个弹出菜单,即[水果]菜单
    55. auto hfmn = GetSubMenu(hmm, 0);
    56. switch(msg)
    57. {
    58. case WM_COMMAND:
    59. {
    60. //判断用户选了哪个菜单
    61. switch(LOWORD(wParam))
    62. {
    63. case IDM_APPLE:
    64. CheckMenuRadioItem(hfmn, IDM_APPLE, IDM_BANANA, IDM_APPLE, MF_BYCOMMAND);
    65. MessageBox(hwnd,L"你选择了苹果。",L"提示",MB_OK);
    66. break;
    67. case IDM_PEAR:
    68. CheckMenuRadioItem(hfmn, IDM_APPLE, IDM_BANANA, IDM_PEAR, MF_BYCOMMAND);
    69. MessageBox(hwnd,L"你选择了梨子。", L"提示", MB_OK);
    70. break;
    71. case IDM_BANANA:
    72. CheckMenuRadioItem(hfmn, IDM_APPLE, IDM_BANANA, IDM_BANANA, MF_BYCOMMAND);
    73. MessageBox(hwnd, L"你选择了香蕉。", L"提示", MB_OK);
    74. break;
    75. }
    76. }
    77. return 0;
    78. case WM_DESTROY:
    79. PostQuitMessage(0);
    80. return 0;
    81. default:
    82. return DefWindowProc(hwnd, msg, wParam, lParam);
    83. }
    84. }

跟我一起玩Win32开发(5):具有单选标记的菜单的更多相关文章

  1. 跟我一起玩Win32开发(4):创建菜单

    也不知道发生什么事情,CSDN把我的文章弄到首页,结果有不少说我在误人子弟,是啊,我去年就说过了,如果你要成为砖家级人物,请远离我的博客,我这个人没什么特长,唯一厉害的一点就是不相信权威,鄙视砖家,所 ...

  2. 跟我一起玩Win32开发(转自CSDN-东邪独孤)

    跟我一起玩Win32开发(1):关于C++的几个要点 跟我一起玩Win32开发(2):完整的开发流程 跟我一起玩Win32开发(3):窗口的重绘 跟我一起玩Win32开发(4):创建菜单 跟我一起玩W ...

  3. 跟我一起玩Win32开发(17):启动和结束进程

    这里我再次说明一下,我不知道为什么,现在的人那么喜欢走极端,估计是价值观都“升级”了的缘故吧. 我撰写这一系列Win32相关的文章,并不是叫大家一定要用Win32去开发项目,仅仅是给大家了解一下,Wi ...

  4. 跟我一起玩Win32开发(18):使用对话框的两个技巧

    相信大家知道对话框怎么用了,就是先用“资源编辑器”设计一个对话框,然后在代码中加载处理.今天,我向大家分享两个使用对话框的技巧,还是比较实用的.不用担心,先喝杯茶,很简单的,一点也不复杂,总之,看俺写 ...

  5. 跟我一起玩Win32开发(19):浏览和打开文件

    在应用程序中,我们很经常要实现的功能,是Open文件或保存文件对话框,让用户来选择一个或N个文件.本文我将介绍两种思路,第一种方法较为复杂,第二种方法较为简单. 方法一:老规矩 这是一种传统方法,使用 ...

  6. 跟我一起玩Win32开发(12):使用控件——单选按钮

    今天,咱们还是接着玩“控件斗地主”,这是我原创的超级游戏,有益身心健康,玩一朝,十年少. 哦,对,脑细胞极速运动了一下,想起了一个问题,这个破问题虽然网上有很多种解决方案,但是,并没有让所有人都解决问 ...

  7. 跟我一起玩Win32开发(21):复制&粘贴&剪贴板操作

    我要提醒一下大家,看了我的博文学到的知识,千万不要用于实际开发,不然你会被你的上司骂:“妈的,这些东西哪来的,从来没有人这样做过.”不信你试试,脑细胞被冻结的经理或者技术总监们肯定会这样说的. 如果是 ...

  8. 跟我一起玩Win32开发(2):完整的开发流程

    上一篇中我给各位说了一般人认为C++中较为难的东西——指针.其实对于C++,难点当然不局限在指针这玩意儿上,还有一些有趣的概念,如模板类.虚基类.纯虚函数等,这些都是概念性的东西,几乎每一本C++书上 ...

  9. 跟我一起玩Win32开发(25):监视剪贴板

    自从郭大侠和蓉儿离开桃花岛后,最近岛比较寂静,有一种“门前冷落鞍马稀”的感觉.于是,老邪就拿出<九阴真经>认真阅读,同时用迅雷下载经典大剧<汉武大帝>晚上睡觉前看上几集,老邪一 ...

  10. 跟我一起玩Win32开发(23):渐变颜色填充

    GradientFill函数可以对特定的矩形区域或者三角形区域进行渐变颜色的填充.我们先来看看GradientFill函数到底长得什么样子,帅不帅. BOOL GradientFill( _In_   ...

随机推荐

  1. npm WARN uninstall not installed in /Users/hrt0kmt/node_modules: "xxx"

    You may meet this error on home directory. % npm uninstall appium npm WARN uninstall not installed i ...

  2. Python 003- 小知识汇总(更新中)

    #查询key是否存在,可以在使用未知的字典的时候使用 #-*- coding:utf-8 -*- D={'a':1,'c':3,'b':2} for key in sorted(D): print(k ...

  3. HDU 6096 String 排序 + 线段树 + 扫描线

    String Time Limit: 6000/3000 MS (Java/Others) Memory Limit: 524288/524288 K (Java/Others) Problem De ...

  4. HDU 6078 Wavel Sequence 树状数组优化DP

    Wavel Sequence Problem Description Have you ever seen the wave? It's a wonderful view of nature. Lit ...

  5. vmware 自动挂起

    用VMware跑虚拟机,经常会出现客户操作系统自己挂起的现象,怀疑是主机自己休眠的设置.设置之后,无效. 后来才发现不是主机休眠设置,还是应该设置客户操作系统中的休眠设置. 在客户机,控制面板  电源 ...

  6. bzoj3136: [Baltic2013]brunhilda

    这个题为什么会放在数据结构啊 首先因为有决策包容性,对于一个n每次必然选择一个n%p最大的p,令n减n%p 设fi表示i变成0的步数的话,同样我们可以知道f是有单调性的 假如fd能转移到fk,首先d一 ...

  7. YTU 2417: C语言习题 字符串长度

    2417: C语言习题 字符串长度 时间限制: 1 Sec  内存限制: 128 MB 提交: 758  解决: 548 题目描述 写一函数,求一个字符串的长度.在main函数中输入字符串,并输出其长 ...

  8. classname.this 和 this的使用场景

    今天在写代码时,发现在写了一个内部类,而在内部类中需要调用外部类的实例方法,直接使用this调用发现调用的不是外部类而是内部类,于是查找资料原来需要使用外部类的classname.this这样的调用, ...

  9. 「NOIP2012」「LuoguP1083」 借教室

    Description 在大学期间,经常需要租借教室.大到院系举办活动,小到学习小组自习讨论,都需要向学校申请借教室.教室的大小功能不同,借教室人的身份不同,借教室的手续也不一样. 面对海量租借教室的 ...

  10. bzoj2560串珠子——子集DP

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=2560 转载: 很明显的状压dp 一开始写的dp可能会出现重复统计的情况 而且难以去重 假设 ...