WIN32汇编语言教程:第05章 使用资源 · 5.1 菜单和加速键(5)
1. 加载菜单
在窗口中加载菜单的方法在第04章已经提及,方法有两个:一是在注册窗口类的时候指定类的默认菜单;二是在建立窗口的时候在参数中指定菜单句柄。Menu.asm程序中用的是第2种方法:
invoke CreateWindowEx,WS_EX_CLIENTEDGE,\ offset szClassName,offset szCaptionMain,\ WS_OVERLAPPEDWINDOW,\ 100,100,400,300,\ NULL,hMenu,hInstance,NULL
在参数中指出了hMenu。不管用哪种方法,首先都必须使用LoadMenu函数来获取菜单句柄hMenu,如下面的语句:
invoke LoadMenu,hInstance,IDM_MAIN mov hMenu,eax
这个函数的第1个参数是用GetModuleHandle获取的实例句柄,第2个参数指定需要装入的菜单资源ID,函数返回菜单句柄。在得到菜单句柄以后,我们先把它放入hMenu变量保存起来以便后用。
当资源文件中用字符串为名称定义菜单而不是用数值的时候,例如:
MainMenu menu //定义菜单名为字符串“MainMenu” begin ... end
那么在程序中就必须用字符串指针代替菜单ID做参数:
szMenu "MainMenu",0 ;在数据段中定义菜单名称字符串 ... invoke LoadMenu,hInstance,addr szMenu ;在程序中装载 mov hMenu,eax
用字符串为名称定义资源,在资源装载函数LoadXXXX中用字符串指针做参数装入,这实际上是一个通用的方法,不仅适用于菜单资源,对于其他类别的资源也是适用的。在其他资源的介绍中就不再另外说明了。
2. 加载加速键
和菜单一样,加速键在使用前也要装入,参数同样是在资源脚本文件中定义的加速键ID,程序中对应的语句是:
invoke LoadAccelerators,hInstance,IDA_MAIN mov @hAccelerator,eax
其实我们自己在程序中也可以很方便地实现加速键功能,方法是:在WM_KEYDOWN消息中判断键盘消息并按照自定义的逻辑进行处理,使用加速键实际上是让Windows替我们完成这个功能,Windows实现的方法正是在消息循环中检查WM_KEYDOWN和WM_SYSKEYDOWN消息。下面是使用加速键时消息循环的代码,请注意粗体字部分:
.while TRUE invoke GetMessage,addr @stMsg,NULL,0,0 .break .if eax == 0 invoke TranslateAccelerator,hWinMain,@hAccelerator,addr @stMsg .if eax == 0 invoke TranslateMessage,addr @stMsg invoke DispatchMessage,addr @stMsg .endif .endw
TranslateAccelerator函数是实现加速键功能的核心,它的参数为目标窗口、加速键句柄和GetMessage取得的消息结构。该函数检查消息结构中的消息,如果遇到WM_KEYDOWN和WM_SYSKEYDOWN消息则检测加速键资源,看按键是否符合某个加速键,符合的话则向目标窗口发送WM_COMMAND或WM_SYSCOMMAND消息,并返回TRUE,不符合的话不进行任何处理并返回FALSE。
由于加速键的键码并不是用户真正想输入窗口的,比如用户在写字板中输入文字,按Ctrl+C键是为了“拷贝”,而并不是想把Ctrl+C键对应的字符输入文档,所以这个Ctrl+C的键码在完成加速键的使命后就应该丢弃,也就是说符合加速键的键盘消息不应该再发送给窗口,TranslateMessage和DispatchMessage函数前的逻辑判断就是这样的意图:只有TranslateAccelerator没有转换的消息(返回值eax为0)才继续处理。
另外,TranslateAccelerator的参数中有个“目标窗口”,例子中是程序的主窗口hWinMain,为什么要设置这样一个参数而不像DispatchMessage函数一样使用MSG结构中的hwnd呢?这是因为键盘消息可以产生于不同窗口中——既可能是主窗口,也可能是其他子窗口,如果用@stMsg.hwnd做目标窗口,就必须在所有子窗口的窗口过程中都设置处理加速键消息的代码,这显然是一种浪费,所以一般把所有的加速键消息都发送到主窗口,然后集中在主窗口的窗口过程中处理WM_COMMAND消息,这样有利于精简代码。
3. 菜单和加速键消息
当用户选择了一个菜单项的时候,Windows向菜单所属的窗口发送WM_COMMAND消息;而用户按下了一个加速键的时候,Windows向TranslateAccelerator函数指定的目标窗口发送WM_COMMAND消息。一般这两种情况对应的窗口都是主窗口,所以可以在主窗口中的窗口过程中集中处理WM_COMMAND消息,而不必考虑它究竟是菜单引发的还是加速键引发的。
WM_COMMAND消息的两个参数是这样定义的:
wParam的高位 = wNotifyCode ;通知码
wParam的低位 = wID ;命令ID
lParam = hwndCtl ;发送WM_COMMAND的子窗口句柄
除了菜单和加速键,WM_COMMAND消息也可以由其他子窗口引发,如主窗口中的按钮或工具栏等,lParam参数指定了引发消息的子窗口句柄,对于菜单和加速键引发的WM_COMMAND消息,lParam的值为零。wParam参数的低16位是命令ID,也就是资源脚本文件中菜单项的命令ID或加速键的命令ID,高16位是通知码,菜单消息的通知码是0,加速键消息的通知码为1。
在需要处理菜单和加速键消息的窗口过程中,一般需要增加一个WM_COMMAND分支来处理对应的消息,这个分支的一般结构为:
.elseif eax == WM_COMMAND ;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_COMMAND消息前将wParam的值通过一个对话框显示出来,读者可以和资源脚本文件中定义的命令ID值对比一下,在正常使用的程序中可以去掉这一句。
读者可以发现,资源文件中定义的“字体”菜单项的ID为Ox4201,当选中“字体”菜单项的时候,对话框中显示的wParam数值正是00004201,而按下加速键Alt+F的时候,显示出来的值就是00014201了,它们的区别就是高16位中的通知码不同。