Fasm--Win32彙編學習12
現在我們開始學習一些有關GUI編程的有趣的部分, 即:以對話框爲主要界面的應用程序。我們將分兩課來講述這一過程
理論:
如果您仔細關注過前一個程序就會發現:您無法按TAB鍵從一個子窗口控件跳到另一個子窗口控件,要想轉移的話只有用鼠標一下一下的去單擊。對用戶來說是不友好的。另一件事如果您象前一節課中那樣把主窗口的北京從白色改爲灰色,爲了子窗口控件無縫地作相應的改變,您必須細分類所有子窗口。造成上述諸多不變的原因是子窗口控件本來是爲對話框二設計的,象子窗口控件的背景色是灰色的,而對話框的背景色也是灰色的,這樣它們本來就相互協調了,二無需程序員加入其他的處理,在我們深入討論對話框前,我們必須知道why是對話框。一個對話框其實本來是有很多的子窗口控件的一個窗口,windows在對話框內部有一個管理程序,由其來處理象按下TAB鍵則輸入焦點從一個子窗口控件跳到另一個子窗口控件、按下ENTER鍵等於在當前具有輸入焦點的子窗口空間商點擊了鼠標等等這些雜事,這樣程序員就可以集中精力於他們的邏輯事務了。對話框主要左輸入輸出的接口,人們無需知道它內部的工作原理,而只要知道如何和他們進行交互就可以了。這也是面向對象設計中的所謂信息隱藏。只要這個黑盒子中的實現足夠完美,我們就可以放心的使用。當然,我們必須強調的是這個“黑盒子”必須是完美的。WIN32 API內部就是一個“黑盒子”,噢,發現好像跑題了,那麼我們回到正題。對話框的設計是爲了減少程序員的工作量的,一般如果您在你的窗口上放一個子窗口控件,您必須處理它的按鍵邏輯以及分類它的窗口過程。如果您把它放在對話框中,則這些雜事對話框會自己處理,您只要知道如何獲得用戶輸入的數據和如何把用戶輸入的數據放入到子窗口控件中就可以了。在程序中對話框和菜單同樣是被定義成一種資源,您可以在腳本文件中寫一個對話框模板,其中包含該對話框以及子窗口控件的特性,然後利用資源編輯器編輯。需要注意的是所有的資源必須放在同一個腳本文件中。雖然用文本編輯器可以編輯資源腳本,但是象調整子窗口控件位置時要牽扯到一些座標值所以最好還是使用可視化的資源編輯器進行編輯,這樣方便多了。一般編譯器的開發包中都帶有資源編輯器,您可以用它們來產生一個模板,然後自己增刪一些子窗口控件。
對話框有兩種,模式對話框和無模式對話框。無模式對話框允許你把輸入焦點切換到同一個程序的另一個窗口,而該對話框無須改變。比如MS WORD的FIND對話框。模式對話框又有兩類:應用程序對話框和系統對話框。應用程序對話框不允許你在本應用程序中切換輸入焦點,但可以切換到其他的程序中,而系統對話框則必須你對對話框做出響應否則不能切換到任何的應用程序中去。要創建一個無模式對話框掉要調用API函數CreateDialogParam,而創建一個模式對話框調用API DialogBoxParam。其中應用程序對話框和系統對話框的區別是style參數不同,要想創建一個系統模式對話框參數必須或上DS_SYSMODAL標誌,在對話框中若要和子窗口通訊則調用函數SendDlgItemMessage。該函數的語法:
SendDlgItemMessage proto hwndDlg:DWORD,/
idControl:DWORD,/
uMsg:DWORD,/
wParam:DWORD,/
lParam:DWORD
該API對於向子窗口控件發送方面非常有用的。譬如你想獲得一個子窗口控件中的字符串你可以這樣做。
call SendDlgItemMessage, [hDlg], IDC_EDITBOX, WM_GETTEXT, 256, szBuffer
具體要發送那些消息可以應該查詢有關的WIN32 API手冊。Windows還提供了幾個快速存取控件數據的api函數。 譬如GetDlgItemText,CheckDlgButton等。這樣一來,您就不需要去查每個消息的wparam和lparam參數獲得相關訊息了。您應儘可能的使用這些API函數,這將使您的代碼比較好維護。對話框管理器會把一些消息發送給一個特定的回調函數,對話框處理函數格式爲:
DlgProc proto hDlg:DWORD ,/
iMsg:DWORD ,/
wParam:DWORD ,/
lParam:DWORD
該函數的格式非常類似窗口過程函數,除了返回值是true和false,而不是LRESULT,存在於windows內部的對話框管理器纔是真正的窗口過程函數。對話框管理會首先調用我們自定義的過程,所以當我們的窗口過程函數處理這些消息時返回true,這表示我們已經處理了某條信息,返回false表示沒有處理。這也意味着我們的窗口過程函數在接受到自己不處理的消息時並不會調用DefWindowProc函數,因爲它本身不是一個真正的窗口過程函數。
對於對話框有兩種用法:
一種是把它作爲主窗口來用,一種是把它作爲輸入輸出設備來使用。
本課中我們將示範第一種方法,“把對話框用作主窗口”有兩種意思,
1.您可以通過RegisterClassEx函數把對話框模板註冊成一個窗口類,這樣對話框的行爲就成爲一個普通的窗口了,它通過在註冊時使用默認的窗口過程來處理所有的消息,通過這樣的方法的好處是你不需要顯示創建子窗口控件,windows對話框管理器會幫你創建,另外還會幫你創建按鍵邏輯,另外您還可以指定您窗口類中的光標和圖標。
2.您的應用程序創建沒有父窗口的對話框窗口,這種方法中,沒有必要需要一段處理消息循環的代碼,因爲所有的消息會windows對話管理器送到對話框過程函數中,這樣您也不用註冊窗口類。本課中我們將先使用第一種方法,然後使用第二種方法。
代碼:
format PE GUI 4.0
include 'win32ax.inc'
macro memmov [dst, src]
{
common
push [src]
pop [dst]
}
.data
;**************數據********************
szClassName db 'DLGCLASS',0
szWndName db 'My first program',0
szMenuName db 'MyMenu',0
szTestString db 'Wow! i am in an edit box now', 0
szDlgName db 'Mydialog', 0
szBuffer db 50 dup (?)
hInstanse rd 1
hIcon rd 1
hCursor rd 1
hDlg rd 1
lpCommand rd 1
hButton rd 1
hEdit rd 1
IDM_HELLO equ 32000
IDM_CLEAR equ 32001
IDM_EXIT equ 32003
IDC_EDIT equ 3000
IDC_BUTTON equ 3001
IDC_EXIT equ 3002
.code
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
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], DLGWINDOWEXTRA
memmov @wc.hInstance, hInstance
memmov @wc.hIcon, hIcon
memmov @wc.hCursor, hCursor
mov [@wc.hbrBackground], COLOR_WINDOW+1
mov [@wc.lpszMenuName], szMenuName
mov [@wc.lpszClassName], szClassName
invoke RegisterClassEx, addr @wc
invoke CreateDialogParam, [hInstanse], szDlgName, NULL, NULL, NULL
mov [hDlg], eax
invoke ShowWindow,[hDlg],SW_SHOWNORMAL
invoke UpdateWindow,[hDlg]
invoke GetDlgItem, [hDlg], IDC_EDIT
invoke SetFocus, eax
.GetMsg:
invoke GetMessage,addr @Msg,NULL, 0, 0
or eax, eax
je .QUIT
invoke IsDialogMessage, [hDlg], addr @Msg
or eax, eax
jnz .GetMsg
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]
cmp ax, IDM_HELLO
je _hello
cmp ax, IDM_CLEAR
je _clear
cmp ax, IDM_EXIT
je _exit
jmp @f
_hello:
invoke GetDlgItemText, [hWnd], IDC_EDIT, szBuffer, 50
invoke MessageBox, NULL, szBuffer, 'test', MB_OK
jmp Myend
_clear:
invoke SetDlgItemText, [hWnd], IDC_EDIT, NULL
jmp Myend
_exit:
invoke DestroyWindow, [hWnd]
jmp Myend
@@:
shr edx, 16
cmp dx,BN_CLICKED
jnz Myend
_SetText:
cmp ax, IDC_BUTTON
jnz _Btexit
invoke SetDlgItemText, [hWnd], IDC_EDIT, szTestString
jmp Myend
_Btexit:
cmp ax, IDC_EXIT
jnz Myend
invoke SendMessage, [hWnd], WM_COMMAND, IDM_EXIT, NULL
jmp Myend
faild:
invoke MessageBox,NULL, 'faild', 'test', MB_OK
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 'resoucename.res'
;/////////////////////////////////////////////////Rc//////////////////////
#include "resource.h"
#define IDC_EDIT 3000
#define IDC_BUTTON 3001
#define IDC_EXIT 3002
#define IDM_HELLO 32000
#define IDM_CLEAR 32001
#define IDM_EXIT 32003
Mydialog DIALOG 10, 10, 205, 60
STYLE 0x0004 | DS_CENTER | WS_CAPTION | WS_MINIMIZEBOX |
WS_SYSMENU | WS_VISIBLE | WS_OVERLAPPED | DS_MODALFRAME | DS_3DLOOK
CAPTION "Our First Dialog Box"
CLASS "DLGCLASS"
BEGIN
EDITTEXT IDC_EDIT, 15,17,111,13, ES_AUTOHSCROLL | ES_LEFT
DEFPUSHBUTTON "Say Hello", IDC_BUTTON, 141,10,52,13
PUSHBUTTON "E&xit", IDC_EXIT, 141,26,52,13, WS_GROUP
END
MyMenu MENU
BEGIN
POPUP "popup&"
BEGIN
MENUITEM "&say hello", IDM_HELLO
MENUITEM "&Clear Edit Box", IDM_CLEAR
MENUITEM SEPARATOR
MENUITEM "&Exit", IDM_EXIT
END
END
分析:
我們先來分析這個例子。
該例子顯示瞭如何把一個對話框模板註冊爲一個窗口類,然後創建一個由該窗口類派生的窗口。由於您沒必要自己去創建子窗口控件,所以就簡化了很多工作。
Mydialog DIALOG 10, 10, 205, 60
先是對話框的名字,然後是關鍵字“DIALOG” 。接下來的四個數字種,前兩個是對話框的座標,後兩個是對話框的高和寬(注意:它們是對話框的單位而不一定是像素點)。
STYLE 0x0004 | DS_CENTER | WS_CAPTION | WS_MINIMIZEBOX |
WS_SYSMENU | WS_VISIBLE | WS_OVERLAPPED | DS_MODALFRAME | DS_3DLOOK
這裏定義的是對話框的風格。
CAPTION "Our First Dialog Box"
這裏是定義的對話框標題條上的標題。
CLASS "DLGCLASS"
這句是定義的類名,很重要。正是有了關鍵字CLASS 我們才能用它來聲明把一個對話框當成一個窗口來用。跟在關鍵字後面的是“窗口類”的名稱。
BEGIN
EDITTEXT IDC_EDIT, 15,17,111,13, ES_AUTOHSCROLL | ES_LEFT
DEFPUSHBUTTON "Say Hello", IDC_BUTTON, 141,10,52,13
PUSHBUTTON "E&xit", IDC_EXIT, 141,26,52,13, WS_GROUP
END
上面這一塊定義的對話框子窗口控件,它們是聲明在一頭一位的兩個關鍵字BEGIN和END之間。
control-type "text" ,controlID, x, y, width, height [,styles]
控件的類型是資源編輯器定義好了的常數,您可以查找有關的手冊。 現在我們來看看彙編源代碼。先看這部分:
mov [@wc.cbWndExtra], DLGWINDOWEXTRA
mov [@wc.lpszClassName], szClassName
通常cbWndExtra成員設置爲NULL,但是我們想把一個對話框模板註冊成一個窗口類我們必須將成員設置爲DLGWINDOWEXTRA。這是MS的規定,你可以查下MSDN 上有對齊的詳細解釋。注意類名必須和資源腳本中CALLS關鍵字後面的類名保持一致。餘下的成員變量和聲明一般的窗口類保持相同。填寫好窗口類變量後調用RegisterClassEx函數進行註冊。看上起和註冊一個普通的窗口類沒什麼不同。
invoke CreateDialogParam, [hInstanse], szDlgName, NULL, NULL, NULL
註冊完畢後,我們調用CreateDialogParam函數來產生一個無模式的對話框。這個函數共有5個參數,其中前兩個參數是必須的:實例句柄和對話框模板名稱的指針。注意:第二個參數是指向模板名稱二不是類名稱。這時windows將產生對話框和子控件窗口。同時您的應用程序將接受到windows傳送的第一個消息WM_CREATE。
invoke GetDlgItem, [hDlg], IDC_EDIT
invoke SetFocus, eax
在對話框產生後,我們把輸入輸出焦點設置到我們的編輯框中。如果在WM_CREATE消息處理那段設置焦點的代碼GetDlgItem函數會返回失敗,因爲此時空間窗口還並沒有產生。爲了在對話框和所有的子窗口控件都產生後調用該函數我把它安排在了UpdataWindow函數後。GetDlgItem函數返回子窗口控件的句柄。
.GetMsg:
invoke GetMessage,addr @Msg,NULL, 0, 0
or eax, eax
je .QUIT
invoke IsDialogMessage, [hDlg], addr @Msg
or eax, eax
jnz .GetMsg
invoke TranslateMessage,addr @Msg
invoke DispatchMessage,addr @Msg
jmp .GetMsg
現在進入我們的消息循環,在我們翻譯和派發消息前,IsDialogMessage該函數使得對話框內置的對話框管理程序來處理按鍵邏輯。如果該函數返回true,則表示消息已經被處理過了(也就表示消息是傳遞給對話框管理器的),所以我們進行判斷如果等於true則直接跳到.GetMsg標號處來獲得消息,就省略了我們的翻譯和派發的過程。注意和前一課不同,當我們想得到控件的文本信息時調用GetDlgItemText函數而不是GetWindowText函數,前者接受的參數是一個控件的ID 號,而不是窗口的句柄,這使得在對話框中調用該函數更方便。
;//____________________________________________Dialog2____________________________________________________________________
好我們現在使用第二種方法把一個對話框當成一個主窗口來使用。在接下來的例子中,我們將產生一個應用程序的模式對話框,您將會發現其中根本沒有消息循環或窗口處理過程,因爲它們根本沒有必要!
format PE GUI 4.0
include 'win32ax.inc'
.data
;**************數據********************
szTestString db 'Wow! i am in an edit box now', 0
szDlgName db 'Mydialog', 0
szBuffer db 50 dup (?)
hInstanse rd 1
IDM_HELLO equ 32000
IDM_CLEAR equ 32001
IDM_EXIT equ 32003
IDC_EDIT equ 3000
IDC_BUTTON equ 3001
IDC_EXIT equ 3002
.code
entry $
xor edi, edi
invoke GetModuleHandle, edi
mov [hInstanse], eax
invoke DialogBoxParam, [hInstanse], szDlgName, edi, DlgProc, edi
invoke GetLastError
invoke ExitProcess,edi
proc DlgProc uses ebx esi edi, hwndDlg:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
cmp [uMsg], WM_INITDIALOG
je Initial
cmp [uMsg], WM_CLOSE
je Finish
cmp [uMsg], WM_COMMAND
je Command
RetFalse:
mov eax, FALSE
ret
Initial:
invoke GetDlgItem, IDC_EDIT
invoke SetFocus, eax
jmp RetTrue
Finish:
invoke SendMessage, [hwndDlg], WM_COMMAND, IDM_EXIT, NULL
jmp RetTrue
Command:
mov eax, [wParam]
cmp [lParam], 0
jne BoxCommand
MenuCommand:
cmp ax, IDM_HELLO
je _hello
cmp ax, IDM_CLEAR
je _Clear
cmp ax, IDM_EXIT
je _Exit
jmp RetTrue
BoxCommand:
mov edx, [wParam]
shr edx, 16
cmp dx, BN_CLICKED
jne RetTrue
cmp ax, IDC_BUTTON
je Button
cmp ax, IDC_EXIT
je _Exit
jmp RetTrue
Button:
invoke SetDlgItemText, [hwndDlg], IDC_EDIT, szTestString
jmp RetTrue
_hello:
invoke GetDlgItemText,[hwndDlg], IDC_EDIT, szBuffer, 50
invoke MessageBox, NULL, szBuffer, 'test', MB_OK
jmp RetTrue
_Clear:
invoke SetDlgItemText,[hwndDlg], IDC_EDIT, NULL
jmp RetTrue
_Exit:
invoke EndDialog,[hwndDlg], NULL
RetTrue:
mov eax, TRUE
ret
endp
.import
library kernel32, 'kernel32.dll',/
user32, 'user32.dll'
include 'api/kernel32.inc'
include 'api/user32.inc'
section '.rsrc' data readable resource from 'test31.res'
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>rc>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#define IDM_HELLO 32000
#define IDM_CLEAR 32001
#define IDM_EXIT 32003
Mydialog DIALOG 10, 10, 205, 60
STYLE 0x0004 | DS_CENTER | WS_CAPTION | WS_MINIMIZEBOX |
WS_SYSMENU | WS_VISIBLE | WS_OVERLAPPED | DS_MODALFRAME | DS_3DLOOK
CAPTION "Our First Dialog Box"
MENU MyMenu
BEGIN
EDITTEXT IDC_EDIT, 15,17,111,13, ES_AUTOHSCROLL | ES_LEFT
DEFPUSHBUTTON "Say Hello", IDC_BUTTON, 141,10,52,13
PUSHBUTTON "E&xit", IDC_EXIT, 141,26,52,13, WS_GROUP
END
MyMenu MENU
BEGIN
POPUP "popup&"
BEGIN
MENUITEM "&say hello", IDM_HELLO
MENUITEM "&Clear Edit Box", IDM_CLEAR
MENUITEM SEPARATOR
MENUITEM "&Exit", IDM_EXIT
END
END
分析:
invoke DialogBoxParam, [hInstanse], szDlgName, edi, DlgProc, edi
在上面我們已經說了創建模式對話框用的DialogBoxParam函數,該函數有五個參數,分別是:實例句柄、對話框模板的名字、父窗口的句柄、對話框過程函數的地址、和對話框相關的數據。該函數產生一個模式對話框。如果不顯示地關閉該函數不會返回。
cmp [uMsg], WM_INITDIALOG
je Initial
cmp [uMsg], WM_CLOSE
je Finish
除了不處理WM_CREATE消息外對話框的窗口過程函數和一般的窗口過程類似,該過程接受到的第一個消息是WM_INITDIALOG。通常把初始化的代碼放在這裏。請注意你處理該消息必須在eax中返回true 。內置的對話框函數不會把WM_DESTROY消息發送到對話框的消息處理過程中,所以我們如果想在對話框關閉時進行處理我們必須把它放在WM_CLOSE消息中處理。在我們的例子中發送WM_COMMAND消息並在wparam中放IDM_EXIT這和處理WM_CLOSE消息是一樣的。在處理IDM_EXIT中我們調用EndDialog函數。如果我們想要銷燬一個對話框必須調用EndDialog函數。該函數不會立即銷燬,而是設置一個標誌位,然後對話框管理器會接下來處理銷燬的動作。好,現在我們來看看資源文件,在調用DialogBoxParam產生的對話框中掛接一個菜單必須這麼做,注意在該對話框模板中,在該標識符前必須加MENU關鍵字,這兩個例子中的顯著不同是後者沒有圖標,這可以在處理WM_INITDIALOG中發送消息WM_SETICON消息,然後在該消息處理代碼中作適當的處理即可。