14 KiB
菜单
1 菜单概念
菜单通常依附于窗口中(称为普通菜单),或者以独立的、可弹出形式出现(称为弹出式菜单)。主要是提供给用户一种快捷选择的方式。
2 创建和操作菜单
2.1 创建普通菜单
在程序中,我们首先要建立菜单,然后将菜单句柄传递给创建主窗口的函数 CreateMainWindow
。当主窗口显示出来时,我们创建的菜单就会在标题栏下显示出来。当用户用鼠标或者 Alt
键激活菜单并选择了菜单项后,该菜单所依附的窗口会收到 MSG_COMMAND
消息。
菜单的创建需要两个过程:
- 建立菜单栏
- 建立菜单栏中各个菜单的子菜单
首先,我们调用 CreateMenu
创建一个空的菜单,然后调用 InsertMenuItem
函数向这个空菜单中添加菜单项,如下所示:
HMENU hmnu;
MENUITEMINFO mii;
hmnu = CreateMenu();
memset (&mii, 0, sizeof(MENUITEMINFO));
mii.type = MFT_STRING ;
mii.state = 0;
mii.id = IDM_ABOUT_THIS;
mii.typedata = (DWORD)"文件...";
InsertMenuItem(hmnu, 0, TRUE, &mii);
如果这个菜单项有子菜单,则可通过设置菜单项的 hsubmenu
变量来指定菜单项的子菜单:
mii.hsubmenu = create_file_menu();
子菜单的创建过程和菜单栏的创建过程类似,但建立空菜单时要调用 CreatePopupMenu
函数,如下所示:
HMENU hmnu;
MENUITEMINFO mii;
memset (&mii, 0, sizeof(MENUITEMINFO));
mii.type = MFT_STRING;
mii.id = 0;
mii.typedata = (DWORD)"文件";
hmnu = CreatePopupMenu (&mii);
memset (&mii, 0, sizeof(MENUITEMINFO));
mii.type = MFT_STRING;
mii.state = 0;
mii.id = IDM_NEW;
mii.typedata = (DWORD)"新建";
InsertMenuItem(hmnu, 0, TRUE, &mii);
上述代码段中的 hmnu
句柄,就可以做为上一级菜单项的子菜单句柄使用。
2.2 创建弹出式菜单
弹出式菜单和菜单栏的用途不同,通常弹出式菜单用来响应用户的鼠标右键点击,通常也称为“上下文菜单”。
创建弹出式菜单和上面创建子菜单的方法一样,需要调用 CreatePopupMenu
函数。在显示这个菜单时,调用 TrackPopupMenu
函数:
int GUIAPI TrackPopupMenu (HMENU hmnu, UINT uFlags, int x, int y, HWND hwnd);
x
和 y
参数为弹出菜单的屏幕坐标位置,它的具体含义是和 uFlags
参数相关的。uFlags
参数的取值包括:
TPM_LEFTALIGN
:菜单以 (x,y) 点为准水平左对齐,也就是说x参数指定的是菜单的左边位置。TPM_CENTERALIGN
:水平居中对齐。TPM_RIGHTALIGN
:水平右对齐。TPM_TOPALIGN
:垂直顶对齐。TPM_VCENTERALIGN
:垂直居中对齐。TPM_BOTTOMALIGN
:垂直底对齐。
如果我们需要弹出一个下拉菜单,uFlags
一般可以用TPM_LEFTALIGN | TPM_TOPALIGN
的取值;如果是向上弹出菜单,uFlags
可以取 TPM_LEFTALIGN | TPM_ BOTTOMALIGN
。
下面的代码段中调用了 StripPopupHead
函数,该函数用来删除 MiniGUI 弹出式菜单的头部。弹出式菜单的头部和主窗口的标题栏类似,在调用该函数之后,弹出式菜单的头部信息就被销毁了。
HMENU hNewMenu;
MENUITEMINFO mii;
HMENU hMenuFloat;
memset (&mii, 0, sizeof(MENUITEMINFO));
mii.type = MFT_STRING;
mii.id = 0;
mii.typedata = (DWORD)"File";
hNewMenu = CreatePopupMenu (&mii);
hMenuFloat = StripPopupHead(hNewMenu);
TrackPopupMenu (hMenuFloat, TPM_CENTERALIGN, 40, 151, hWnd);
2.3 MENUITEMINFO
结构
MENUITEMINFO
结构是用来操作菜单项的核心数据结构,其定义如下:
typedef struct _MENUITEMINFO {
UINT mask;
UINT type;
UINT state;
int id;
HMENU hsubmenu;
PBITMAP uncheckedbmp;
PBITMAP checkedbmp;
DWORD itemdata;
DWORD typedata;
UINT cch;
} MENUITEMINFO;
typedef MENUITEMINFO* PMENUITEMINFO;
对这些成员说明如下:
mask
:由GetMENUITEMINFO
和SetMENUITEMINFO
函数使用,可由下面的宏组成(可以按位或使用):MIIM_STATE
:获取或者设置菜单项的状态MIIM_id
:获取或者设置菜单项的标识符MIIM_SUBMENU
:获取或者设置菜单项的子菜单MIIM_CHECKMARKS
:获取或者设置菜单位图信息MIIM_TYPE
:获取或者设置菜单项的类型和类型数据MIIM_DATA
:获取或者设置菜单项的私有数据
这些宏用来定义 GetMENUITEMINFO
和 SetMENUITEMINFO
函数具体操作菜单项的哪些项目。
-
type
:定义菜单项的类型,可取下面的值之一:MFT_STRING
:普通的文字菜单项MFT_BITMAP
:位图菜单项MFT_BMPSTRING
:含有位图和文字的菜单项MFT_SEPARATOR
:分割栏MFT_RADIOCHECK
:含有圆点的普通文字菜单项
-
state
:菜单项的状态,可取下面的值之一:MFS_GRAYED
:菜单项灰化MFS_DISABLED
:菜单项被禁止,不可用MFS_CHECKED
:菜单项含有对勾,即选定状态,当type
使用MFT_STRING
时,显示对勾,当type
使用MFT_RADIOCHECK
时,显示圆点MFS_ENABLED
:菜单项是可用的MFS_UNCHECKED
:菜单项不含对勾,没有被选定
-
id
:菜单项的整数标识符。 -
hsubmenu
:如果菜单项含有子菜单,则表示子菜单的句柄。 -
uncheckedbmp
:如果菜单项是位图菜单,则该位图用于显示非选定状态的菜单项。 -
checkedbmp
:如果菜单项是位图菜单,则该位图用于显示选定状态的菜单项。 -
itemdata
:和该菜单项关联的私有数据。 -
typedata
:菜单项的类型数据,用来传递菜单项的文本字符串。 -
cch
:由GetMENUITEMINFO
函数使用,用来表示字符串的最大长度。
当 type
为 MFT_BMPSTRING
的时候,checkedbmp
和uncheckedbmp
分别表示菜单项选中时和非选中时的位图。
2.4 操作菜单项
应用程序可以通过 GetMENUITEMINFO
函数获得感兴趣的菜单项属性,也可以通过 SetMENUITEMINFO
函数设置感兴趣的菜单项属性。这两个函数的接口定义如下:
int GUIAPI GetMenuItemInfo (HMENU hmnu, int item, BOOL flag, PMENUITEMINFO pmii);
int GUIAPI SetMenuItemInfo (HMENU hmnu, int item, BOOL flag, PMENUITEMINFO pmii);
这两个函数用来获取或者修改 hmnu
菜单中某个菜单项的属性。这时,我们需要一种方法来定位菜单中的菜单项,MiniGUI 提供了两种方式:
flag
取MF_BYCOMMAND
:通过菜单项的整数标识符。这时,上述两个函数中的item
参数取菜单项的标识符。flag
取MF_BYPOSITION
:通过菜单项在菜单中的位置。这时,上述两个函数中的item
参数取菜单项在菜单中的位置索引值,第一个菜单项取 0 值。
在使用这两个函数获取或设置菜单属性的时候,MENUITEMINFO
的mask
成员要设置相应的值才能成功获取或设置菜单的属性,有关 mask
的值请参照 2.3 对于 mask
成员的相关说明。
MiniGUI 还提供了其它一些获取和设置菜单项属性的函数,这些函数均使用上述这种定位菜单项的方法。这些函数包括 GetSubMenu
、SetMenuItemBitmaps
、GetMenuItemid
、EnableMenuItem
等等。这些函数的功能其实均可通过上面这两个函数实现,因此,这里不再赘述。
2.5 删除和销毁菜单或菜单项
MiniGUI 提供了如下函数用来从菜单中删除菜单项或者销毁菜单:
RemoveMenu
:该函数从菜单中删除指定的菜单项。如果菜单项含有子菜单,则会解除子菜单和该菜单项的关联,但并不删除子菜单。DeleteMenu
:该函数从菜单中删除指定的菜单项。如果菜单项含有子菜单,则同时会删除子菜单。DestroyMenu
:删除整个菜单。
2.6 MSG_ACTIVEMENU
消息
在用户激活菜单栏中的某个弹出式菜单后,MiniGUI 将给菜单栏所在的窗口过程发送 MSG_ACTIVEMENU
消息。该消息的第一个参数是被激活的弹出式菜单位置,第二个参数是该弹出式菜单的句柄。应用程序可以利用该消息对菜单进行处理,比如根据程序运行状态修改某些菜单项的选中标志等等。下面的代码段来自 MiniGUI 的 libvcongui
,这段程序根据用户的设置(虚拟终端的大小以及字符集)相应设置了菜单项的选中状态:
case MSG_ACTIVEMENU:
if (wParam == 2) {
CheckMenuRadioItem ((HMENU)lParam,
IDM_40X15, IDM_CUSTOMIZE,
pConInfo->termType, MF_BYCOMMAND);
CheckMenuRadioItem ((HMENU)lParam,
IDM_DEFAULT, IDM_BIG5,
pConInfo->termCharset, MF_BYCOMMAND);
}
break;
注意在上述代码中,两次调用 CheckMenuRadioItem
函数分别设置当前的终端大小和字符集选项。
3 编程实例
清单 1 给出了普通菜单的编程实例,该实例是 mg-samples
中 notebook
的一部分,鉴于篇幅,只给出关于菜单的部分。该程序创建的菜单效果见图 1。
清单 1 普通菜单的编程实例
/* 创建“文件”菜单 */
static HMENU createpmenufile (void)
{
HMENU hmnu;
MENUITEMINFO mii;
memset (&mii, 0, sizeof(MENUITEMINFO));
mii.type = MFT_STRING;
mii.id = 0;
mii.typedata = (DWORD)"文件";
hmnu = CreatePopupMenu (&mii);
memset (&mii, 0, sizeof(MENUITEMINFO));
mii.type = MFT_STRING;
mii.state = 0;
mii.id = IDM_NEW;
mii.typedata = (DWORD)"新建";
InsertMenuItem(hmnu, 0, TRUE, &mii);
mii.type = MFT_STRING;
mii.state = 0;
mii.id = IDM_OPEN;
mii.typedata = (DWORD)"打开...";
InsertMenuItem(hmnu, 1, TRUE, &mii);
mii.type = MFT_STRING;
mii.state = 0;
mii.id = IDM_SAVE;
mii.typedata = (DWORD)"保存";
InsertMenuItem(hmnu, 2, TRUE, &mii);
mii.type = MFT_STRING;
mii.state = 0;
mii.id = IDM_SAVEAS;
mii.typedata = (DWORD)"另存为...";
InsertMenuItem(hmnu, 3, TRUE, &mii);
mii.type = MFT_SEPARATOR;
mii.state = 0;
mii.id = 0;
mii.typedata = 0;
InsertMenuItem(hmnu, 4, TRUE, &mii);
mii.type = MFT_SEPARATOR;
mii.state = 0;
mii.id = 0;
mii.typedata = 0;
InsertMenuItem(hmnu, 5, TRUE, &mii);
mii.type = MFT_STRING;
mii.state = 0;
mii.id = IDM_EXIT;
mii.typedata = (DWORD)"退出";
InsertMenuItem(hmnu, 6, TRUE, &mii);
return hmnu;
}
/* 创建菜单栏 */
static HMENU createmenu (void)
{
HMENU hmnu;
MENUITEMINFO mii;
hmnu = CreateMenu();
memset (&mii, 0, sizeof(MENUITEMINFO));
mii.type = MFT_STRING;
mii.id = 100;
mii.typedata = (DWORD)"文件";
mii.hsubmenu = createpmenufile ();
InsertMenuItem(hmnu, 0, TRUE, &mii);
...
return hmnu;
}
/* 处理 MSG_ACTIVEMENU,以确保正确设定菜单项的选中状态 */
case MSG_ACTIVEMENU:
if (wParam == 2) {
/* 用 CheckMenuRadioItem 来设定菜单项的选中状态 */
CheckMenuRadioItem ((HMENU)lParam,
IDM_40X15, IDM_CUSTOMIZE,
pNoteInfo->winType, MF_BYCOMMAND);
CheckMenuRadioItem ((HMENU)lParam,
IDM_DEFAULT, IDM_BIG5,
pNoteInfo->editCharset, MF_BYCOMMAND);
}
break;
/* 处理 MSG_COMMAND 消息,处理各个菜单命令 */
case MSG_COMMAND:
switch (wParam) {
case IDM_NEW:
break;
case IDM_OPEN:
break;
case IDM_SAVE:
break;
case IDM_SAVEAS:
break;
};
图 1 记事本程序创建的菜单
清单 2 给出了弹出式菜单的编程实例。
清单 2 弹出菜单的编程实例
static HMENU CreateQuickMenu (void)
{
int i;
HMENU hNewMenu;
MENUITEMINFO mii;
HMENU hMenuFloat;
char *msg[] = {
"A",
"F",
"H",
"L",
"P",
"S",
"X"
};
memset (&mii, 0, sizeof(MENUITEMINFO));
mii.type = MFT_STRING;
mii.id = 0;
mii.typedata = (DWORD)"File";
hNewMenu = CreatePopupMenu (&mii);
for ( i = 0; i <7; i ++ ) {
memset ( &mii, 0, sizeof (MENUITEMINFO) );
mii.type = MFT_STRING;
mii.id = 100+ i;
mii.state = 0;
mii.typedata= (DWORD) msg[i];
InsertMenuItem ( hNewMenu, i, TRUE, &mii );
}
hMenuFloat = StripPopupHead(hNewMenu);
TrackPopupMenu (hMenuFloat, TPM_CENTERALIGN | TPM_LEFTBUTTON , 40, 151, hWnd);
}
清单 2 中的程序创建的弹出式菜单如图 2 所示。
图 2 弹出式菜单