📄 005.txt
字号:
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.endif
;********************************************************************
xor eax,eax
ret
_ProcWinMain endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_WinMain proc
local @stWndClass:WNDCLASSEX
local @stMsg:MSG
local @hAccelerator
invoke GetModuleHandle,NULL
mov hInstance,eax
invoke LoadMenu,hInstance,IDM_MAIN
mov hMenu,eax
invoke LoadAccelerators,hInstance,IDA_MAIN
mov @hAccelerator,eax
;********************************************************************
; 注册窗口类
;********************************************************************
invoke RtlZeroMemory,addr @stWndClass,sizeof @stWndClass
invoke LoadIcon,hInstance,ICO_MAIN
mov @stWndClass.hIcon,eax
mov @stWndClass.hIconSm,eax
push hInstance
pop @stWndClass.hInstance
mov @stWndClass.cbSize,sizeof WNDCLASSEX
mov @stWndClass.style,CS_HREDRAW or CS_VREDRAW
mov @stWndClass.lpfnWndProc,offset _ProcWinMain
mov @stWndClass.hbrBackground,COLOR_WINDOW + 1
mov @stWndClass.lpszClassName,offset szClassName
invoke RegisterClassEx,addr @stWndClass
;********************************************************************
; 建立并显示窗口
;********************************************************************
invoke CreateWindowEx,WS_EX_CLIENTEDGE,\
offset szClassName,offset szCaptionMain,\
WS_OVERLAPPEDWINDOW,\
100,100,400,300,\
NULL,hMenu,hInstance,NULL
mov hWinMain,eax
invoke ShowWindow,hWinMain,SW_SHOWNORMAL
invoke UpdateWindow,hWinMain
;********************************************************************
; 消息循环
;********************************************************************
.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
ret
_WinMain endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
start:
call _WinMain
invoke ExitProcess,NULL
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
end start
1. 加载菜单
在窗口中加载菜单的方法在第4章已经提及,方法有两个:一是在注册窗口类的时候指定类的默认菜单;二是在建立窗口的时候在参数中指定菜单句柄。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位中的通知码不同。
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_BYCOMMAND时,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_BYCOMMAND还是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_SYSCOMMAND消息而非WM_COMMAND消息。默认的系统菜单中已经有“还原”、“移动”、“大小”、“最大化”、“最小化”和“关闭”等菜单项,这些菜单项的命令ID已经预定义为SC_RESTORE,SC_MOVE,SC_SIZE,SC_MAXIMIZE,SC_MINIMIZE和SC_CLOSE等,如果读者要自己处理它们,可以在WM_SYSCOMMAND消息中建立一个比较分支对它们进行处理,一般在程序中并不自己处理WM_SYSCOMMAND消息,而是交给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_SYSCOMMAND
mov eax,wParam
.if ax == 自定义ID1
...
.elseif ax == 自定义ID2
...
.else
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -