WIN32汇编语言教程:第05章 使用资源 · 5.1 菜单和加速键(7)

7. 菜单状态的检测和设置

在程序中经常要对菜单项的状态进行设置,如剪贴板中没有数据时,“粘贴”菜单项应该灰化,窗口中没有被选中的字符时,“拷贝”菜单项也应该灰化,这样可以给使用者一个善意的提醒。同样,对菜单的状态也常常需要检测,如查看菜单项的状态是否处于灰化状态或选中状态以便进行下一步操作等。

对菜单项状态的检测可以用GetMenuState函数来完成,用法是:

   invoke GetMenuState,hMenu,uId,uFlags

参数hMenu是菜单的句柄,uId用来定位要检测的菜单项,当uFlags是MF_BYCOMMAND的时候,uId用菜单项的命令ID指定,当uFlags是MF_BYPOSITION的时候,uId的值是位置索引,函数执行后的返回值为-1时表示失败,否则会是MF_CHECKED,MF_DISABLED,MF_GRAYED,MF_HILITE,MF_MENUBARBREAK,MF_MENUBREAK和MF_SEPARATOR的组合值,它们分别表示菜单项的状态是选中、禁用、灰化、高亮显示以及3种分隔线,读者可以用test指令测试相应的数据位来分辨菜单项处于哪种状态,一般的测试代码如下:

invoke   GetMenuState,hMenu,IDM_XXX,MF_BYCOMMAND
.if      eax & MF_CHECKED
         ;表示IDM_XXX菜单项现在是选中状态
.endif

同样,读者也可以用eax & MF_DISABLED和eax & MF_GRAYED等条件测试其他状态。

设置菜单项的状态可以用下列3个函数来实现不同的功能:

invoke EnableMenuItem,hMenu,uIDEnableItem,uEnable
invoke CheckMenuItem,hMenu,uIDCheckItem,uCheck
invoke CheckMenuRadioItem,hMenu,idFirst,idLast,idCheck,uFlags

EnableMenuItem函数将菜单项在禁用、可用和灰化状态之间切换,uEnable可以取值为MF_DISABLED,MF_ENABLED和MF_GRAYED,分别代表这3种状态。

CheckMenuItem函数将菜单项在非互斥的选定状态和非选定状态之间切换(即前面是否有对钩),uCheck的取值可以是MF_CHECKED或MF_UNCHECKED,代表选定或非选定状态。

CheckMenuRadioItem将菜单项在互斥的选定状态和非选定状态之间切换(即前面是否有圆点标志),由于互斥的菜单项在一个范围内只有一个是可以选定的,当选定另一个的时候,原来的选定应该撤销,idFirst和idLast就指定了这个互斥范围。函数在选定idCheck指定的菜单项的同时将自动清除idFirst和idLast范围内的其他选定。所以uFlags中无需指定状态,只需指定MF_BYCOMMAND或MF_BYPOSITION定位方法。

在这些函数的参数中,uIDEnableItem,uIDCheckItem,idFirst,idLast和idCheck用来定位菜单项,同样,参数的取值可以是菜单项的命令ID或位置索引,可以在状态参数(uEnable,uCheck,uFlags)中组合定义MF_BYCOMMAND或MF_BYPOSITION来决定使用哪种方法。

在例子程序中,当选中IDM_TOOLBAR和IDM_STATUSBAR之间的菜单项的时候,程序先用invoke GetMenuState,hMenu,ebx,MF_BYCOMMAND获取当前的状态,检查是否选定,并将选定状态反转后用CheckMenuItem重新设置:

.elseif eax >= IDM_TOOLBAR && eax <= IDM_STATUSBAR
       mov    ebx,eax
       invoke GetMenuState,hMenu,ebx,MF_BYCOMMAND
       .if    eax == MF_CHECKED
              mov    eax,MF_UNCHECKED
       .else
              mov    eax,MF_CHECKED
       .endif
       invoke CheckMenuItem,hMenu,ebx,eax

当选中IDM_BIG和IDM_DETAIL之间的菜单项的时候,程序用CheckMenuRadioItem将原先IDM_BIG和IDM_DETAIL范围内的互斥选定撤销并将当前选定的菜单项加圆点标记。

.elseif eax >= IDM_BIG && eax <= IDM_DETAIL
     invokeCheckMenuRadioItem,hMenu,IDM_BIG,IDM_DETAIL,eax,MF_BYCOMMAND

最后,修改菜单状态的时机是什么时候呢?在程序中似乎不应该随时去检测状态并设置,这显然是很浪费资源的。Windows考虑到了这一点:在菜单将要激活的时候,也就是用户在菜单上按动鼠标的时候,Windows在菜单弹出之前会向窗口过程发送WM_INITMENU消息,我们可以从容不迫地在这里进行各种检测,并设置对应的菜单项。

读者可以注意到,在状态参数中指定MF_BYCOMMAND或MF_BYPOSITION将决定位置参数用命令ID还是位置索引表示,这个规则在所有的菜单函数中都是适用的,MF_BYCOMMAND是默认值(它的定义值是0),如果两者都不定义的话,位置参数代表的就是命令ID。

8. 其他菜单函数

除了前面介绍的一些函数之外,还有一些不太常用的菜单函数,在这里作一个简单的介绍。

菜单不一定非要在资源文件中定义,在程序中也可以用代码来建立菜单,不过比较麻烦一点,方法是先用CreateMenu建立一个菜单,CreateMenu函数没有参数,调用后返回一个没有任何菜单项的菜单句柄,接下来就可以用AppendMenu在上面一条条地添加菜单项了。

同样,CreatePopupMenu也可以建立一个没有任何菜单项的菜单句柄,但它建立的是popup类型的菜单句柄,可以在TrackPopupMenu中直接使用。

如果要获取一个窗口当前使用的菜单句柄,那么可以使用GetMenu函数:

invoke GetMenu,hWnd
mov hMenu,eax

一个菜单的总项数可以用GetMenuItemCount函数获取:

invoke GetMenuItemCount,hMenu

不过GetMenuItemCount函数的返回值不包括子菜单展开以后的项数,而是指最上层菜单的项数,比如在例子程序中对hMenu统计的结果是3,因为Menu.rc中定义的最上层的菜单项是“文件”、“查看”和“帮助”,总共3个,如果要统计全部展开后的项数,那么只好用GetSubMenu一层层地统计下去了。

对菜单中各个菜单项当前的文字和命令ID也是可以查询的,方法是用GetMenuString和GetMenuItemID,读者可以参考命令手册。

建立窗口时指定了菜单句柄后并不是不能改变的,我们常常见到一些编辑软件,没有打开文件之前菜单只有寥寥几项,一打开文件以后功能菜单就全部出来了,实际上这是用SetMenu函数完成的:

invoke SetMenu,hWnd,hMenu

可以在资源文件中预定义几个不同的菜单,在使用的时候根据不同情况用SetMenu设置不同的菜单句柄。

使用菜单后,要涉及清除的问题,和窗口相连的菜单句柄在窗口摧毁的时候会由Windows自动释放,不需要手工释放,但没有和窗口相连的菜单就要由程序自己来释放了,方法是使用DestroyMenu函数,比如没有和窗口相连而仅用TrackPopupMenu弹出的菜单句柄:

invoke DestroyMenu,hMenu

上页:第05章 使用资源 · 5.1 菜单和加速键(6) 下页:第05章 使用资源 · 5.2 图标和光标(1)

第05章 使用资源

版权所有 © 中山市飞娥软件工作室 证书:粤ICP备09170368号