luqingfei@C++

为中华之崛起而崛起!
兼听则明,偏听则暗。

Win32汇编--使用资源--菜单和加速键

 

使用资源 —— 菜单和加速键

 

主菜单,顶层菜单

弹出式菜单,子菜单

右键弹出式菜单

系统弹出式菜单

 

菜单中的菜单项有好几种,从资源定义的角度来看,分隔用的横线也是一个菜单项,除横线外其他菜单项可以供用户选择,也可以设置为“禁止”或“灰化”状态暂时停用。

 

菜单项上的圆点表示选中标记是互斥的,对钩表示是不互斥的。

 

加速键就是菜单项的快捷键。表示当窗口是激活的时候,不必打开菜单,直接按快捷键就相当于选择了菜单项。

 

菜单和加速键的资源定义

资源脚本文件举例:

//Menu.rc

#include <resource.h>

#define ICO_MAIN                     0x1000           //图标

#define IDM_MAIN                    0x2000           //菜单

#define IDA_MAIN                     0x2000           //加速键

#define IDM_OPEN                    0x4101

#define IDM_OPTION                0x4102

#define IDM_EXIT                            0x4103

#define IDM_SETFONT              0x4201

#define IDM_SETCOLOR            0x4202

#define IDM_INACT                   0x4203

#define IDM_GRAY                    0x4204

#define IDM_BIG                        0x4205

#define IDM_SMALL                  0x4206

#define IDM_LIST                      0x4207

#define IDM_DETALL                 0x4208

#define IDM_TOOLBAR              0x4209

#define IDM_TOOLBARTEXT     0x4210

#define IDM_INPUTBAR             0x4211

#define IDM_STATUSBAR           0x4212

#define IDM_HELP                     0x4301

#define IDM_ABOUT                  0x4302

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

ICO_MAIN           ICON             “Main.ico”

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

IDM_MAIN           menu             discardable

BEGIN

                     popup “文件(&F)”

                     BEGIN

                                   menuitem              打开文件(&O)…”, IDM_OPEN

                                   menuitem              关闭文件(&C)…”, IDM_OPTION

                                   menuitem              separator

                                   menuitem              退出(&X)”, IDM_EXIT

                     END

                     popup “查看(&V)”

                     BEGIN

                                   menuitem              字体(&F)…\tAlt+F”, IDM_SETFONT

                                   menuitem              背景色(&B) …\tCtrl+Alt+B”, IDM_SETCOLOR

                                   menuitem              separator

                                   menuitem              被禁用的菜单项”, IDM_INACT, INACTIVE

                                   menuitem              被灰化的菜单项”, IDM_GRAY, GRAYED

                                   menuitem              separator

                                   menuitem              大图标(&G)”, IDM_BIG

                                   menuitem              小图标(&M)”, IDM_SMALL

                                   menuitem              列表(&L)”, IDM_LIST

                                   menuitem              详细资料(&D)”, IDM_DETAIL

                                   menuitem              separator

                                   menuitem              工具栏(&T)”

                                   BEGIN

                                                 menuitem              标准按钮(&S)”, IDM_TOOLBAR

                                                 menuitem              文字标签(&C)”, IDM_TOOLBARTEXT

                                                 menuitem              命令栏(&I)”, IDM_INPUTBAR

                                   END

                                   menuitem              状态栏(&U)”, IDM_STATUSBAR

                     END

                     popup “帮助(&H)” HELP

                     BEGIN

                                   menuitem              帮助主题(&H)\F1”, IDM_HELP

                                   menuitem              separator

                                   menuitem              关于本程序(&A)…”, IDM_ABOUT

                     END

END

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

IDA_MAIN           accelerators

BEGIN

                     VK_F1, IDM_HELP, VIRTKEY

                     “B”,        IDM_SETCOLOR, VIRTKEY, CONTROL, ALT

                     “F”,        IDM_SETFONT, VIRTKEY, ALT

END

//编译上述文件使用的makefile文件如下:

NAME = Menu

OBJS = $(NAME).obj

RES = $(NAME).res

 

LINK_FLAG = /subsystem:windows

ML_FLAG = /c /coff

 

$(NAME).exe: $(OBJ) $(RES)

                     Link $(LINK_FLAG) $(OBJS) $(RES)

 

.asm .obj:

                     ml $(ML_FLAG) $<

 

.rc .res:

                     rc $<

 

clean:

                     del *.obj

                     del *.res

 

为了编译资源文件,makefile中多了一个资源编译的隐含规则:

.rc .res:

              rc $<

同时在exe文件的依赖文件中增加了Menu.res文件。

 

rc文件中,各种语句使用的是C语言语法格式,因为资源编译器Rc.exe根本上就是Visual C++附带的,所以在定义等值语句的时候用的是#define,包含语句使用#include<文件名>,用到16进制数值的时候并不是用汇编的语法在后面加h,而是用前面加0x的方法,如1234h写为0x1234,注释也要用前面加//的方法。

 

在脚本文件的头部,首先要把MASM32软件包中的resource.h文件包含进来,这个文件中包括了资源定义中很多的预定义值,如窗口属性与加速键的键值等。资源在程序中的引用往往用一个数值来表示,称为资源的ID值,但在定义的时候直接使用数值不是很直观,所以往往用#define语句将数值定义为容易记忆的字符串。

 

菜单的定义

在资源脚本文件中菜单的定义格式是:

菜单ID          MENU           [DISCARDABLE]

BEGIN

              菜单项定义

             

END

“菜单ID   MENU   [DISCARDABLE]”语句用来指定菜单的ID值和内存属性,菜单ID可以是16位的整数,范围是1~65535,在Menu.rc文件中,定义的菜单ID2000h,但菜单ID也可以字符串表示,如下面的定义:

MainMenu menu

begin

              menuitem…

end

 

表示菜单的ID是字符串型的“MainMenu”,但这样定义的话,在程序中引用的时候就要用字符串指针代替16进制的菜单ID值,显得相当不便,所以在实际应用中通常使用16进制数值当做菜单ID

 

数值型ID的范围限制在1~65535之间的原因是字符串在内存中的线性地址总是大于10000hAPI函数检测参数时发现小于10000h时就可以把它认为是数值型的,大于10000h时就当做字符串指针处理。

 

menu关键字后面的DISCARDABLE是菜单的内存属性,表示菜单在不再使用的时候可以暂时从内存中释放以节省内存,这是一个可选属性。菜单项的定义语句必须包含在beginend关键字之内,这两个关键字也可以用花括号{}代替。

