本節課中,我們來學習下菜單。
譯者注:
菜單可以說是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消息,然後關閉消息循環,結束程序。。