增加LoadMenuIndirectEx函数来装载MENUEX_TEMPLATE_ITEM菜单模板
使用LoadMenuIndirect的API函数可以装载MENUEX_TEMPLATE_HEADER扩展菜单模板,但不能使用字符串资源,所以用途不大。如果自己编写一个装载函数LoadMenuIndirectEx,就可以使菜单模板用于多语言编程中,可以节省很多的工程量。
原理如下:
扩展菜单模板中,其菜单项使用MENUEX_TEMPLATE_ITEM定义,该结构szText成员是菜单项的Unicode串;wFlags成员用来指示菜单项的属性,它有2个标志值,MFR_END指示这是该菜单条的最后一个菜单项,MFR_POPUP指示这是一个子菜单项。我给wFlags成员增加一个新的标志值MFR_TXTID,来指示szText成员是字符串资源ID。同时,不管wFlags成员是否有MFR_TXTID标志位,如果szText成员为空串,则均将菜单ID作为字符串资源ID来使用,所以如果菜单ID就是字符串资源ID,你可以不使用MFR_TXTID标志。
编写一个装载函数LoadMenuIndirectEx,并增加装载快捷菜单的功能,因为LoadMenuIndirect函数不能装载为快捷菜单。
编写LoadMenuIndirectEx函数的主要目的,是为了实现菜单的本地化。通常情况下,我们会使用菜单资源本地化的方法来实现菜单的本地化,这样就需要编辑多个菜单资源。如果使用菜单模板,则只需一个菜单模板就可以实现菜单本地化,当然还需要字符串表本地化,也就是说,只要本地化字符串表,就可以本地化一个程序中与字符串相关的所有项目。
注: 有关字符串表本地化,请参阅本站《字符串表资源(STRINGTABLE)和程序本地化》。有关菜单模板请查阅Windows API文档。
示例模板和完整代码如下:
1. 资源文件rsrc.rc的内容示例
以下资源文件的内容为定义字符串表,包含菜单中使用的字符串,也可以根据需要增加控件字符串等。
#include "\masm32\include64\resource.h"
//=======================================
//菜单标题ID,也可作为菜单ID
//=======================================
//----------
//主菜单
//----------
#define IDS_FILE 1600 //文件
#define IDS_NEW 1601 //新建
#define IDS_CLOSE 1611 //关闭
#define IDS_ABOUT 1612 //关于
//---------
//编辑器
//---------
#define IDS_EDIT 1616 //编辑
#define IDS_UNDO 1619 //重做
#define IDS_COPY 1621 //复制
#define IDS_CUT 1622 //剪切
#define IDS_PASTE 1623 //粘贴
#define IDS_DEL 1624 //删除
#define IDS_SELECTALL 1625 //全选
//=========================
// 字符串表定义
//=========================
STRINGTABLE
BEGIN
//主菜单
IDS_FILE "文 件"
IDS_NEW "新 建"
IDS_CLOSE "关 闭"
IDS_ABOUT "关 于"
//编辑器菜单
IDS_EDIT "编 辑"
IDS_UNDO "重 做"
IDS_COPY "复 制"
IDS_CUT "剪 切"
IDS_PASTE "粘 贴"
IDS_DEL "删 除"
IDS_SELECTALL "全 选"
END
2. 源代码
源代码共3个函数,功能如下:
Menu_GetTempItemInfoEx: 解释一个MENUEX_TEMPLATE_ITEM结构,并填写MENUITEMINFO结构。
Menu_LoadTemplateItemEx: 将模板中的全部菜单项添加到菜单句柄中。该函数是一个递归函数。
LoadMenuIndirectEx: 这是菜单装载的主函数。
;----------
;主菜单
;----------
IDS_FILE EQU 1600 ;文件
IDS_NEW EQU 1601 ;新建
IDS_CLOSE EQU 1611 ;关闭
IDS_ABOUT EQU 1612 ;关于
;---------
;编辑菜单
;---------
IDS_EDIT EQU 1616 ;编辑
IDS_UNDO EQU 1619 ;重做
IDS_COPY EQU 1621 ;复制
IDS_CUT EQU 1622 ;剪切
IDS_PASTE EQU 1623 ;粘贴
IDS_DEL EQU 1624 ;删除
IDS_SELECTALL EQU 1625 ;全选
;========================================================
;MENUEX_TEMPLATE_ITEM中的wFlags标志
;增加一个MFR_TXTID,作用如下:
;(1)如果wFlags标志无MFR_TXTID,则szText成员为字符串。
; 此时如果szText值为零,则将菜单ID作为字符串资源ID。
;(2)如果wFlags标志有MFR_TXTID,则szText成员为字符串资源ID。
; 此时如果szText值为零,则将菜单ID作为字符串资源ID。
;========================================================
MFR_END EQU 80h ;一个菜单条中的最后一个菜单项。
MFR_POPUP EQU 01h ;子菜单标题项。
MFR_TXTID EQU 100h ;szText成员指示标志
.data
;-----------------
; 扩展菜单模板
;-----------------
align 4
;---MENUEX_TEMPLATE_HEADER结构---
Menu_Temp1 \
dw 1 ;扩展菜单模板版本号。此成员必须为 1。
dw 4 ;首个 MENUEX_TEMPLATE_ITEM 结构的偏移
dd 100 ;菜单栏的帮助标识符(还不知道有什么用处,随便给一个值)。
;---以下紧跟菜单项(每项一个MENUEX_TEMPLATE_ITEM结构)---
dd MFT_STRING ;dwType
dd MFS_ENABLED ;dwState
dd IDS_FILE ;uId
dw MFR_POPUP or MFR_TXTID ;wFlags
dw 0 ;szText
dd 0 ;HELPID (只有MFR_POPUP类有该项)
;--------------
align 4
dd MFT_STRING
dd MFS_ENABLED
dd IDS_NEW
dw MFR_TXTID
dw IDS_NEW ;使用字符串资源ID
;--------------
align 4
dd MFT_STRING
dd MFS_ENABLED
dd IDS_CLOSE
dw MFR_END or MFR_TXTID ;该子菜单条的最后一项
dw 0 ; 默认使用菜单ID作为字符串资源ID
;--------------
align 4
dd MFT_STRING
dd MFS_ENABLED
dd IDS_EDIT
dw MFR_POPUP or MFR_TXTID
dw 0
dd 0 ;HELPID (只有MFR_POPUP类有该项)
;-----------------
align 4
dd MFT_STRING
dd MFS_ENABLED
dd IDS_UNDO
dw MFR_TXTID
dw 0
;-----------------
align 4
dd MFT_STRING
dd MFS_GRAYED
dd IDS_COPY
dw 0 ;不设置MFR_TXTID
dw 0 ;szText为空,自动以菜单项ID作为字符串资源ID
;-----------------
align 4
dd MFT_STRING
dd MFS_ENABLED
dd IDS_CUT
dw MFR_TXTID
dw 0
;-----------------
align 4
dd MFT_STRING
dd MFS_ENABLED
dd IDS_PASTE
dw MFR_TXTID
dw 0
;-----------------
align 4
dd MFT_STRING
dd MFS_ENABLED
dd IDS_DEL
dw MFR_TXTID
dw 0
;-----------------
align 4
dd MFT_SEPARATOR ;分隔线
dd 0
dd 0
dw 0
dw 0
;-----------------
align 4
dd MFT_STRING
dd MFS_ENABLED
dd IDS_SELECTALL
dw MFR_END or MFR_TXTID
dw 0
;-----------------
align 4
dd MFT_STRING
dd MFS_ENABLED
dd IDS_ABOUT
dw MFR_END
WSTR ,"About" ;这里使用Unicode字符串
;==========================
.code
;====================================================
;获取一个菜单项模板数据---含有自定义标志的扩展模板
;入: pTemp=当前菜单项模板MENUEX_TEMPLATE_ITEM结构地址
; pInfo=MENUITEMINFO结构地址。用于接收信息
; pFlag=DWORD变量地址。用于接收wFlags成员
; pHelpId=DWORD变量地址。用于接收dwHelpId成员
; =0: 不用
;出: RAX=被处理的结构的字节长度
; .hSubMenu=子菜单句柄(如果为MFR_POPUP类型)
;====================================================
Menu_GetTempItemInfoEx proc pTemp:QWORD,pInfo:QWORD,\
pFlag:QWORD,pHelpId:QWORD
LOCAL ss_rbx:QWORD
LOCAL ss_rsi:QWORD
LOCAL ss_flag:QWORD
mov ss_rbx,rbx
mov ss_rsi,rsi
mov rsi,rcx ;pTemp
mov rbx,rdx ;pInfo
mov [rbx.MENUITEMINFO].cbSize,SIZEOF MENUITEMINFO
mov [rbx.MENUITEMINFO].fMask,MIIM_STATE
lodsd ;dwType
mov [rbx.MENUITEMINFO].fType,eax
lodsd ;dwState
mov [rbx.MENUITEMINFO].fState,eax
lodsd ;uId
mov [rbx.MENUITEMINFO].wID,eax
xor rax,rax
lodsw ; wFlags
mov ss_flag,rax
mov [r8],eax
test ax,MFR_POPUP
jnz ss_sub
test [rbx.MENUITEMINFO].fType,MFT_SEPARATOR
jnz ss_sp
or [rbx.MENUITEMINFO].fMask,MIIM_ID
jmp ss_str
;---分隔条---
ss_sp:
add rsi,2 ;移过空串
mov [rbx.MENUITEMINFO].fMask,MIIM_TYPE
mov [rbx.MENUITEMINFO].fType,MFT_SEPARATOR
mov [rbx.MENUITEMINFO].dwTypeData,0
jmp ss_ok
;---子菜单---
ss_sub:
or [rbx.MENUITEMINFO].fMask,MIIM_SUBMENU
call CreatePopupMenu
mov [rbx.MENUITEMINFO].hSubMenu,rax
;---菜单项目串---
ss_str:
or [rbx.MENUITEMINFO].fMask,MIIM_STRING
mov [rbx.MENUITEMINFO].dwTypeData,rsi
mov rax,2
test ss_flag,MFR_TXTID
jnz ss_2
invoke lstrlenW,rsi
inc rax
shl rax,1
ss_2:
add rsi,rax
test ss_flag,MFR_POPUP
jz ss_ok
lodsd ;dwHelpId
mov rdx,pHelpId
test rdx,rdx
jz ss_ok
mov [rdx],eax
ss_ok:
mov rax,rsi
sub rax,pTemp
mov rbx,ss_rbx
mov rsi,ss_rsi
ret
Menu_GetTempItemInfoEx endp
;====================================================
;菜单模板项目装载---含有自定义标志的扩展模板
;入: uhMenu=菜单句柄
; item=新菜单项的插入位置,其意义由zFlag指定。
; zFlag=FALSE: item为插入处的菜单项目ID
; TURE: item为插入的菜单项位置(0...)
; pTemp=首个MENUEX_TEMPLATE_ITEM结构
; hInst=字符串资源所在的模块实例句柄
; =0: 为本进程模块
;出: RAX=指向下一个MENUEX_TEMPLATE_ITEM结构(递归使用)
;====================================================
Menu_LoadTemplateItemEx proc uhMenu:QWORD,item:DWORD,\
zFlag:DWORD,pTemp:QWORD,hInst:QWORD
LOCAL ss_mi:MENUITEMINFO
LOCAL ss_buf[MAX_PATH]:WORD
LOCAL ss_rsi:QWORD
LOCAL ss_Flag:DWORD
mov ss_rsi,rsi ;保护
mov rsi,pTemp
cmp hInst,0
jnz ss_lp1
invoke GetModuleHandle,NULL
mov hInst,rax
ss_lp1:
;---4字节对齐---
add rsi,3
and rsi,NOT 3
;---取MENUEX_TEMPLATE_ITEM结构数据---
invoke Menu_GetTempItemInfoEx,rsi,ADDR ss_mi,ADDR ss_Flag,0
add rsi,rax
;---处理字符串---
test ss_mi.fMask,MIIM_STRING
jz ss_add
mov edx,ss_mi.wID
mov rax,ss_mi.dwTypeData
mov ax,[rax]
test ax,ax
jz ss_ldstr ;文本项为空,菜单ID作为字符串资源ID
mov dx,ax
test ss_Flag,MFR_TXTID
jz ss_add
ss_ldstr:
lea r8,ss_buf
mov ss_mi.dwTypeData,r8
invoke LoadStringW,hInst,edx,r8,MAX_PATH
ss_add:
;---添加项目---
invoke InsertMenuItemW,uhMenu,item,zFlag,ADDR ss_mi
test eax,eax
jz ss_out
cmp zFlag,0
jz ss_5
inc item
ss_5:
test ss_mi.fMask,MIIM_SUBMENU
jz ss_nt
invoke Menu_LoadTemplateItemEx,ss_mi.hSubMenu,0,TRUE,rsi,hInst ;递归
mov rsi,rax
ss_nt:
test ss_Flag,MFR_END
jz ss_lp1
ss_out:
mov rax,rsi ;递归使用
mov rsi,ss_rsi
ret
Menu_LoadTemplateItemEx endp
;================================================
;菜单模板装载---含有自定义标志的扩展模板
;入: pTemp=指向MENUEX_TEMPLATE_HEADER结构
; 即菜单扩展模板。
; zFlag=标志:
; MF_POPUP: 装入为快捷菜单
; hInst=字符串资源所在的模块实例句柄
; =0: 为本进程模块
;出: RAX=菜单句柄
;================================================
LoadMenuIndirectEx proc pTemp:QWORD,zFlag:DWORD,\
hInst:QWORD
LOCAL ss_hMenu:QWORD
cmp hInst,0
jnz ss_be
invoke GetModuleHandle,NULL
mov hInst,rax
ss_be:
test zFlag,MF_POPUP
jnz ss_popup
invoke CreateMenu
jmp ss_2
ss_popup:
invoke CreatePopupMenu
ss_2:
mov ss_hMenu,rax
xor rax,rax
mov r10,pTemp
add r10,2
mov ax,[r10]
add r10,2
add r10,rax ;指向首个MENUEX_TEMPLATE_ITEM结构
invoke Menu_LoadTemplateItemEx,ss_hMenu,0,TRUE,r10,hInst
mov rax,ss_hMenu
ret
LoadMenuIndirectEx endp
3. 调用例
用以下方法装载菜单模板,并关联到窗口hWin:
invoke LoadMenuIndirectEx,ADDR Menu_Temp1,0,0
invoke SetMenu,hWin,rax