动 态 创 建 级 联 菜 单
菜单是Windows的标准界面元素,几乎所有的Windows应用程序中都有它的身影。MFC中的类CMenu对它进行了封装,使其使用起来更加简便。要在程序中使用菜单也很简单,一般分以下几步:第一步先用VC的菜单编辑器创建一个菜单资源,给它赋予我们自己定义的ID,第二步在程序中构造一个CMenu类的对象,用CMenu::LoadMenu函数将菜单从资源中装入,接着调用CWnd::SetMenu函数将新菜单连到相应的窗口中,最后切记,要调用Detach成员函数把CMenu对象和菜单分离,以确保当CMenu对象生存期结束时,菜单不会被销毁。
以上是在程序中使用菜单的一般步骤,但有时候,我们在设计期是无法确定菜单包含哪些项的,这时这种事先设计好菜单资源的方法就行不通了。为了解决这个问题,必须要用到动态创建菜单的技术。通过CMenu类所提供的CreateMenu/CreatePopupMenu、AppendMenu/InsertMenu等函数,可以很方便地实现菜单的动态创建。下面简单地介绍一下这几个函数(函数原型和参数注释请查阅MSDN,这里不再赘述),然后用几个实例,讲解动态创建菜单的步骤。
CreateMenu函数用来创建一个空的菜单,并将其绑定到该CMenu对象上。要添加菜单项,可以使用CMenu的AppendMenu/InsertMenu成员函数(稍后我会介绍这两个函数有何不同)。如果这个创建出来的菜单是连到一个窗口上的,那么在窗口被销毁的时候,菜单会自动随之销毁;如果这个菜单不属于任何窗口,那么程序退出前,必须手动销毁菜单以及和它相关联的一切系统资源。销毁菜单用的是CMenu的DestroyMenu成员函数。
CreatePopupMenu和CreateMenu类似,只不过它创建的是一个空的浮动菜单(Pop-up Menu),和普通菜单不同的是,浮动菜单既可以连到别的菜单上(连到普通菜单或者浮动菜单都可以)成为次级子菜单,也可以连接到某个窗口上;而普通菜单只能连接到某个窗口上。
AppendMenu用于在菜单的末尾追加一个菜单项,而InsertMenu可以在菜单的指定位置插入一个菜单项。这里尤其要注意的就是第一个参数,如果添加的菜单项,是要弹出另一个子菜单的(比如下图所示的“View”菜单项),那么第一个参数nFlags一定要包含MF_POPUP标志,同时第二个参数nIDNewItem设置成子菜单的句柄。具体后面会详细讲述。
Menu和Pop-up Menu,是有区别的。普通菜单(就是用CreateMenu函数创建出来的菜单),直观地说,就是固定在标题栏下方的菜单,每Append一个菜单项,横向上就多出一列出来。让我们在OnInitDialog函数中添加如下代码:
CMenu Menu;
Menu.CreateMenu();
SetMenu(&Menu);
Menu.Detach();
编译运行,没有效果,这是显而易见的,因为这时Menu还是空的,因为我们没有添加任何菜单项。截图如下:
好,现在让我们向菜单中添加一个菜单项。在Menu.CreateMenu()语句下面插入下面的代码:
Menu.AppendMenu(MF_STRING, ID_APP_FILE, "&File");
好了,有效果了,出来一个菜单,但是鼠标按下去什么都没有,仔细想想也很合理,只是添加了一个菜单项而已,一个菜单项怎么会弹出另一个菜单呢?效果如下(可以看到鼠标按下去什么都没有出来)
还记得刚才我说的吗,普通菜单其实就是横向的那一条,每一个都是一个菜单项。好,现在让我们来试验一下,再在Menu中添加一个菜单项:
Menu.AppendMenu(MF_STRING, ID_APP_EDIT, "&Edit");
可以看到又出来一项,仍然是按下去什么都没有,但这证明了我们之前的结论——Menu就好比横过来的一个菜单,有固定的位置。
如何要让这个菜单项按下去出现另一个菜单,就要另外创建一个浮动菜单(Pop-up Menu),因为只有浮动菜单可以连到另一个菜单中去。好,让我们修改代码成下面这个样子:
CMenu Menu,PopupMenu;
Menu.CreateMenu();
PopupMenu.CreatePopupMenu();
PopupMenu.AppendMenu(MF_STRING,IDC_POPMENU_FILE_NEW,"&New");
PopupMenu.AppendMenu(MF_STRING,IDC_POPMENU_FILE_OPEN,"&Open");
PopupMenu.AppendMenu(MF_STRING,IDC_POPMENU_FILE_CLOSE,"&Close");
PopupMenu.AppendMenu(MF_STRING,IDC_POPMENU_FILE_EXIT,"E&xit");
Menu.AppendMenu(MF_POPUP, (UINT)PopupMenu.m_hMenu, "&File"); //关键!
Menu.AppendMenu(MF_STRING, ID_APP_EDIT, "&Edit");
SetMenu(&Menu);
Menu.Detach();
PopupMenu.Detach();
然后看看效果:
现在我们来详细分析一下上面的代码。
首先,再创建一个CMenu类的对象PopupMenu,然后调用函数CreatePopupMenu创建了一个空的浮动菜单。紧接着向这个空的浮动菜单中加入我们想要的菜单项。接着是最关键的一步:
Menu.AppendMenu(MF_POPUP, (UINT)PopupMenu.m_hMenu, "&File");
这一步是向普通菜单Menu中追加一项。注意这里的参数和下一句有什么不同。第一个参数设成MF_POPUP,表示本菜单项(即“File”这个菜单项),是用来操纵一个浮动菜单的,第二个参数,原本应该是新菜单项的命令ID,这里设成子菜单的句柄。因为单击Fiile菜单项的时候,它的动作是弹出浮动菜单,所以它就没有必要拥有命令ID了(命令ID只是为了处理单击消息而设),最后一个参数,是这个菜单项在Menu中的标题,如果换成别的字符串,譬如Hello,那么最上面的File将变成Hello。
这里要强调一点,普通的Menu(相对Pop-up Menu而言),就只是标题栏下面那一条,这一行上都是它的菜单项(你可以把它看成是一个横过来放的菜单);而所有在鼠标点击后才出现的菜单(不管是左键还是右键),统统都是Pop-up Menu(也可以认为,Menu就是横排的菜单,Pop-up Menu就是竖排的菜单)。以一个Windows Shell窗口为例,只有最上一行才是Menu,其中诸如“文件”,“编辑”等等,每个都是Menu的一个Item,其余,都是Pop-up Menu。
最后简单介绍一下如何实现右键弹出菜单。步骤很简单:首先,映射WM_CONTEXTMENU消息,在消息处理函数中,先通过指定要弹出的子菜单的位次,用函数GetSubMenu函数得到该子菜单的指针。这里要注意,指定位次的菜单项,必须是有MF_POPUP属性的,并且的菜单项是从上到下以0为开始计数的(对于普通Menu则是从左到右)。得到想要的菜单的指针后,就可以用函数TrackPopupMenu把菜单在指定的位置显示出来了。整个用于显示的代码只有一行即可(当然为了安全性可以增加一些ASSERT宏进行检测,这里为了突出重点而略过):
GetMenu()->GetSubMenu(0)->
TrackPopupMenu(TPM_LEFTALIGN|TPM_RIGHTBUTTON,point.x,point.y,this);
这里GetMenu得到最上面的主菜单指针,GetSubMenu(0)得到File下面的Pop-up菜单指针,最后用TrackPopupMenu将其显示出来。