Files
minigui-docs/programming-guide-zh/MiniGUIProgGuidePart1Chapter06-zh.md
2022-11-21 22:10:14 +00:00

14 KiB
Raw Permalink Blame History

菜单

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);

xy 参数为弹出菜单的屏幕坐标位置,它的具体含义是和 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 函数使用,用来表示字符串的最大长度。

typeMFT_BMPSTRING 的时候,checkedbmpuncheckedbmp 分别表示菜单项选中时和非选中时的位图。

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 提供了两种方式:

  • flagMF_BYCOMMAND:通过菜单项的整数标识符。这时,上述两个函数中的 item 参数取菜单项的标识符。
  • flagMF_BYPOSITION:通过菜单项在菜单中的位置。这时,上述两个函数中的 item 参数取菜单项在菜单中的位置索引值,第一个菜单项取 0 值。

在使用这两个函数获取或设置菜单属性的时候,MENUITEMINFOmask 成员要设置相应的值才能成功获取或设置菜单的属性,有关 mask 的值请参照 2.3 对于 mask 成员的相关说明。

MiniGUI 还提供了其它一些获取和设置菜单项属性的函数这些函数均使用上述这种定位菜单项的方法。这些函数包括 GetSubMenuSetMenuItemBitmapsGetMenuItemidEnableMenuItem 等等。这些函数的功能其实均可通过上面这两个函数实现,因此,这里不再赘述。

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-samplesnotebook 的一部分,鉴于篇幅,只给出关于菜单的部分。该程序创建的菜单效果见图 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 弹出式菜单