菜单项目的定义方法有3类:

       MENUITEM 菜单文字,命令ID[,选项列表]        (用法1

    MENUITEM SEPARATOR                                       (用法2

 POPUP 菜单文字[,选项列表]                               (用法3

       BEGIN

                     item-definitions

                    

       END

 

用法1定义的是普通菜单项

组成部分有:

菜单文字——显示在菜单项中的字符串。如果需要字符串中某个字母带下横线,那么可以在字母前面加&符号,带下横线的字母可以被系统自动当做快捷键。另外,如果要把加速键的提示信息显示在菜单项的右边,如“字体”菜单项中的“Alt+F”字符,可以在两者中间加\t,表示插入一个Tab字符),写为“字体(&F)…\tAlt+F”,这样Tab后面的字符在显示的时候会右对齐。

 

命令ID——用来分辨不同的菜单项,当菜单项被选中的时候,Windows会向窗口过程发送WM_COMMAND消息,消息的参数就是这个命令ID。用命令ID可以分辨用户究竟选中了哪个菜单项,所以不同的菜单项应该定义不同的ID值,除非想让两个菜单项的功能相同。

 

选项——用来定义菜单项的各种属性,它可以是下列数值:

       CHECKED——表示打上选定标志(对钩)。

       GRAYED——表示菜单项是灰化的。

       INACTIVE——表示菜单是禁用的。

       MENUBREAKMENUBARBREAK——表示将这个菜单项和以后的菜单项列到新的列中。

 

用法2定义的是菜单项之间的分隔线,显然,分隔线是不需要字符串和选项的。

 

用法3定义的是弹出式菜单,顶层菜单是由多个弹出式子菜单组成的,所以在Menu.rc文件中,主菜单是由“文件”、“查看”、“帮助”3个顺序定义的弹出式菜单组成的,弹出式菜单的定义也可以嵌套,如“查看”菜单中的“工具栏”又是一个弹出式菜单,在嵌套的时候要注意像写C的源程序一样把beginend(或者{})正确地配对。popup菜单的选项列表可以是以下的值:

       GRAYED——表示菜单项是灰化的。

       INACTIVE——表示菜单项是禁用的。

       HELP——表示本项和以后的菜单项是右对齐的。

 

popup菜单项选中的时候会自动将弹出式菜单弹出来,不需要向程序发送消息,所以在定义的参数中不需要命令ID

 

有些选项是可以同时定义,如果要指定超过一个的选项,中间要用逗号隔开,但是也有些小小的限制:GRAYEDINACTIVE不能同时使用,MENUBREAKMENUBARBREAK也是不能同时使用的。

 

 

加速键的定义

和菜单的定义相比,加速键的定义要简单得多,具体的语法如下:

加速键ID    ACCELERATORS

BEGIN

              键名,命令ID [,类型] [,选项]

             

END

 

加速键ID同样可以是一个字符串或者是1~65535之间的数字,整个定义内容也是用beginend(或花括号)包含起来,中间是多个加速键的定义项目,每个键占据一行,各字段的含义如下所示。

键名——表示加速键对应的按钮,可以有3种方式定义:

       ^字母”:表示Ctrl键加上字母键。

       “字母”:表示字母,这时类型必须指明是VIRTKEY

       数值:表示ASCII码为该数值的字母,这时类型必须指明为ASCII

 

命令ID——按下加速键后,Windows向程序发送的命令ID。如果想把加速键和菜单项关联起来,这里就是要关联期间项的命令ID

类型——用来指定键的定义方式,可以是VIRTKEYASCII,分别用来表示“键名”字段定义的是虚拟键还是ASCII码。

选项——可以是AltControlShift中的单个或多个,如果指定多个,则中间用逗号隔开,表示加速键是按键加上这些控制键的组合键。这些选项只能在类型是VIRTKEY的情况下才能使用。

 

在键名的定义中,系统按键如F1F2BackSpaceEsc等都是用虚拟键的方法定义的,Resource.h中已经包括所有的预定义,它们是以VK_开头的一引起值,如VK_BACKVK_TABVK_RETURNVK_ESCAPEVK_DELETEVK_F1VK_F2等,读者可以查看Resource.h文件,下面是加速键定义的一些例子:

 

下面是加速键定义的一些例子:

“^C”,      ID                 ;Ctrl+C

“K”,        ID                 ;Shift+K

“k”,        ID,ALT          ;Alt+k

98,          ID,ASCII              ;b(字符bASCII码为98

66,          ID,ASCII              ;BShift b

“g”,        ID                 ;g

VK_F1,   ID,VIRTKEY ;F1

VK_F2,   ID,VIRTKEY, CONTROL     ;Ctrl + F1

VK_F3,   ID,VIRTKEY,ALT,SHIFT      ;Alt + Shift + F2

 

在一个资源脚本文件中,可以定义多个菜单和多个加速键表,当然也有其他各式各样的资源,有位图、图标与对话框等,这就涉及为这些资源取ID值的问题,取值的时候要掌握的原则是:

1)对于同类别的多个资源,资源ID必须为不同的值,如定义了两个菜单,那么它们的ID就必须用不同的数值表示,否则将无法分辨。

2)对于不同类别的资源,资源ID在数值上可以是相同的,如可以将菜单和加速键的ID都定义为1,同时也可以有ID1的位图或图标等,Windows并不会把它们搞混。

 

 

使用菜单和加速键

在完成资源文件所示的编写后,来看看如何在程序中使用菜单和加速键,这里先列出程序的功能说明,读者可以先尝试一下,以便在以下的程序分析中有所印象。程序功能如下:

·程序在用户选择了任何一个菜单项以后,会弹出一个对话框,将接收到的菜单命令ID显示出来。

·选择“大图标”、“小图标”、“列表”和“详细资料”菜单项后,选中的菜单项前面会出现一个圆点选中标记,4个菜单项的选择是互斥的。

·在窗口的客户区单击鼠标右键会弹出和“查看”菜单一致的弹出式菜单。

·在标题栏图标上单击鼠标左键,会弹出系统菜单,注意上面比默认的菜单多了两项:“帮助主题”和“关于本程序”。

 

接下来,将逐步分析这些功能是如何实现的。下面是Menu.asm源代码,代码是在以前的FirstWindow程序的基础上改写的,这是编写Win32汇编程序的一个常用方法——拷贝一个模板程序再进行修改会节省很多的时间。

 

                .386

                .model flat,stdcall

                option casemap:none

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

; Include 文件定义

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

include         windows.inc

include         user32.inc

includelib      user32.lib

include         kernel32.inc

includelib      kernel32.lib

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

; Equ 等值定义

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

ICO_MAIN        equ     1000h   ;图标

IDM_MAIN        equ     2000h   ;菜单

IDA_MAIN        equ     2000h   ;加速键

 

IDM_OPEN        equ     4101h

IDM_OPTION      equ     4102h

IDM_EXIT        equ     4103h

 

IDM_SETFONT     equ     4201h

IDM_SETCOLOR    equ     4202h

IDM_INACT       equ     4203h

IDM_GRAY        equ     4204h

IDM_BIG         equ     4205h

IDM_SMALL       equ     4206h

IDM_LIST        equ     4207h

IDM_DETAIL      equ     4208h

IDM_TOOLBAR     equ     4209h

IDM_TOOLBARTEXT equ     4210h

IDM_INPUTBAR    equ     4211h

IDM_STATUSBAR   equ     4212h

 

IDM_HELP        equ     4301h

IDM_ABOUT       equ     4302h

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

; 数据段

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

                .data?

hInstance       dd      ?

hWinMain        dd      ?

hMenu           dd      ?

hSubMenu        dd      ?

 

                .const

szClassName     db      'Menu Example',0

szCaptionMain   db      'Menu',0

szMenuHelp      db      '帮助主题(&H)',0

szMenuAbout     db      '关于本程序(&A)...',0

szCaption       db      '菜单选择',0

szFormat        db      '您选择了菜单命令:%08x',0

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

; 代码段

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

                .code

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

; 弹出窗口,显示菜单命令ID

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

_DisplayMenuItem    proc    _dwCommandID

                    local @szBuffer[256]:byte

                       

                pushad

                invoke wsprintf, addr @szBuffer, addr szFormat, _dwCommandID

                invoke MessageBox, hWinMain, addr @szBuffer, offset szCaption, MB_OK

                popad

                ret

_DisplayMenuItem    endp

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

; 退出

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

_Quit           proc

                invoke DestroyWindow, hWinMain

                invoke PostQuitMessage, NULL

                ret

_Quit           endp

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

; 窗口过程

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

_ProcWinMain    proc uses ebx edi esi, hWnd, uMsg, wParam, lParam

                local @stPos:POINT

                local @hSysMenu

 

                mov eax,uMsg

;*************************************************************************

                .if     eax == WM_CREATE

                        invoke     GetSubMenu, hMenu, 1

                        mov     hSubMenu, eax

;***************************************************************************

; 在系统菜单中添加菜单项

;***************************************************************************

                        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_COMMAND

                        invoke _DisplayMenuItem, wParam

                        mov     eax, wParam

                        movzx   eax, ax

                        .if     eax == IDM_EXIT

                                call    _Quit

                        .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

                        .elseif eax >= IDM_BIG && eax <= IDM_DETAIL

                                invoke CheckMenuRadioItem, hMenu, IDM_BIG, IDM_DETAIL, eax, MF_BYCOMMAND

                        .endif

;***************************************************************************

; 处理系统菜单消息

;***************************************************************************

                .elseif eax == WM_SYSCOMMAND

                        mov     eax, wParam

                        movzx   eax, ax

                        .if     eax == IDM_HELP || eax == IDM_ABOUT

                                invoke _DisplayMenuItem, wParam

                        .else

                                invoke DefWindowProc, hWnd, uMsg, wParam, lParam

                                ret

                        .endif

;***************************************************************************

; 单击鼠标右键时弹出一个POPUP菜单

;***************************************************************************

                .elseif eax == WM_RBUTTONDOWN

                        invoke GetCursorPos, addr @stPos

                        invoke TrackPopupMenu, hSubMenu, TPM_LEFTALIGN, @stPos.x, @stPos.y, NULL, hWnd, NULL

;***************************************************************************

                .elseif eax == WM_CLOSE

                        call    _Quit

;***************************************************************************

                .else

                        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                      ;把菜单句柄存储于hMenu变量

                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

                invoke LoadCursor, 0, IDC_ARROW

                mov     @stWndClass.hCursor, 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、加载菜单

在窗口中加载菜单的方法有两个:一是在注册窗口类的时候指定类的默认菜单;二是在建立窗口的时候在参数中指定菜单句柄。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_KEYDOWNWM_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_KEYDOWNWM_SYSKEYDOWN消息则检测加速键资源,看按键是否符合某个加速键,符合的话则向目标窗口发送WM_COMMANDWM_SYSCOMMAND消息,并返回TRUE,不符合的话不进行任何处理并返回FALSE

 

由于加速键的键码并不是用户真正想输入窗口的,比如用户在写字板中输入文字,按Ctrl+C键是为了“拷贝”,而不是想把Ctrl+C键对应的字符输入文档,所以这个Ctrl+C的键码在完成加速键的使命后就应该丢弃,也就是说符合加速键的键盘消息不应该再发送给窗口,TranslateMessageDispatchMessage函数前的逻辑判断就是这样的意图:只有TranslateAccelerator没有转换的消息(返回值eax0)才继续处理。

 

另外,TranslateAccelerator的参数中有个“目标窗口”,例子中是程序的主窗口hWinMain,为什么要设置这样一个参数而不像DispatchMessage函数一样使用MSG结构中的hwnd呢?这是因为键盘消息可以产生于不同窗口中——既可能是主窗口,也可能是其他子窗口,如果用@stMsg.hwnd做目标窗口,就必须在所有子窗口的窗口过程中都设置处理加速键消息的代码,这显然是一种浪费,所以一般把所有的加速键消息都发送到主窗口,然后集中在主窗口的窗口过程中处理WM_COMMAND消息,这样有利精简代码。

 

3、菜单和加速键消息

当用户选择了一个菜单项的时候,Windows向菜单所属的窗口发送WM_COMMAND消息;而用户按下了一个加速键的时候,WindowsTranslateAccelerator函数指定的目标窗口发送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值对比一下,在正常使用的程序中可以去掉这一句。

 

读者可以发现,资源文件中定义的“字体”菜单项的ID0x4201,当选中“字体”菜单项的时候,对话框中显示的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可以修改一个菜单项的文字,DeleteMenuRemoveMenu则可以删除一个菜单项。

 

这些函数中的参数都雷同的,hMenu参数指要操作的菜单句柄;uPosition用来定位要操作的菜单项,定位的方法有两种:用命令ID定义或用位置索引,用哪一种方法取决于后面的uFlags参数,当uFlagsMF_BYCOMMAND时,uPosition为菜单项的命令ID,而当uFlagsMF_BYPOSITION的时候,uPosition表示菜单项的位置索引,索引是从0开始的,也就是说第一个菜单项的索引值为0

 

AppendMenu函数总是在菜单的最后添加新的菜单项,所以不需要uPosition参数。

对于AppendMenuInsertMenu,会有一个新的菜单项产生,uIDNewItem就表示这个新菜单项的命令IDlpNewItem指向新菜单项的文字字符串,ModifyMenu函数可以修改一个菜单项的命令ID或文字字符串,所以也有uIDNewItemlpNewItem参数。而用来删除菜单项的DeleteMenuRemoveMenu显然用不着uIDNewItemlpNewItem参数。

 

uFlags参数除了指定MF_BYCOMMAND还是MF_BYPOSITION外,还可以组合指定菜单项的其他属性,如MF_CHECKED,MF_DISABLED, MF_ENABLED, MF_GRAYED, MF_MENUBARBREAK, MF_MENUBREAK, MF_SEPARATORMF_UNCHECKED等,从其字面上就可以看出这些属性的含义。

 

DeleteMenuRemoveMenu的不同之外在于对popup菜单项的处理。当它们用于popup属性的菜单项时,DeleteMenu不仅删除菜单项,而且将这个popup菜单项的所有子项目全部删除,这样,这个popup菜单就不能在别的地方继续使用;而RemoveMenu仅从菜单中移去这个popup菜单项,整个popup菜单在内存中还是存在的。以Menu.asm程序为例,扫鼠标右键弹出的菜单实际上是主菜单中的“查看”菜单项,假如用DeleteMenu删除主菜单中的“查看”项目,那么按右键也就弹不出菜单了,而用RemoveMenu删除主菜单中的“查看”项目,按鼠标右键依然可以弹出菜单。对于非popup属性的菜单项,DeleteMenuRemoveMenu的效果是同样的。

 

5、使用系统菜单

系统菜单指按下了标题栏图标后弹出的菜单,和窗口菜单不同,选中系统菜单的菜单项后,Windows向窗口发送的是WM_SYSCOMMAND消息而非WM_COMMAND消息。默认的系统菜单中已经有“还原”、“移动”、“大小”、“最大化”、“最小化”和“关闭”等菜单项,这些菜单项的命令ID已经预定义为SC_RESTORESC_MOVESC_SIZESC_MAXIMIZESC_MINIMIZESC_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

                                  

                     .else

                                   invoke     DefWindowProc, hWnd, uMsg, wParam, lParam

                                   ret

                     .endif

和处理WM_COMMAND消息不同的是,在WM_SYSCOMMAND消息中处理了自定义的菜单命令ID后,必须把其他命令ID交给DefWindowProc处理,并直接把返回值返回给Windows,不然的话会发现窗口不能移动,不能关闭,不能最小化……因为它相当于屏幕了所有SC_RESTORE, SC_MOVE, SC_SIZE, SC_MAXIMIZE, SC_MINIMIZESC_CLOSE等消息的处理。

 

6、右键弹出菜单

例子程序的一个功能是当用户在窗口客户区按下鼠标右键的时候弹出一个菜单,这个功能是用TrackPopupMenu函数实现的。TrackPopupMenu函数的用法:

       invoke     TrackPopupMenu, hMenu, uFlags, x, y, nReserved, hWnd, lpRect

这个函数本身很简单,执行后在参数指定的x,y位置弹出一个属于hWnd窗口(也就是说WM_COMMAND消息发到这个窗口)的菜单,菜单句柄是hMenu。函数中的坐标是以整个屏幕左上角为基准的,所以弹出菜单的位置不一定在窗口的客户区内,它可以是屏幕的任何一个地方。

 

uFlags参数指定一些和位置相关的选项,它可以是PM_CENTERALIGNTPM_LEFTALIGNTPM_RIGHTALIGN三者之一,表示(x, y)坐标是代表弹出菜单位置的中间、左上角还是右上角,一般的习惯是使用TPM_LEFTALIGN,这样菜单会在鼠标点击处的右边弹出。uFlags中同时还可以指定用鼠标左键还是右键选定菜单项,定义值可以是TPM_LEFTBUTTONTPM_RIGHTBUTTON,如果选择TPM_RIGHTBUTTON的话,对在菜单项上面按鼠标左键是没有反应的。

 

lpRect指向一个RECT结构,用来指定一个区域,当菜单弹出后,在这个区域外单击鼠标,菜单才会消失,如果这个参数指定为NULL的话,在菜单之外单击鼠标,菜单就会消失。

 

在使用TrackPopupMenu之前,有几个准备工作是要做的:为了在客户区中按下鼠标右键弹出菜单,我们当然要处理鼠标右键消息,也就是说在WM_RBUTTIONDOWN消息中调用TrackPopupMenu函数,一般的习惯是在鼠标按下的地方弹出菜单,所以还要首先获取鼠标光标的位置,然后在此位置弹出菜单。

 

要获取鼠标位置,可以用GetCursorPos函数:

       invoke GetCusorPos, 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输入坐标的方法是用xy两个参数,而不是一个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用来定位要检测的菜单项,当uFlagsMF_BYCOMMAND的时候,uId用菜单项的命令ID指定,当uFlagsMF_BYPOSITION的时候,uId的值是位置索引,函数执行后的返回值为-1时表示失败,否则会是MF_CHECKED, MF_DISABLED, MF_GRAYED, MF_HILITE, MF_MENUBARBREAK, MF_MENUBREAKMF_SEPARATOR的组合值,它们分别表示菜单项的状态是选中、禁止、灰化、高亮显示以及3种分隔线,读者可以用test指令测试相应的数据位来分辨菜单项处于哪种状态,一般的测试代码如下:

       invoke    GetMenuStage, hMenu, IDM_XXX, MF_BYCOMMAND

       .if           eax & MF_CHECKED

                     ;表示IDM_XXX菜单项现在是选中状态

       .endif

 

同样,读者也可以用eax & MF_DISABLEDeax & 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_ENABLEDMF_GRAYED,分别代表这3种状态。

 

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

 

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

 

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

 

在例子程序中,当选中IDM_TOOLBARIDM_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_BIGIDM_DETAIL之间的菜单项的时候,程序用CheckMenuRadioItem将原告IDM_BIGIDM_DETAIL范围内的互斥选定撤销并将当前选定的菜单项加圆点标记。

.elseif      eax >= IDM_BIG && eax <= IDM_DETAIL

              invoke     CheckMenuRadioItem, hMenu, IDM_BIG, IDM_DETAIL, \

                            eax, MF_BYCOMMAND

 

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

 

注意:读者可以注意到,在状态参数中指定MF_COMMANDMF_POSITION将决定位置参数用命令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也是可以查询的,方法是用GetMenuStringGetMenuItemID,读者可以参考命令手册。

 

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

       invoke     SetMenu, hWnd, hMenu

 

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

 

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

       invoke     DestroyMenu, hMenu

 

 

posted on 2010-08-31 09:43 luqingfei 阅读(3455) 评论(1)  编辑 收藏 引用 所属分类: Win32汇编程语言序设计

评论

# re: Win32汇编--使用资源--菜单和加速键 2011-03-29 09:38 coreBugZJ

初学的汇编(用NASM),仰慕!!  回复  更多评论   


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


导航

<2024年3月>
252627282912
3456789
10111213141516
17181920212223
24252627282930
31123456

统计

留言簿(6)

随笔分类(109)

随笔档案(105)

Blogers

Game

Life

NodeJs

Python

Useful Webs

大牛

搜索

积分与排名

最新评论

阅读排行榜

评论排行榜