菜单和按钮
例子:菜单1
本小节仅仅向你展示如果向你的窗口中加入一个基本的菜单,通常你会用到一个提前制作好的菜单资源,这会是一份.rc文件并且会被编译链接进你的.exe可执行程序中。这是具体的流程做法,而商业编译器将会有一个资源编辑器,你可以通过这个编辑器来创建菜单,但是在这个例子中我会向你展示如何用.rc文件的手动写法。通常我会配合使用一个头文件,在资源文件和源文件中我们需要引入这个头文件,这个头文件中包含了控制和菜单选项等的标识符。
在本小节的栗子中,你可以按照指示在simple_window代码的基础上做改造。
首先头文件名通常会叫“resource.h”
1 #define IDR_MYMENU 101 2 #define IDI_MYICON 201 3 4 #define ID_FILE_EXIT 9001 5 #define ID_STUFF_GO 9002
没有更多的东西,我们的菜单很简单,标识符名和ID号由你自己选择,现在我们来写.rc文件的代码。
1 #include "resource.h" 2 3 IDR_MYMENU MENU 4 BEGIN 5 POPUP "&File" 6 BEGIN 7 MENUITEM "E&xit", ID_FILE_EXIT 8 END 9 10 POPUP "&Stuff" 11 BEGIN 12 MENUITEM "&Go", ID_STUFF_GO 13 MENUITEM "G&o somewhere else", 0, GRAYED 14 END 15 END 16 17 IDI_MYICON ICON "menu_one.ico"
根据你使用的工具,将.rc文件加入你的项目或生成文件中。
你也要在你的源文件中使用#include “resource.h”引入资源头文件。
在你的窗口中附加菜单和图标最简单的方式就是在注册窗口类是指定好,就像下面:
1 wc.lpszMenuName = MAKEINTRESOURCE(IDR_MYMENU); 2 3 wc.hIcon = LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_MYICON)); 4 wc.hIconSm = (HICON)LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_MYICON), IMAGE_ICON, 16, 16, 0);
在原来的代码上面改看看会发生什么,你的窗口现在应该有一个File和Stuff的菜单,这种是你的资源文件被成功编译并链接至程序中的情况,如果发生错误请检查。
窗口右上角的图标和任务栏上的指定的小图标现在应该能够显示出来了,如果你使用Alt-Tab的组合键你会看到应用列表的大图标。
我曾经用loadIcon()来加载小图标因为它更简单,但是这个方法加载的图标的默认大小是32x32,为了加载小图标我们需要使用LoadImage()方法。请注意图标文件和资源可以包含多个图像,在这种情况下我提供了加载资源图标的两个尺寸。
例子:菜单2
另一种使用菜单资源的选择是动态创建(即你的程序运行的时候),这是一种更加聪明的工作方式,有时为了增加程序的灵活性是有必要的。
你也可以使用没有存储在资源中的图标,而选择在运行的时候单独加载存储图标的文件,这也给了你一种选择,即允许用户在通用的对话框中选择图标,在后面我们会讨论到,或者其他受动态创建影响的东西。
让我们再一次从没有资源文件和头文件的simple_windows的代码开始,现在我们将会处理WM_CREATE消息然后向我们的窗口中增加菜单。
1 #define ID_FILE_EXIT 9001 2 #define ID_STUFF_GO 9002
现在把这两个id放在你的源文件的头部,就在#include的下面,现在我们把下面的代码加入到WM_CREATE消息的句柄中:
1 case WM_CREATE: 2 { 3 HMENU hMenu, hSubMenu; 4 HICON hIcon, hIconSm; 5 6 hMenu = CreateMenu(); 7 8 hSubMenu = CreatePopupMenu(); 9 AppendMenu(hSubMenu, MF_STRING, ID_FILE_EXIT, "E&xit"); 10 AppendMenu(hMenu, MF_STRING | MF_POPUP, (UINT)hSubMenu, "&File"); 11 12 hSubMenu = CreatePopupMenu(); 13 AppendMenu(hSubMenu, MF_STRING, ID_STUFF_GO, "&Go"); 14 AppendMenu(hMenu, MF_STRING | MF_POPUP, (UINT)hSubMenu, "&Stuff"); 15 16 SetMenu(hwnd, hMenu); 17 18 19 hIcon = LoadImage(NULL, "menu_two.ico", IMAGE_ICON, 32, 32, LR_LOADFROMFILE); 20 if(hIcon) 21 SendMessage(hwnd, WM_SETICON, ICON_BIG, (LPARAM)hIcon); 22 else 23 MessageBox(hwnd, "Could not load large icon!", "Error", MB_OK | MB_ICONERROR); 24 25 hIconSm = LoadImage(NULL, "menu_two.ico", IMAGE_ICON, 16, 16, LR_LOADFROMFILE); 26 if(hIconSm) 27 SendMessage(hwnd, WM_SETICON, ICON_SMALL, (LPARAM)hIconSm); 28 else 29 MessageBox(hwnd, "Could not load small icon!", "Error", MB_OK | MB_ICONERROR); 30 } 31 break;
这个跟第一个例子的效果一样,创建了一个菜单附加到我们的窗口中,当程序终止时,指定附加到窗口的菜单会自动被移除。所以我们不要担心如何除掉它,如果我们想这样做,可以使用GetMenu()和DestroyMenu()来达到目的。
加载图标的代码相当简单,这里我们调用了两次LoadImage(),分别加载16x16和32x32尺寸的图标,我们不能使用LoadIcon()因为它只能加载资源文件,而不是图标文件本身。我们为实例句柄参数指定了一个NULL的参数因为我们不是从一个模块中加载资源的,我们通过传递我们想要加载的图标文件名而不是资源ID。最后我们通过LR_LOADFROMFILE标识表明我们想要函数我们传递进入的字符串当做文件名而不是一个资源名。
如果图标加载成功,我们会将图标通过WM_SETICON分配给窗口;如果失败了,会弹出一个告诉我们加载出错的对话框。
注意:如果突变文件不在我们程序的当前工作目录下那么调用LoadImage()会失败。如果你使用VC++并且从IDE运行程序,当前工作目录会是项目文件所在的目录中的一个。如果你使用浏览器的调试或发布目录或者命令行运行程序,那么你需要将图标文件复制进相应的工作目录中以便程序可以找到它;如果都不行,尝试用绝对路径"C:\\Path\\To\\Icon.ico"
OK,现在我们有自己的菜单了,我们需要让它做些东西,这相当简单,我们需要做的就是处理WM_COMMAND消息。同样我们需要检查得到了哪些命令然后根据它做处理,现在我们的WndProc()看起来影响像下面这样了。
1 LRESULT CALLBACK WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam) 2 { 3 switch(Message) 4 { 5 case WM_CREATE: 6 { 7 HMENU hMenu, hSubMenu; 8 9 hMenu = CreateMenu(); 10 11 hSubMenu = CreatePopupMenu(); 12 AppendMenu(hSubMenu, MF_STRING, ID_FILE_EXIT, "E&xit"); 13 AppendMenu(hMenu, MF_STRING | MF_POPUP, (UINT)hSubMenu, "&File"); 14 15 hSubMenu = CreatePopupMenu(); 16 AppendMenu(hSubMenu, MF_STRING, ID_STUFF_GO, "&Go"); 17 AppendMenu(hMenu, MF_STRING | MF_POPUP, (UINT)hSubMenu, "&Stuff"); 18 19 SetMenu(hwnd, hMenu); 20 21 22 hIcon = LoadImage(NULL, "menu_two.ico", IMAGE_ICON, 32, 32, LR_LOADFROMFILE); 23 if(hIcon) 24 SendMessage(hwnd, WM_SETICON, ICON_BIG, (LPARAM)hIcon); 25 else 26 MessageBox(hwnd, "Could not load large icon!", "Error", MB_OK | MB_ICONERROR); 27 28 hIconSm = LoadImage(NULL, "menu_two.ico", IMAGE_ICON, 16, 16, LR_LOADFROMFILE); 29 if(hIconSm) 30 SendMessage(hwnd, WM_SETICON, ICON_SMALL, (LPARAM)hIconSm); 31 else 32 MessageBox(hwnd, "Could not load small icon!", "Error", MB_OK | MB_ICONERROR); 33 } 34 break; 35 case WM_COMMAND: 36 switch(LOWORD(wParam)) 37 { 38 case ID_FILE_EXIT: 39 40 break; 41 case ID_STUFF_GO: 42 43 break; 44 } 45 break; 46 case WM_CLOSE: 47 DestroyWindow(hwnd); 48 break; 49 case WM_DESTROY: 50 PostQuitMessage(0); 51 break; 52 default: 53 return DefWindowProc(hwnd, Message, wParam, lParam); 54 } 55 return 0; 56 }
正如你看到了我们得到了所有设置的WM_COMMAND,甚至它有自己的switch,这个switch的值是根据wParam的低字来判断的,wParam对于WM_COMMAND来说包含了发送消息的控制或菜单id。
很明显,我们想要退出菜单选项实现关闭程序的功能,所以在WM_COMMAND的ID_FILE_EXIT句柄下你可以使用以下的代码来达到目的。
1 PostMessage(hwnd, WM_CLOSE, 0, 0);
你的WM_COMMAND消息处理程序看起来应该像下面这样子:
1 case WM_COMMAND: 2 switch(LOWORD(wParam)) 3 { 4 case ID_FILE_EXIT: 5 PostMessage(hwnd, WM_CLOSE, 0, 0); 6 break; 7 case ID_STUFF_GO: 8 9 break; 10 } 11 break;
我留给你实现另一个ID_STUFF_GO菜单命令做点什么。
程序文件图标
你可能注意到了menu_one.exe文件现在像我们用资源文件引入图标的方式显示了图标,然而menu_two.exe并没有这样做因为我们引入了一个外部文件。窗口浏览器在第一个程序中通过资源文件非常简单的显示了一个图标,因为只需要一个图标;如果我们想通过程序显示一个确切地图标,那么就可以采取第一种方式,即简单地使用资源ID;如果你想要根据你的选择显示图标那么就采取第二种方式吧。
PS.由于本人英文水平所限,只能翻译到这个程度了,有纰漏还望多多指出,附上本篇翻译的英文原版教程地址:http://www.winprog.org/tutorial/menus.html