跟我一起玩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个妞,我一个都没换成,你的爱情事业如此成功。再说了,吕不韦临死前有教过秦始皇怎么统一中华吗?难道秦始皇会说:以前没人统一过华夏,我怎么统一?最后他老人家还是把六国给干掉了。