📄 005.txt
字号:
.endif
和处理WM_COMMAND消息不同的是,在WM_SYSCOMMAND消息中处理了自定义的菜单命令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_COMMAND消息发到这个窗口)的菜单,菜单句柄是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中使用,这个菜单句柄就是主菜单中的“查看”菜单,所以按鼠标右键弹出的菜单和下拉菜单中的“查看”菜单是一模一样的。
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
invoke CheckMenuRadioItem,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
5.2 图标和光标
图标和光标是图形资源,图标通常用做应用程序的“形象代表”出现在文件浏览器、运行窗口左上角或程序的快捷方式等所有代表文件的地方,为自己写的应用程序选一个合适的图标会使程序变得引人注目;而光标就是鼠标移动时屏幕上那个指示位置的东西,应用程序可以定义自己的光标,这样光标移到程序的客户区中就会变成需要的形状。
5.2.1 图标和光标的资源定义
和菜单、加速键等资源不同,在资源脚本文件中定义图标和光标时并不是一个个像素地定义,而是指定图标和光标的文件名,由资源编译器将像素数据读入再转换成二进制格式,所以在资源定义之前要用其他工具先创建图标和光标文件。图标和静态光标文件的扩展名分别是ico和cur,还有一种扩展名为ani的动态光标文件。
光标和图标在资源文件中的定义语句是:
图标ID ICON [DISCARDABLE] 图标文件名 ;定义图标
光标ID CURSOR [DISCARDABLE] 光标文件名 ;定义光标
DISCARDABLE关键字是内存选项,表示在不用的时候可以从内存暂时卸掉,当文件名包含空格时,两边要用双引号引起来,图标ID和光标ID同样也可以用16位的整数或字符串表示,这里是几个定义的例子:
MyIcon icon “1.ico” ;把1.ico定义为ID为“MyIcon”的图标资源
1000 icon discardable 2.ico ;把2.ico定义为ID为1000的图标资源
1001 icon “big icon.ico” ;把big icon.ico定义为ID为1001的图标资源
1002 cursor “big arrow.ani” ;把big arrow.ani定义为ID为1002的光标资源
GoodCursor cursor arrow.cur ;把arrow.cur定义为ID为“GoodCursor”的光标资源
资源文件中定义的图标可以不止一个,但Windows在“我的电脑”中列出文件的时候总是使用资源中的第一个图标当做文件的图标,所以在资源脚本文件中要把想用做程序图标的图标定义语句排在最前面。
5.2.2 使用图标和光标
在这里,用一个例子来说明图标和光标的用法,程序是建立在FirstWindow.asm和Menu.asm程序的基础上的,为了节省篇幅,在这里就不列出全部源程序了,完整的源程序可以在所附光盘的Chapter05\Icon目录中找到。程序中创建了一个菜单,运行后可以在“图标和光标”菜单中选择不同的图标和光标,选择不同的图标以后,窗口标题栏左边的图标和桌面任务栏上的窗口图标都会变化;选择不同的光标后,当鼠标移动到窗口客户区中的时候,光标会变成程序指定的光标。具体的效果如图5.3所示,大图标对应“笑脸”,小图标对应“箭头”,而光标A和B分别是“小恐龙”光标和“手型”光标,其中“小恐龙”光标是ani类型的动态光标,在屏幕上显示为一个走动中的恐龙模样
图5.3 不同的图标和光标
资源文件Icon.rc的定义如下:
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#include
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#define ICO_BIG Ox1000
#define ICO_SMALL Ox1001
#define CUR_2 Ox1000
#define IDM_MAIN Ox2000
#define IDM_EXIT Ox2101
#define IDM_BIG Ox2201
#define IDM_SMALL Ox2202
#define IDM_CUR1 Ox2203
#define IDM_CUR2 Ox2204
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
ICO_SMALL ICON "Small.ico"
ICO_BIG ICON "Big.ico"
CUR_2 CURSOR "2.cur"
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
IDM_MAIN menu discardable
BEGIN
popup "文件(&F)"
BEGIN
menuitem "退出(&X)", IDM_EXIT
END
popup "图标和光标(&I)"
BEGIN
menuitem "大图标(&G)", IDM_BIG
menuitem "小图标(&M)", IDM_SMALL
menuitem separator
menuitem "光标A(&A)", IDM_CUR1
menuitem "光标B(&B)", IDM_CUR2
END
END
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
经过上一节的“洗礼”,读者对菜单的定义应该很熟悉了,这里就不再说明IDM_MAIN的定义了,脚本文件中定义了ICO_SMALL,ICO_BIG两套图标和CUR_2静态光标,磁盘上还有个动态光标文件1.ani。
Icon.asm的大部分是窗口模板程序的内容,和FirstWindow.asm是相同的,仅在窗口过程的WM_CREATE和WM_COMMAND增加了一些内容:
.const
szCursorFile db ~1.ani~,0
.code
…
.if eax == WM_CREATE
invoke LoadIcon,hInstance,ICO_BIG
mov hIcoBig,eax
invoke LoadIcon,hInstance,ICO_SMALL
mov hIcoSmall,eax
invoke LoadCursorFromFile,addr szCursorFile
mov hCur1,eax
invoke LoadCursor,hInstance,CUR_2
mov hCur2,eax
invoke SendMessage,hWnd,WM_COMMAND,IDM_BIG,NULL
invoke SendMessage,hWnd,WM_COMMAND,IDM_CUR1,NULL
.elseif eax == WM_COMMAND
mov eax,wParam
movzx eax,ax
.if eax == IDM_EXIT
call _Quit
.elseif eax == IDM_BIG
invoke SendMessage,hWnd,WM_SETICON,ICON_BIG,hIcoBig
invoke CheckMenuRadioItem,hMenu,\
IDM_BIG,IDM_SMALL,IDM_BIG,MF_BYCOMMAND
.elseif eax == IDM_SMALL
invoke SendMessage,hWnd,\
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -