按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
由于加速键的键码并不是用户真正想输入窗口的,比如用户在写字板中输入文字,按Ctrl+C键是为了“拷贝”,而并不是想把Ctrl+C键对应的字符输入文档,所以这个Ctrl+C的键码在完成加速键的使命后就应该丢弃,也就是说符合加速键的键盘消息不应该再发送给窗口,TranslateMessage和DispatchMessage函数前的逻辑判断就是这样的意图:只有TranslateAccelerator没有转换的消息(返回值eax为0)才继续处理。
另外,TranslateAccelerator的参数中有个“目标窗口”,例子中是程序的主窗口hWinMain,为什么要设置这样一个参数而不像DispatchMessage函数一样使用MSG结构中的hwnd呢?这是因为键盘消息可以产生于不同窗口中——既可能是主窗口,也可能是其他子窗口,如果用@stMsg。hwnd做目标窗口,就必须在所有子窗口的窗口过程中都设置处理加速键消息的代码,这显然是一种浪费,所以一般把所有的加速键消息都发送到主窗口,然后集中在主窗口的窗口过程中处理WM_MAND消息,这样有利于精简代码。
3。 菜单和加速键消息
当用户选择了一个菜单项的时候,Windows向菜单所属的窗口发送WM_MAND消息;而用户按下了一个加速键的时候,Windows向TranslateAccelerator函数指定的目标窗口发送WM_MAND消息。一般这两种情况对应的窗口都是主窗口,所以可以在主窗口中的窗口过程中集中处理WM_MAND消息,而不必考虑它究竟是菜单引发的还是加速键引发的。
WM_MAND消息的两个参数是这样定义的:
wParam的高位 = wNotifyCode ;通知码
wParam的低位 = wID ;命令ID
lParam = hwndCtl ;发送WM_MAND的子窗口句柄
除了菜单和加速键,WM_MAND消息也可以由其他子窗口引发,如主窗口中的按钮或工具栏等,lParam参数指定了引发消息的子窗口句柄,对于菜单和加速键引发的WM_MAND消息,lParam的值为零。wParam参数的低16位是命令ID,也就是资源脚本文件中菜单项的命令ID或加速键的命令ID,高16位是通知码,菜单消息的通知码是0,加速键消息的通知码为1。
在需要处理菜单和加速键消息的窗口过程中,一般需要增加一个WM_MAND分支来处理对应的消息,这个分支的一般结构为:
。elseif eax WM_MAND ;eax中为wMsg
mov eax;wParam
movzx eax;ax
。if eax 命令ID1
。。。
。elseif eax 命令ID2
。。。
。endif
其中movzx eax;ax指令将16位的ax扩展到32位的eax,相当于将eax的高16位填零,作用就是当消息由加速键引起时,将高16位中的1忽略,这样下面的分支就可以同时处理菜单和加速键消息,当然读者也可以去掉这一句,这样下面的比较语句中就要使用ax而不是eax。
在例子程序中,mov eax;wParam前面还有一句invoke _DisplayMenuItem;wParam,作用是在处理WM_MAND消息前将wParam的值通过一个对话框显示出来,读者可以和资源脚本文件中定义的命令ID值对比一下,在正常使用的程序中可以去掉这一句。
读者可以发现,资源文件中定义的“字体”菜单项的ID为Ox4201,当选中“字体”菜单项的时候,对话框中显示的wParam数值正是00004201,而按下加速键Alt+F的时候,显示出来的值就是00014201了,它们的区别就是高16位中的通知码不同。
来源:电子工业出版社 作者:罗云彬 上一页 回书目 下一页
上一页 回书目 下一页
第5章 使用资源
5。1 菜单和加速键(6)
4。 菜单项的修改
在程序的运行中也可以动态修改菜单项,包括添加、删除和修改操作,这些操作是通过几个API函数来完成的:
invoke AppendMenu;hMenu;uFlags;uIDNewItem;lpNewItem ;添加菜单项
invoke InsertMenu;hMenu;uPosition;uFlags;uIDNewItem;lpNewItem ;插入菜单项
invoke ModifyMenu;hMenu;uPosition;uFlags;uIDNewItem;lpNewItem ;修改菜单项
invoke DeleteMenu;hMenu;uPosition;uFlags ;删除菜单项
invoke RemoveMenu;hMenu;uPosition;uFlags ;删除菜单项
其中AppendMenu用来在一个菜单的最后添加菜单项,InsertMenu则在中间插入菜单项,ModifyMenu可以修改一个菜单项的文字,DeleteMenu和RemoveMenu则可以删除一个菜单项。
这些函数中的参数都是雷同的,hMenu参数指要操作的菜单句柄;uPosition用来定位要操作的菜单项,定位的方法有两种:用命令ID定位或用位置索引,用哪一种方法取决于后面的uFlags参数,当uFlags为MF_BYMAND时,uPostion为菜单项的命令ID,而当uFlags为MF_BYPOSITION的时候,uPostion表示菜单项的位置索引,索引是从0开始的,也就是说第一个菜单项的索引值为0。
AppendMenu函数总是在菜单的最后添加新的菜单项,所以不需要uPostion参数。
对于AppendMenu和InsertMenu,会有一个新的菜单项产生,uIDNewItem就表示这个新菜单项的命令ID,lpNewItem指向新菜单项的文字字符串,ModifyMenu函数可以修改一个菜单项的命令ID或文字字符串,所以也有uIDNewItem和lpNewItem参数。而用来删除菜单项的DeleteMenu和RemoveMenu显然用不着uIDNewItem和lpNewItem参数。
uFlags参数除了指定MF_BYMAND还是MF_BYPOSITION外,还可以组合指定菜单项的其他属性,如MF_CHECKED,MF_DISABLED,MF_ENABLED,MF_GRAYED,MF_MENUBARBREAK,MF_MENUBREAK,MF_SEPARATOR和MF_UNCHECKED等,从其字面上就可以看出这些属性的含义。
DeleteMenu和RemoveMenu的不同之处在于对popup菜单项的处理。当它们用于popup属性的菜单项时,DeleteMenu不仅删除菜单项,而且将这个popup菜单项的所有子项目全部删除,这样,这个popup菜单就不能在别的地方继续使用;而RemoveMenu仅从菜单中移去这个popup菜单项,整个popup菜单在内存中还是存在的。以Menu。asm程序为例,按鼠标右键弹出的菜单实际上是主菜单中的“查看”菜单项,假如用DeleteMenu删除主菜单中的“查看”项目,那么按右键也就弹不出菜单了,而用RemoveMenu删除主菜单中的“查看”项目,按鼠标右键仍然可以弹出菜单。对于非popup属性的菜单项,DeleteMenu和RemoveMenu的效果是同样的。
5。 使用系统菜单
系统菜单指按下了标题栏图标后弹出的菜单,和窗口菜单不同,选中系统菜单的菜单项后,Windows向窗口发送的是WM_SYSMAND消息而非WM_MAND消息。默认的系统菜单中已经有“还原”、“移动”、“大小”、“最大化”、“最小化”和“关闭”等菜单项,这些菜单项的命令ID已经预定义为SC_RESTORE,SC_MOVE,SC_SIZE,SC_MAXIMIZE,SC_MINIMIZE和SC_CLOSE等,如果读者要自己处理它们,可以在WM_SYSMAND消息中建立一个比较分支对它们进行处理,一般在程序中并不自己处理WM_SYSMAND消息,而是交给DefWindowProc处理。
如何在系统菜单中添加自己的菜单项呢?方法就是使用上面介绍的AppendMenu(当然也可以用InsertMenu),在添加前必须用GetSystemMenu函数首先获取系统菜单的句柄。例子程序在窗口初始化的时候在系统菜单尾添加了一个分隔线和两个菜单项:“帮助主题”和“关于本程序”:
。if eax WM_CREATE
。。。
invoke GetSystemMenu;hWnd;FALSE
mov @hSysMenu;eax
invoke AppendMenu;@hSysMenu;MF_SEPARATOR;0;NULL
invoke AppendMenu;@hSysMenu;0;IDM_HELP;offset szMenuHelp
invoke AppendMenu;@hSysMenu;0;IDM_ABOUT;offset szMenuAbout
在窗口过程中处理系统菜单消息的分支结构为:
。elseif eax WM_SYSMAND
mov eax;wParam
。if ax 自定义ID1
。。。
。elseif ax 自定义ID2
。。。
。else
invoke DefWindowProc;hWnd;uMsg;wParam;lParam
ret
。endif
和处理WM_MAND消息不同的是,在WM_SYSMAND消息中处理了自定义的菜单命令ID后,必须把其他命令ID交给DefWindowProc处理,并直接把返回值返回给Windows,不然的话会发现窗口不能移动,不能关闭,不能最小化……因为它相当于屏蔽了所有SC_RESTORE,SC_MOVE,SC_SIZE,SC_MAXIMIZE,SC_MINIMIZE和SC_CLOSE等消息的处理。
6。 右键弹出菜单
例子程序的一个功能是当用户在窗口客户区按下鼠标右键的时候弹出一个菜单,这个功能是用TrackPopupMenu函数实现的。TrackPopupMenu函数的用法:
invoke TrackPopupMenu;hMenu;uFlags;x;y;nReserved;hWnd;lpRect
这个函数本身很简单,执行后在参数指定的x,y位置弹出一个属于hWnd窗口(也就是说WM_MAND消息发到这个窗口)的菜单,菜单句柄是hMenu。函数中的坐标是以整个屏幕左上角为基准的,所以弹出菜单的位置不一定在窗口的客户区内,它可以是屏幕的任何一个地方。
uFlags参数指定一些和位置相关的选项,它可以是PM_CENTERALIGN,TPM_LEFTALIGN或TPM_RIGHTALIGN三者之一,表示(x,y)坐标是代表弹出菜单位置的中间、左上角还是右上角,一般的习惯是使用TPM_LEFTALIGN,这样菜单会在鼠标点击处的右边弹出。uFlags中同时还可以指定用鼠标左键还是右键选定菜单项,定义值可以是TPM_LEFTBUTTON或TPM_RIGHTBUTTON,如果选择TPM_RIGHTBUTTON的话,对在菜单项上面按鼠标左键是没有反应的。
lpRect指向一个RECT结构,用来指定一个区域,当菜单弹出后,在这个区域外单击鼠标,菜单才会消失,如果这个参数指定为NULL的话,在菜单之外单击鼠标,菜单就会消失。
在使用TrackPopupMenu之前,有几个准备工作是要做的:为了在客户区中按下鼠标右键弹出菜单,我们当然要处理鼠标右键消息,也就是说在WM_RBUTTONDOWN消息中调用TrackPopupMenu函数,一般的习惯是在鼠标按下的地方弹出菜单,所以还要首先获取鼠标光标的位置,然后在此位置弹出菜单。
要获取鼠标位置,可以用GetCursorPos函数:
invoke GetCursorPos;lpPoint
参数lpPoint指向一个POINT数据结构,这个结构只有两个字段:
POINT STRUCT
x DWORD ?
y DWORD ?
POINT ENDS
该结构用来表示一个点的(x,y)坐标,GetCursorPos将当前的鼠标位置返回到这个结构中,程序中的相关代码是:
local @stPos:POINT ;首先定义一个POINT结构
。。。
invoke GetCursorPos;addr @stPos ;获取鼠标位置
invoke TrackPopupMenu;hSubMenu;
TPM_LEFTALIGN;@stPos。x;@stPos。y;NULL;hWnd;NULL
用GetCursorPos获取的鼠标位置是一个POINT结构,但TrackPopupMenu输入坐标的方法是用x,y两个参数,而不是一个POINT结构,所以要用结构中的两个字段@stPos。x和@stPos。y分别输入。
使用TrackPopupMenu时要注意的是,弹出的菜单句柄必须是popup类型的,而在资源文件中定义并且可以用LoadMenu函数装入的菜单并不是popup类型的,popup菜单(如例子中的“文件”与“查看”等)只能在第二层中才能定义,在程序中用GetSubMenu得到的第二层子菜单的句柄才是popup类型的。GetSubMenu函数的用法是:
invoke GetSubMenu;hMenu;nPos
。if eax
mov hSubMenu;eax
。endif
nPos参数指定要获取的菜单的位置索引,GetSubMenu的返回值是获取的子菜单句柄。
例子用invoke GetSubMenu;hMenu;1取得第二个子菜单(“文件”子菜单为0,“查看”子菜单为1,……)的句柄,然后在TrackPopupMenu中使用,这个菜单句柄就是主菜单中的“查看”菜单,所以按鼠标右键弹出的菜单和下拉菜单中的“查看”菜单是一模一样的。
来源:电子工业出版社 作者:罗云彬 上一页 回书目 下一页
上一页 回书目 下一页
第5章 使用资源
5。1 菜单和加速键(7)
7。 菜单状态的检测和设置
在程序中经常要对菜单项的状态进行设置,如剪贴板中没有数据时,“粘贴”菜单项应该灰化,窗口中没有被选中的字符时,“拷贝”菜单项也应该灰化,这样可以给使用者一个善意的提醒。同样,对菜单的状态也常常需要检测,如查看菜单项的状态是否处于灰化状态或选中状态以便进行下一步操作等。
对菜单项状态的检测可以用GetMenuState函数来完成,用法是:
invoke GetMenuState;hMenu,uId,uFlags
参数hMenu是菜单的句柄,uId用来定位要检测的菜单项,当uFlags是MF_BYMAND的时候,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_BYMAND
。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_BYMAND或MF_BYPOSITION定位方法。
在这些函数的参数中,uIDEnableItem,uIDCheckItem,idFirst,idLast和idCheck用来定位菜单项,同样,参数的取值可以是菜单项的命令ID或位置索引,可以在状态参数(uEnable,uCheck,uFlags)中组合定义MF_BYMAND或MF_BYPOSITION来决定使用哪种方法。
在例子程序中,当选中IDM_TOOLBAR和IDM_STATUSBAR之间的菜单项的时候,程序先用invoke GetMenuState;hMenu;ebx;MF_BYMAND获取当前的状态,检查是否选定,并将选定状态反转后用CheckMenuItem重新设置:
。elseif eax 》= IDM_TOOLBAR && eax