跟我一起玩Win32开发(4):创建菜单
也不知道发生什么事情,CSDN把我的文章弄到首页,结果有不少说我在误人子弟,是啊,我去年就说过了,如果你要成为砖家级人物,请远离我的博客,我这个人没什么特长,唯一厉害的一点就是不相信权威,鄙视砖家,所以,我一直以来都有属于俺自己的编程思想。
就好比当年咏春拳刚浮出江湖的时候,武学界骂声不停,有人说:“这哪像拳?分明是女人拳。”然后不知道什么时候,一位叫叶问的大侠突然牛B起来了,于是,咏春拳的传播速度比其他拳种更快,都传到老外那里去了。
所以,为了继续误人子弟,我决定把这些文章写下去,直到误尽天下苍生为止。
我们当然知道,现在,在实际开发中肯定不会像我这样写Win32程序的,你看,连个WinMain都要N行代码。但很多人不明白什么叫学习,什么叫探索。实际上,通常能用于实际开发中的技巧只是占我们对客观世界的认识总和不到20%,所以,如果你有兴趣计算一下,估计有80%的知识你不知道用到哪里去了。就算我们今后不会把Win32程序投入到实际操作中,然而如果你了解过这东西,你会发现很多时候对我们是有帮助的。
哪怕只是简单认识一下Win32的一些原理,相信对于日后编程的学习和成长,是有益处的。
为了提高误人子弟的效果,上面我说了几段F话,下面开始今天的正题。
要在窗口上添加菜单,当然你可能会研究出N种方法,不过,这里我说两种,一种相当复杂,另一种稍微简单。
方法一,用代码添加菜单
这种方法的思路是:首先在全局范围内定义一个HMENU的变量,用来保存窗口中菜单栏的句柄,根菜单(菜单栏)可以CreateMenu函数来创建,接着可以使用AppendMenu函数或者InsertMenuItem函数来创建菜单项。
句柄就是内存中各种资源的ID,比如图标,图片,字符串等。我们的菜单也是一种资源。
下面我写了一个函数,用来动态创建菜单。
- void CreateMyMenu()
- {
- hRoot = CreateMenu();
- if(!hRoot)
- return;
- HMENU pop1 = CreatePopupMenu();
- AppendMenu(hRoot,
- MF_POPUP,
- (UINT_PTR)pop1,
- L"操作");
- // 一种方法是使用AppendMenu函数
- AppendMenu(pop1,
- MF_STRING,
- IDM_OPT1,
- L"飞机");
- // 另一种方法是使用InsertMenuItem函数
- MENUITEMINFO mif;
- mif.cbSize = sizeof(MENUITEMINFO);
- mif.cch = 100;
- mif.dwItemData = NULL;
- mif.dwTypeData = L"机关枪";
- mif.fMask = MIIM_ID | MIIM_STRING | MIIM_STATE;
- mif.fState = MFS_ENABLED;
- mif.fType = MIIM_STRING;
- mif.wID = IDM_OPT2;
- InsertMenuItem(pop1,IDM_OPT2,FALSE,&mif);
- }
hRoot是在外部定义的全局变量,保存菜单栏的标识。完整的代码如下:
- #include <Windows.h>
- #define IDM_OPT1 301
- #define IDM_OPT2 302
- HMENU hRoot;
- void CreateMyMenu();//创建菜单
- LRESULT CALLBACK MyWinProce(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
- int CALLBACK WinMain(HINSTANCE hInstance,
- HINSTANCE hPrevInstance,
- LPSTR cmdLine,
- int nShow)
- {
- CreateMyMenu();//创建菜单
- WCHAR* cn = L"Myapp";
- WNDCLASS wc={ };
- wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
- wc.lpszClassName = cn;
- wc.style = CS_HREDRAW | CS_VREDRAW;
- wc.hInstance = hInstance;
- wc.lpfnWndProc = (WNDPROC)MyWinProce;
- RegisterClass(&wc);
- HWND hm = CreateWindow(cn,
- L"我的应用程序",
- WS_OVERLAPPEDWINDOW,
- 20,
- 15,
- 420,
- 360,
- NULL,
- hRoot,
- hInstance,
- NULL);
- if( hm == NULL )
- return 0;
- ShowWindow(hm,nShow);
- MSG msg;
- while(GetMessage(&msg,NULL,0,0))
- {
- TranslateMessage(&msg);
- DispatchMessage(&msg);
- }
- return 0;
- }
- LRESULT CALLBACK MyWinProce(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
- {
- switch(msg)
- {
- case WM_DESTROY:
- //DestroyMenu(hRoot);
- PostQuitMessage(0);
- return 0;
- default:
- return DefWindowProc(hwnd, msg, wParam,lParam);
- }
- }
- void CreateMyMenu()
- {
- hRoot = CreateMenu();
- if(!hRoot)
- return;
- HMENU pop1 = CreatePopupMenu();
- AppendMenu(hRoot,
- MF_POPUP,
- (UINT_PTR)pop1,
- L"操作");
- // 一种方法是使用AppendMenu函数
- AppendMenu(pop1,
- MF_STRING,
- IDM_OPT1,
- L"飞机");
- // 另一种方法是使用InsertMenuItem函数
- MENUITEMINFO mif;
- mif.cbSize = sizeof(MENUITEMINFO);
- mif.cch = 100;
- mif.dwItemData = NULL;
- mif.dwTypeData = L"机关枪";
- mif.fMask = MIIM_ID | MIIM_STRING | MIIM_STATE;
- mif.fState = MFS_ENABLED;
- mif.fType = MIIM_STRING;
- mif.wID = IDM_OPT2;
- InsertMenuItem(pop1,IDM_OPT2,FALSE,&mif);
- }

方法二,通过编辑资源来添加菜单
上面的方法虽然创建了菜单,但你也看到了,是相当地不方便,所以,下面我重点介绍一下用资源编辑器来创建菜单资源。
在你的开发工具上,依次找到菜单项【视图】【资源视图】。

在资源视图中,右击项目根节点,从弹出的菜单中选择【添加】【资源】。

在随后弹出的对话框中,选择Menu,单击右边的“新建”按钮。

可以通过属性窗口来重命名菜单的ID。

我们可以使用可视化视图来建立菜单,为了可以在代码中使用,给需要的菜单一个ID,这个名字你可以自己喜欢,只是惯用的是以IDM_开头,意思是Menu ID,比如IDC开头的,意思是Control ID等等。

编辑好之后,保存,在【解决方案资源管理器】中你会看到一个resource.h文件,其实我们为资源定义的ID都以宏的形式声明的,不信你打开看看。
资源ID都是数字,只是为它定义个名字,方便识别罢了,就好像人们平时只叫你的名字或者小名,你见过谁会叫你的身份证号码的?
开发工具生成的ID有时候会有问题,或者有些ID我们在程序中没用上,如果你觉得它们留在代码文件中会影响市容的话,你可以这样:
1、在【资源视图】窗口中,右击,从弹出的快捷菜单中选择【资源符号...】,弹出一个窗口,这里可以看到应用程序中的资源ID列表,以及哪些ID已被使用,但是,这个窗口中显示的内容,有时候不准确,有些ID明明没有被使用,它右边却打上了勾。
这里可以修改ID的值,也可以新建资源ID,所以,你也可以在这里预先为资源分配ID,然后在属性窗口设置资源的标识时,从下拉列表中选择指定的ID。资源ID的名字和数值不能重复,但是,不同的资源是可以使用同一个资源ID的。例如,通常在应用程序中,某些菜单项的功能和工具栏上的按钮是一一对应的,功能相同,这种情况下,我们可以考虑让它们共用一个ID。
2、你可以直接打开resource.h头文件,直接在上面修改。
响应菜单命令
当用户单击某个菜单项后,窗口处理程序(WindowProc)会收到一条WM_COMMAND消息,它的两个附加参数如下:
在收到WM_COMMAND后,我们可以用LOWORD取得它的低数位,上表中已经说明,wParam的低位值表示菜单的资源ID,我们通过它的值与哪个菜单的ID相等,就知道用户点击了哪个菜单项。
所以,我们的程序代码现在应为:
- #include <Windows.h>
- #include "resource.h"
- LRESULT CALLBACK MyWinProce(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
- int CALLBACK WinMain(HINSTANCE hInstance,
- HINSTANCE hPrevInstance,
- LPSTR cmdLine,
- int nShow)
- {
- WCHAR* cn = L"Myapp";
- WNDCLASS wc={ };
- wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
- wc.lpszClassName = cn;
- wc.style = CS_HREDRAW | CS_VREDRAW;
- wc.hInstance = hInstance;
- wc.lpfnWndProc = (WNDPROC)MyWinProce;
- RegisterClass(&wc);
- HWND hm = CreateWindow(cn,
- L"我的应用程序",
- WS_OVERLAPPEDWINDOW,
- 20,
- 15,
- 420,
- 360,
- NULL,
- // 加载菜单资源
- LoadMenu(hInstance, MAKEINTRESOURCE(IDR_MAIN)),
- hInstance,
- NULL);
- if( hm == NULL )
- return 0;
- ShowWindow(hm,nShow);
- MSG msg;
- while(GetMessage(&msg,NULL,0,0))
- {
- TranslateMessage(&msg);
- DispatchMessage(&msg);
- }
- return 0;
- }
- LRESULT CALLBACK MyWinProce(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
- {
- switch(msg)
- {
- case WM_COMMAND:
- {
- // 取出资源Id值
- // 并判断用户选择了哪个菜单项
- switch(LOWORD(wParam))
- {
- case IDM_PLANE:
- MessageBox(hwnd,L"灰机来了。",L"提示",MB_OK);
- break;
- case IDM_GUN:
- MessageBox(hwnd,L"让炮弹飞。",L"提示",MB_OK);
- break;
- case IDM_MT_GUN:
- MessageBox(hwnd,L"山炮欲来风满楼。",L"提示",MB_OK);
- break;
- default:
- break;
- }
- }
- return 0;
- case WM_DESTROY:
- PostQuitMessage(0);
- return 0;
- default:
- return DefWindowProc(hwnd, msg, wParam,lParam);
- }
- }
在注册窗口类时,如要设置菜单,调用LoadMenu函数,第一个参数是当前程序实例的句柄,从WinMain的参数中获得,第二个参数是菜单的ID,因为这里要名字,字符串,而我们的ID都是数值,可通过MAKEINTRESOURCE宏转换。至于MessageBox函数就不用我介绍了。
好了,我们的应用程序已经创建了菜单了。
呵,有人说我的编程学习方法很奇特,其实,我们何苦要墨守成规呢,局限在定势思维中呢?枯躁无味的东西,你可以人为地让它变得充满乐趣,关键是你的心态罢了。这让我想起,以前某同学A跟我讨论两个问题:
1、我的程序只想保存用户的一些使用设置,用数据库没必要,写注册不环保;
2、我有一个dll类库,我希望最后我的程序既可以引用它,但同时只生成一个exe文件,能做到吧。
我就说了,问题一好办,你定义一个存放设置项的类,把它直接进行XML序列化和反序列化就完事了,这既能保存数据,又可以封装对象。如果不想让别人看到XML文件中的内容,就把它用DES算法加密/解密;而第二个问题,你把dll文件当成资源嵌入到程序的资源文件中,运行时如果要用到类库的类,那就把它反射出来,动态调用就行了。
然后他说,这好像没有人这样做,老师也没教过。
我说:靠,你老爸小时候教过你泡妞吗?你一上大学怎么就学会了泡妞?四年大学还换了N个妞,我一个都没换成,你的爱情事业如此成功。再说了,吕不韦临死前有教过秦始皇怎么统一中华吗?难道秦始皇会说:以前没人统一过华夏,我怎么统一?最后他老人家还是把六国给干掉了。
跟我一起玩Win32开发(4):创建菜单的更多相关文章
- 跟我一起玩Win32开发(转自CSDN-东邪独孤)
跟我一起玩Win32开发(1):关于C++的几个要点 跟我一起玩Win32开发(2):完整的开发流程 跟我一起玩Win32开发(3):窗口的重绘 跟我一起玩Win32开发(4):创建菜单 跟我一起玩W ...
- 跟我一起玩Win32开发(17):启动和结束进程
这里我再次说明一下,我不知道为什么,现在的人那么喜欢走极端,估计是价值观都“升级”了的缘故吧. 我撰写这一系列Win32相关的文章,并不是叫大家一定要用Win32去开发项目,仅仅是给大家了解一下,Wi ...
- 跟我一起玩Win32开发(6):创建右键菜单
快捷菜单,说得容易理解一点,就是右键菜单,当我们在某个区域内单击鼠标右键,会弹出一些菜单项.这种类型的菜单,是随处可见的,我们在桌面上右击一下,也会弹出一个菜单. 右键菜单的好处就是方便,它经常和我们 ...
- 跟我一起玩Win32开发(18):使用对话框的两个技巧
相信大家知道对话框怎么用了,就是先用“资源编辑器”设计一个对话框,然后在代码中加载处理.今天,我向大家分享两个使用对话框的技巧,还是比较实用的.不用担心,先喝杯茶,很简单的,一点也不复杂,总之,看俺写 ...
- 跟我一起玩Win32开发(10):绘图(C)
今天我们来欣赏一下用于填充图形的函数,当然我不会逐个去介绍,因为我们参考MSDN直接套参数就可以了. SetDCBrushColor函数有必要扯一下,它的声明如下: COLORREF SetDCBru ...
- 跟我一起玩Win32开发(12):使用控件——单选按钮
今天,咱们还是接着玩“控件斗地主”,这是我原创的超级游戏,有益身心健康,玩一朝,十年少. 哦,对,脑细胞极速运动了一下,想起了一个问题,这个破问题虽然网上有很多种解决方案,但是,并没有让所有人都解决问 ...
- 跟我一起玩Win32开发(19):浏览和打开文件
在应用程序中,我们很经常要实现的功能,是Open文件或保存文件对话框,让用户来选择一个或N个文件.本文我将介绍两种思路,第一种方法较为复杂,第二种方法较为简单. 方法一:老规矩 这是一种传统方法,使用 ...
- 跟我一起玩Win32开发(2):完整的开发流程
上一篇中我给各位说了一般人认为C++中较为难的东西——指针.其实对于C++,难点当然不局限在指针这玩意儿上,还有一些有趣的概念,如模板类.虚基类.纯虚函数等,这些都是概念性的东西,几乎每一本C++书上 ...
- 跟我一起玩Win32开发(21):复制&粘贴&剪贴板操作
我要提醒一下大家,看了我的博文学到的知识,千万不要用于实际开发,不然你会被你的上司骂:“妈的,这些东西哪来的,从来没有人这样做过.”不信你试试,脑细胞被冻结的经理或者技术总监们肯定会这样说的. 如果是 ...
随机推荐
- JAVA WEB学习笔记(二):Tomcat服务器的安装及配置
一.Tomcat的下载及安装. 前往Tomcat官网下载安装包或者免安装压缩包.链接http://tomcat.apache.org/ 这里,我选择的是Tomcat8.0,而不是最新的Tomcat9. ...
- java设计模式----复合模式
复合模式:复合模式结合两个或以上的模式,组成一个解决方案,解决一再发生的一般性问题 要点: 1.MVC是复合模式,结合了观察者模式.策略模式和组合模式 2.模型使用观察者模式,以便观察者更新,同时保持 ...
- 全志Android SDK编译详解(一)
1.编译环境说明 Linux 服务器(任意电脑都可以,建议选择配置高的)安装ubuntu 10.04 作为编译的主机,其他开发者在xp 下,通过SSH/Telent 等协议登录linux 主机进行编. ...
- Express:模板引擎深入研究
深入源码 首先,看下express模板默认配置. view:模板引擎模块,对应 require('./view'),结合 res.render(name) 更好了解些.下面会看下 view 模块. v ...
- [网页游戏开发]容器的使用及自定义Tab,RadioGroup,List,ViewStack
Morn里面,容器和其他普通组件不同,无需皮肤,所以也不能从组件树种拖动创建(Tab,RadioGroup例外),只能转换而来 Morn的容器组件主要有Box,Container,Panel,Tab, ...
- [Android]Android5.0实现静默接听电话功能
原因: android曾经能够通过AIDL进行静默接听.可是5.0以后就被谷歌给屏蔽了.这时候我们仅仅能通过其它方式实现了. 解决方式: try { Runtime.getRuntime().exec ...
- Liunx之Lamp搭建笔记
1:LAMP源代码搭建用户关系 a. apache服务以daemon用户的处理请求.以root身份作为主进程. b. php源代码安装,会在httpd.conf文件里自己主动增加调用模块.可是在该文 ...
- 经常使用 Java API
经常使用Java API 一. java.io.BufferedReader类(用于从文件里读入一段字符.所属套件:java.io) 1. 构造函数BufferedReader(java.io.Fil ...
- 按行读入xml文件,删除不需要的行 -Java
删除挺麻烦的,这里其实只是把需要的行存到arraylist中再存到另一个文件中 import java.io.BufferedReader;import java.io.BufferedWriter; ...
- Codeforces Round #Pi (Div. 2) C. Geometric Progression
C. Geometric Progression time limit per test 1 second memory limit per test 256 megabytes input stan ...