本节课中,我们来学习下菜单。
译者注:
菜单可以说是Windows下最重要的元素之一。windows用它来提供大部分的命令操作。我之前说过,我们在Windows下编程,就要遵循Windows下相应的规则,那么菜单也是一样的。 大家可以看下Windows本身自带的一些应用程序,它们的菜单的基本也是相同的。都是位于标题栏的下方。当然这是我们通过它的外形的综述,那么内在的。我们windows提供的菜单,必须是资源的形式。所以我们必须遵守这样的规则。
有了它,用户可以方便地选择操作命令。用户只要细读一下所有的菜单项就可以明了程序所提供的大概功能,而且可以立即操作,无须去阅读手册。正因为菜单给了用户一种方便的方式,所以您在应用程序中加入菜单时要遵守一定的规则。譬如l一般头两个菜单项是“File”和“Edit”,最后是“Help”,您可以在这中间插入您要定义的菜单项。如果所运行的菜单命令会弹出一个对话框,我们一般在其菜单项后加入(......),菜单是一种资源,除资源外还有其他的像对话框,字符串,图标,位图资源等都是资源。在链接程序的时候将资源加入到可执行文件中,最后我们的执行程序既包括机器指令又包括了资源。您可以在任何文本编辑器中编写脚本文件,在文件中您可以以指令资源呈现出来的外观和其他的一些属性。当然更直观的方法是用资源编辑器,通常资源编辑器都打包在编译环境中,像Visual C++ ,Borland C++ 等。
我们可以按以下方式来定义一个菜单资源。
菜单ID MENU [DISCARDABLE]
BEGIN
[Menu List here]
END
当然BEGIN END还可以替换成{}C语言的中的风格。我们一般是定义一个顶层菜单,然后在顶层菜单里定义子菜单和一些弹出式菜单等。
好我们按照如上的方式来定义一个菜单。
MyMenu MENU
{
[Menu List here]
}
这和C语言中的结构体定义非常相似。MyMenu类似被定义的变量,而MENU类似于关键字。当然您也可以用另外一种办法,那就是用BEGIN和END来代替花括号,这和Pascal语言风格相近。
在顶层菜单项中是一大串MENUITEM和POPUP语句。MENUITEM定义了一个菜单项,当选择后不会激活对话框。它的语法如下:
MENUITEM “&text”, ID [,option]
它由关键字MENUITEM开头,紧跟在MENUITEM后面的是菜单项的名称字符串,符号“&”后的第一个字符将会带下划线,它是由该单项的快捷键。ID的作用是当该菜单被选中的时,Windows的消息过程处理函数用来区分菜单项的。毫无疑问, ID必须是唯一的。option有以下选项。
1.GRAYED 代表该菜单项处于非激活状态,即当选被选中时不会产生WM_COMMAND消息。该菜单项以灰色显示。
2.INACTIVE 代表该菜单项处于非激活状态,即当被选中时不会产生WM_COMMAND消息。该菜单项以正常颜色显示。
3.MENUBREAK 该菜单项和随后的菜单项会显示在新列中。(译者著:比较难描述,请做实验)
4.help 该菜单项和随后的菜单项右对齐。
你可以单独使用以上标志也可以把这些标志痛过 or 连接起来。当然INACTIVE 和 GRAYED是不能痛过or连接的。 POPUP的语法如下:
POPUP “&text” [,option]
{
[MENU LIST]
}
POPUP定义了一个菜单项当该菜单项被选中的时候,会弹出一个子菜单。另外有一种特别类型MENUITE语句,MENUITEM SEPARATOR,它表示在菜单项位置画一条分割线。定义完菜单后,您就可以在程序中使用这些菜单资源了。您可以在程序的两个地方使用它们。
1.在WNDCLASSEXE结构体的lpszMenuName成员。譬如你有一个菜单“First Window”,你可以用如下方法将它联系到您的创口。
szMenuName db 'firs Window', 0
mov [@wc.lpszMenuName], szMenuName
2.在CreateWindowEx函数中指明菜单的句柄:
szMenuName db ‘first Window’,0
hMenu dd ?
invoke LoadMenu, [hInstnace], szMenuName
mov [hMenu], eax
invoke CreateWindowEx,NULL,OFFSET ClsName,/
OFFSET Caption, WS_OVERLAPPEDWINDOW,/
CW_USEDEFAULT,CW_USEDEFAULT,/
CW_USEDEFAULT,CW_USEDEFAULT,/
NULL,/
hMenu,/
hInst,/
NULL
你也许会问这有什么不同?
当您使用第一种方法的时刻,由于是在窗口类中指定。故所有由该窗口类派生下来的窗口都将有相同的菜单。如果您想要从相同的类中派生的窗口有不同的菜单,则必须用第二种方法,该方法通过CreateWindowEx指定的菜单会覆盖WNDCLASSEX结构中指定的菜单。接下来我们看看当用户选择一个菜单项时,它是如何来通知windows窗口过程函数的,当用户选择一个窗口菜单项时,Windows窗口过程函数会接受到一个WM_COMMAND消息。传进来的参数wparam参数包含了菜单项的ID。 好了上面就是菜单的一切,那么下面我们来实践。
format PE GUI 4.0
include 'win32ax.inc'
macro memmov [dst, src]
{
common
push [src]
pop [dst]
}
IDM_TEST equ 1
IDM_HELLO equ 2
IDM_GOODBYE equ 3
IDM_EXIT equ 4
.text
;**************数据********************
szClassName db 'first Window',0
szWndName db 'My first program',0
szMenuName db 'MyMenu',0
szTestString db 'you selected test menu item', 0
HelloString db 'you selected hello menu item', 0
goodstring db 'you selected goodbyte menu item',0
hInstanse rd 1
hIcon rd 1
hCursor rd 1
hWndow rd 1
lpCommand rd 1
entry $
xor edi, edi
invoke GetModuleHandle, edi
or eax, eax
jz .fail
mov [hInstanse], eax
invoke GetCommandLine,edi
mov [lpCommand], eax
stdcall _WndMain, hInstanse, edi, [lpCommand],SW_SHOWDEFAULT
.fail:
invoke ExitProcess,NULL
proc _WndMain hInstance:DWORD, hPrevInstance:DWORD, lpCmdLine:DWORD, nCmdShow:DWORD
local @wc:WNDCLASSEX
local @Msg:MSG
local @hMenu:DWORD
invoke RtlZeroMemory, addr @wc, sizeof.WNDCLASSEX
invoke LoadIcon, NULL,IDI_ASTERISK
mov [hIcon], eax
invoke LoadCursor, NULL,IDC_ARROW
mov [hCursor], eax
mov [@wc.cbSize], sizeof.WNDCLASSEX
mov [@wc.style], CS_HREDRAW or CS_VREDRAW
mov [@wc.lpfnWndProc], _WndProc
mov [@wc.cbClsExtra], NULL
mov [@wc.cbWndExtra], NULL
memmov @wc.hInstance, hInstance
memmov @wc.hIcon, hIcon
memmov @wc.hCursor, hCursor
mov [@wc.hCursor], hCursor
mov [@wc.hbrBackground], COLOR_WINDOW + 1
mov [@wc.lpszMenuName], NULL
mov [@wc.lpszClassName], szClassName
invoke RegisterClassEx, addr @wc
invoke LoadMenu,[hInstanse], szMenuName
mov [@hMenu], eax
invoke CreateWindowEx, NULL, szClassName, szWndName, WS_OVERLAPPEDWINDOW,/
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,/
NULL, [@hMenu] , [hInstanse] , NULL
mov [hWndow], eax
invoke ShowWindow,[hWndow],SW_SHOWNORMAL
invoke UpdateWindow,[hWndow]
.GetMsg:
invoke GetMessage,addr @Msg,NULL, 0, 0
or eax, eax
je .QUIT
invoke TranslateMessage,addr @Msg
invoke DispatchMessage,addr @Msg
jmp .GetMsg
.QUIT:
mov eax, [@Msg.wParam]
ret
endp
proc _WndProc uses ebx esi edi, hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
cmp [uMsg], WM_DESTROY
je finish
cmp [uMsg], WM_COMMAND
je .command
invoke DefWindowProc,[hWnd],[uMsg],[wParam], [lParam]
ret
.command:
mov eax ,[wParam]
and eax, 0FFFFh
cmp eax, IDM_TEST
je _test
cmp eax, IDM_HELLO
je _hello
cmp eax, IDM_GOODBYE
je _good
cmp eax, IDM_EXIT
je _exit
jmp Myend
_test:
invoke MessageBox,NULL, szTestString, '提示', MB_OK
jmp Myend
_hello:
invoke MessageBox,NULL, HelloString, '提示', MB_OK
jmp Myend
_good:
invoke MessageBox,NULL, goodstring, '提示', MB_OK
jmp Myend
_exit:
invoke DestroyWindow, [hWnd]
jmp Myend
finish:
invoke PostQuitMessage, NULL
Myend:
xor eax, eax
ret
endp
.import
library kernel32, 'kernel32.dll',/
user32, 'user32.dll'
include 'api/kernel32.inc'
include 'api/user32.inc'
section '.rsrc' data readable resource from 'resoursename.RES'
;*********************************************************Menu.RC**********************************************
#define IDM_TEST 1
#define IDM_HELLO 2
#define IDM_GOODBYE 3
#define IDM_EXIT 4
MyMenu MENU
{
POPUP "popup&"
{
MENUITEM "&say hello", IDM_HELLO, GRAYED
MENUITEM "&say Good Byte", IDM_GOODBYE
MENUITEM SEPARATOR
MENUITEM "&say Exit", IDM_EXIT
}
MENUITEM "&test", IDM_TEST
}
分析:
首先我们来分析资源脚本文件。我们可以看到
#define IDM_TEST 1
#define IDM_HELLO 2
#define IDM_GOODBYE 3
#define IDM_EXIT 4
这几行定义资源ID号。 由于我们资源文件用的是vc的资源编译器,所以我们定义的时候要用C语言的语法。
我们只要知道这些资源ID号必须是唯一的,因为我们必须通过它在窗口过程函数中来区分各个菜单项。
POPUP "popup&"
{
MENUITEM "&say hello", IDM_HELLO, GRAYED
MENUITEM "&say Good Byte", IDM_GOODBYE
MENUITEM SEPARATOR
MENUITEM "&say Exit", IDM_EXIT
}
定义了一个有4个菜单项的子菜单。这个菜单有4个子菜单。其中第三个菜单项是一个分割线。
MENUITEM "&test", IDM_TEST 定义主菜单的一项。
我们现在来看源代码:
szMenuName db 'MyMenu',0
szTestString db 'you selected test menu item', 0
HelloString db 'you selected hello menu item', 0
goodstring db 'you selected goodbyte menu item',0
exitstring db 'you selected exit menu item',0
szMenuName是资源文件中指定菜单的名字。因为你可以在脚本文件中定义多个菜单,所以在使用前你必须指定要使用哪一个。接下来的行是在选中时弹出消息框的内容。
IDM_TEST equ 1
IDM_HELLO equ 2
IDM_GOODBYE equ 3
IDM_EXIT equ 4
定义的菜单ID号,要和资源脚本文件中一样。
cmp [uMsg], WM_COMMAND
je .command
.command:
mov eax ,[wParam]
and eax, 0FFFFh
cmp eax, IDM_TEST
je _test
cmp eax, IDM_HELLO
je _hello
cmp eax, IDM_GOODBYE
je _good
cmp eax, IDM_EXIT
je _exit
jmp Myend
在本窗口过程中我们处理的 WM_COMMAND消息。当用户选择一个菜单项时,该菜单项的菜单ID放入参数wparam中被同时送到windows窗口过程处理函数。我们把它保存到eax寄存器中以便比较。由于wparam的高2个字节是通知码,低2个字节是菜单ID,所以我们通过and将高两个字节设置为0,从而进行判断。前三种情况下我们选择Test , HELLO, GOODBYTE菜单项的时候, 会弹出一个消息框显示相关字符串。但是当选择Exit菜单项的时候我们发送DestroyWindow函数来销毁我们的窗口。从而发送WM_DESTROY消息,然后调用PostQuitMessage消息,从而发送WM_QUIT消息,然后关闭消息循环,结束程序。。