菜单和图标
菜单和按钮
例子:菜单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.由于本人英文水平所限,只能翻译到这个程度了,有纰漏还望多多指出,附上本篇翻译的英文原版教程地
分清几个概念
<1>“主菜单” 和 “顶层菜单” 是一个意思。
<2>主菜单中的项目叫做 “弹出菜单” 或者 “子菜单”。
<3>弹出菜单的项目可以是另外一个弹出菜单。
<4>菜单的状态:启用,禁用,无效化,无效化跟前两者的区别是灰色显示文字。
(1)菜单消息
<1>WM_INITMENU
<2>WM_MENUSELECT
菜单项被选中的时候
其中
旗标是MF_GRAYED、MF_DISABLED、MF_CHECKED、MF_BITMAP、MF_POPUP、MF_HELP、MF_SYSMENU和MF_MOUSESELECT的集合。
<3>WM_INITMENUPOPUP
当下拉菜单被激活的时候就会发出这样的消息
LOWORD(lParam)代表的是菜单项索引,HIWORD(lParam)表示的是TRUE,或者FALSE,菜单是系统菜单的时候表示的TRUE,非系统菜单的时候表示的是FALSE。
<4>WM_COMMAND
表示使用者已经从菜单中选择了一个被启用的菜单项,
LOWORD (wParam):菜单命令ID
HIWORD(wParam):0
lParam:0
<5>WM_MENUCHAR
(2)菜单项中的字母的下划线
把字母前面加&字符,就可以出现字母下划线的效果,当用Alt键+ 字符,可以快捷的弹出子菜单,或者执行菜单项命令。
对应属性
(3)菜单项的选中和去选中状态
(4)关于Menu的函数
关于菜单的操作从大体方向上看无外乎增删改查四种操作。
4.1 HMENUCreateMenu(VOID);
4.2 BOOL AppendMenu( HMENU hMenu, // handle to menu
UINT uFlags, //menu-item options
UINT_PTR uIDNewItem, // identifier, menu, or submenu
LPCTSTR lpNewItem //menu-item content);
其中uFlags 可以是:
MF_BITMAP,MF_OWNERDRAW,MF_STRING
示例:
AppendMenu (hMenuPopup, MF_STRING, IDM_APP_EXIT,"E&xit") ;
AppendMenu (hMenu, MF_POPUP, hMenuPopup,"&File") ;
4.3 BOOLInsertMenu( HMENU hMenu, // handle to menu
UINT uPosition, // item that new item precedes
UINT uFlags, // options
UINT_PTR uIDNewItem, // identifier, menu, or submenu
LPCTSTR lpNewItem // menu item content);
这个函数和AppendMenu相比除了uPosition这个参数外,其余完全相同
uPosition:表示被插的位置索引或者菜单ID,具体是哪一个取决于uFlags中是MF_BYCOMMAND还是MF_BYPOSITION。
uIDNewItem:插入项是命令项的时候表示的是ID,插入项是菜单的时候表示的菜单句柄。
lpNewItem: 插入项的内容,具体取决于uFlags中包含的MF_BITMAP,MF_STRING,MF_OWNERDRAW
注意:不能放在一起的命令组合
- MF_BYCOMMAND and MF_BYPOSITION
- MF_DISABLED, MF_ENABLED, and MF_GRAYED
- MF_BITMAP, MF_STRING, MF_OWNERDRAW, and MF_SEPARATOR
- MF_MENUBARBREAK and MF_MENUBREAK
- MF_CHECKED and MF_UNCHECKED
4.4 BOOLInsertMenuItem( HMENU hMenu, // handle to menu
UINT uItem, // identifier or position
BOOL fByPosition, // meaning of uItem
LPCMENUITEMINFO lpmii // menu item information);
4.5 BOOL RemoveMenu( HMENU hMenu, // handle to menu
UINT uPosition, // menu item identifier or position
UINT uFlags // options);
4.6 BOOLModifyMenu( HMENU hMnu, // handle to menu
UINT uPosition, // menu item to modify
UINTuFlags, // options
UINT_PTR uIDNewItem, // identifier, menu, or submenu
LPCTSTR lpNewItem // menu item content);
4.7 BOOLDeleteMenu( HMENU hMenu, // handle to menu
UINT uPosition, // menu item identifier or position
UINT uFlags // option);
删除菜单项,如果菜单项时弹出菜单,那么就会释放菜单句柄所指向的内存。
4.7 BOOLDrawMenuBar( HWND hWnd // handle to window);
4.8 HMENUGetSubMenu( HMENU hMenu, // handle to menu
int nPos // menu item position);
当菜单栏发生变化时候,必须调用这个函数重新绘制菜单//经常验证,未必
(6)右键菜单
右键菜单跟一般的菜单没什么区别,只是弹出的时候,需要用到这个函数
其中的uFlags参数指定了菜单中菜单中的对齐方式,左键右键选定菜单项
TPM_CENTERALIGN,TMP_LEFTALIGN,TMP_RIGHTALIGN
TPM_BOTTOMALIGN, TPM_TOPALIGN, TPM_VCENTERALIGN
TPM_LEFTBUTTPON,TPM_RIGHTBUTTON
TPM_NONOTIFY, TPM_RETURNCMD
(7)系统菜单
获取系统菜单句柄
HMENU GetSystemMenu(
HWNDhWnd, // handle to window
BOOL bRevert // reset option);
其中bRevert = FALSE时候,表示,复制系统的菜单,并且返回复制菜单的句柄,这个菜单可以进行修改。当bRevert = TRUE 时,设置系统菜单为默认的原始状态,并且函数返回值是NULL.
(8) 加速键
加速键也是一种资源,它可以使用快捷键迅速的打开命令项。加速键 可以在在资源中自己添加,也可以直接使用程序编写。加速键中主要的改变的code有
while(GetMessage(&msg,NULL,0,0))
{
if (!TranslateAccelerator(hWnd,hAccel,&msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
在这里 TranslateAccelerator函数把 一些按键消息翻译成WM_COMMAND,或者WM_SYSCOMMAND消息。
(9)代码示例
一下示例是一个完全依靠code不依赖创建资源的菜单demo,其中包含菜单栏,右键菜单,系统菜单,加速键可以对菜单进行动态的修改,删除,添加操作。