第十課 以對話框爲主要界面的應用程序
現在我們開始學習一些有關GUI編程的有趣的部分, 即:以對話框爲主要界面的應用程序。我們將分兩課來講述這一過程
理論:
如果您仔細關注過前一個程序就會發現:您無法按TAB鍵從一個子窗口控件跳到另一個子窗口控件,要想轉移的話只有 用鼠標一下一下地去點擊。對用戶來說這是不友好的。另一件事是如果您象前一課中那樣把主窗口的背景色從白色改成 灰色,爲了子窗口控件無縫地作相應地改變,您必須細分類所有子窗口。 造成上述諸多不便的原因是子窗口控件本來是爲對話框而設計的,象子窗口控件的背景色是灰色的,而對話框的背景色也是 灰色的,這樣它們本來就相互協調了,而無須程序員加入其他的處理。 在我們深入討論對話框前我們必須知道何謂對話框。一個對話框其實是有很多的子窗口控件的一個窗口,WINDOWS在對話框 內部有一個管理程序,由其來處理象按下TAB鍵則輸入焦點從一個子窗口空間條到另一個子窗口控件、按下ENTER鍵等於在當 前具有輸入焦點的子窗口控件上點擊了鼠標 等等這些雜事,這樣程序員就可以集中精力於他們的邏輯事務了。對話框主要用 作輸入輸出接口,人們無須知道它們內部的工作原理,而只要知道如何和他們進行交互就可以了。這也是面向對象設計中的 所謂信息隱藏。只要這個黑盒子中的實現足夠完美,我們就可以放心地使用,當然我們必須強調的是“黑盒子”必須完美。 WIN32 API 內部 的實現即是一個“黑盒子”。 噢,好象我們的討論有些走題,現在讓我們回到正題來,對話框的設計是爲了減少程序員的工作量的,一般您如果在窗口中 自己放一個子窗口控件您就必須自己處理其中的按鍵邏輯和細分類它的窗口過程。如果您把它放到對話框中,則這些雜事 對話框會自己處理,您只要知道如何獲得用戶輸入的數據和如何把數據放入到子窗口控件中去就可以了。 在程序中對話框和菜單一樣被定義成一種資源,您可以在腳本文件中寫一個對話框模板,其中包含該對話框和子窗口的特性, 然後用資源編輯器編輯。需要注意的是所有的資源必須放在同一個腳本文件中。 雖然可以用文本編輯器去編輯腳本文件,但是象要調整子窗口控件位置時要涉及到一些座標值時最好還是用一些可視化的編 輯器,這樣方便多了。一般在編譯器的開發包中都會帶資源編輯器,您可以用它們來產生一個模板然後增刪一些子窗口控件。 有兩種主要的對話框:模式對話框和無模式對話框。無模式對話框允許您把輸入焦點切換到(同一個應用程序的)另一個窗口,而該對話框無須關閉 。比如MS WORD 中的FIND對話框。模式對話框又有兩類:應用程序模式對話框和系統對話框。應用程序對話框不允許您在本 應用程序中切換輸入焦點,但是可以切換到其它的應用程序中去,而系統對話框則必須您對該對話框做出響應否則不能切換到 任何的應用程序中去。要創建一個無模式對話框調用API函數CreateDialogParam,而創建一個模式對話框則調用API函數DialogBoxParam。 其中應用程序模式對話框和系統模式對話框之間的差別是style參數不同,要想創建一個系統模式對話框該參數必須“或”上 DS_SYSMODAL標誌位。在對話框中若要和子窗口控件通訊則調用函數SendDlgItemMesage。該函數的語法如下:SendDlgItemMessage proto hwndDlg:DWORD,/該PAI函數對於用在向子窗口控件發送方面是非常有用的。譬如:如果您想得到編輯控件中的字符串可以這麼做:
idControl:DWORD,/
uMsg:DWORD,/
wParam:DWORD,/
lParam:DWORD
call SendDlgItemMessage, hDlg, ID_EDITBOX, WM_GETTEXT, 256, ADDR text_buffer具體要發送那些消息應當查詢有關的WIN32 API 參考手冊。 WINDOWS 還 提供幾個快速存取控件數據的函數。譬如:GetDlgItemText、CheckDlgButton等。這樣一來,您就可以不用去查詢每個消息的wParam和lParam參數獲得相關信息了。您應儘可能地使用這些API 函數,這樣使得您的代碼將來比較容易維護。對話框的管理函數會把一些消息發送給一個特定的回調函數:對話框過程處理函數,該函數的格式爲:
DlgProc proto hDlg:DWORD ,/該函數的格式非常類似於窗口的過程函數,除了返回值是TURE和FALSE,而不是HRESULT,存在於WINDOWS內部的對話框管理器纔是對話框真正的窗口過程函數。它會把某些消息傳遞給我們的窗口過程函數。所以當我們的窗口過程函數處理這些消息時就返回TTRUE,否則就在eax中返回FALSE。這也意味着我們的窗口過程函數在接受到自己不處理的消息時並不會調用DefWindowProc函數,因爲它本身不是一個真正的窗口過程函數。對於對話框有兩種用法:一種是把它作爲一個主窗口來用,一種是把它作爲一種輸入輸出設備使用。本課中我們將示範第一種用法。“把對話框用作主窗口”有兩種意思: 1。您可以調用RegisterClassEx函數把對話框模板註冊爲一個窗口類。這樣該對話框的行爲就類似於一個普通的窗口了:它通過在註冊窗口時指定的窗口過程來處理所有的消息,通過這種方法來使用對話框的好處是您不需要顯示地創建子窗口控件,WINDOWS本身會幫您創建好,另外還會幫您處理所有的按鍵邏輯,另外您還可以指定您窗口類結構中的光標和圖標; 2。您的應用程序創建沒有父窗口的對話框窗口,這種方法中,沒有必要需要一段處理消息循環的代碼,因爲所有的消息被直接送到對話框過程處理函數,這樣您也可以不要註冊一個窗口類。本課中我門將先使用第一種方法然後使用第二中方法。
iMsg:DWORD ,/
wParam:DWORD ,/
lParam:DWORD
例子:
.386
.model flat,stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include /masm32/include/windows.inc
include /masm32/include/user32.inc
include /masm32/include/kernel32.inc
includelib /masm32/lib/user32.lib
includelib /masm32/lib/kernel32.lib
.data
ClassName db "DLGCLASS",0
MenuName db "MyMenu",0
DlgName db "MyDialog",0
AppName db "Our First Dialog Box",0
TestString db "Wow! I'm in an edit box now",0
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
buffer db 512 dup(?)
.const
IDC_EDIT equ 3000
IDC_BUTTON equ 3001
IDC_EXIT equ 3002
IDM_GETTEXT equ 32000
IDM_CLEAR equ 32001
IDM_EXIT equ 32002
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hDlg:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,DLGWINDOWEXTRA
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_BTNFACE+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateDialogParam,hInstance,ADDR DlgName,NULL,NULL,NULL
mov hDlg,eax
invoke ShowWindow, hDlg,SW_SHOWNORMAL
invoke UpdateWindow, hDlg
invoke GetDlgItem,hDlg,IDC_EDIT
invoke SetFocus,eax
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke IsDialogMessage, hDlg, ADDR msg
.IF eax ==FALSE
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDIF
.ENDW
mov eax,msg.wParam
ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.IF lParam==0
.IF ax==IDM_GETTEXT
invoke GetDlgItemText,hWnd,IDC_EDIT,ADDR buffer,512
invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK
.ELSEIF ax==IDM_CLEAR
invoke SetDlgItemText,hWnd,IDC_EDIT,NULL
.ELSE
invoke DestroyWindow,hWnd
.ENDIF
.ELSE
mov edx,wParam
shr edx,16
.IF dx==BN_CLICKED
.IF ax==IDC_BUTTON
invoke SetDlgItemText,hWnd,IDC_EDIT,ADDR TestString
.ELSEIF ax==IDC_EXIT
invoke SendMessage,hWnd,WM_COMMAND,IDM_EXIT,0
.ENDIF
.ENDIF
.ENDIF
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start
#include "resource.h"
#define IDC_EDIT 3000
#define IDC_BUTTON 3001
#define IDC_EXIT 3002
#define IDM_GETTEXT 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 "Test Controls"
BEGIN
MENUITEM "Get Text", IDM_GETTEXT
MENUITEM "Clear Text", IDM_CLEAR
MENUITEM "", , 0x0800 /*MFT_SEPARATOR*/
MENUITEM "E&xit", IDM_EXIT
END
END
分析:
我們先來分析第一個例子:該例顯示瞭如何把一個對話框模板註冊成一個窗口類,然後創建一個由該窗口類派生的窗口。由於您沒有必要自己去創建子窗口控件,所以就簡化了許多的工作。
我們先來分析對話框模板。
MyDialog DIALOG 10, 10, 205, 60
先是對話框的名字,然後是關鍵字“DAILOG”。接下來的四個數字中,前兩個是對話框的座標,後兩個是對話框的寬和高(注意:它們的單位是對話框的單位,而不一定是像素點)。
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
END
上面的一塊定義了對話框中的子窗口控件,它們是聲明在一頭一尾的兩個關鍵字BEGIN和END之間的。
control-type "text" ,controlID, x, y, width, height [,styles] 控件的類型是資源編輯器定義好了的常數,您可以查找有關的手冊。現在我們來看看彙編源代碼。先看這部分: mov wc.cbWndExtra,DLGWINDOWEXTRA
mov wc.lpszClassName,OFFSET ClassName 通常cbWndExtra被設成NULL,但我們想把一個對話框模板註冊成一個窗口類,我們必須把該成員的值設成DLGWINDOWEXTRA。注意類的名稱必須和模板中跟在CLASS關鍵字後面的名稱一樣。餘下的成員變量和聲明一般的窗口類相同。填寫好窗口類結構變量後調用函數RegisterClassEx進行註冊。看上去這一切和註冊一個普通的窗口類是一樣的。 invoke CreateDialogParam,hInstance,ADDR DlgName,NULL,NULL,NULL 註冊完畢後,我們就創建該對話框。在這個例子中,我們調用函數CreateDialogParam產生一個無模式對話框。這個函數共有5個參數,其中前兩個參數是必須的:實例句柄和指向對話框模板名稱的指針。注意第二個參數是指向模板名稱而不是類名稱的指針。這時,WINDOWS將產生對話框和子控件窗口。同時您的應用程序將接收到由WINDOWS傳送的第一個消息WM_CREATE。 invoke GetDlgItem,hDlg,IDC_EDIT
invoke SetFocus,eax 在對話框產生後,我們把輸入輸出焦點設到編輯控件上。如果在WM_CREATE消息處理段中假如設置焦點的代碼,GetDlgItem函數就會失敗,因爲此時空間窗口還未產生,爲了在對話框和所有的子窗口控件都產生後調用該函數我們把它安排到了函數UpdatWindow後,GetDlgItem函數返回該控件的敞口句柄。
invoke IsDialogMessage, hDlg, ADDR msg
.IF eax ==FALSE
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDIF
現在程序進入消息循環,在我們翻譯和派發消息前,該函數使得對話框內置的對話框管理程序來處理有關的鍵盤跳轉邏輯。如果該函數返回TRUE,則表示消息是傳給對話框的已經由該函數處理了。注意和前一課不同,當我們想得到控件的文本信息時調用GetDlgItemText函數而不是GetWindowText函數,前者接受的參數是一個控件的ID 號,而不是窗口的句柄,這使得在對話框中調用該函數更方便。
好我們現在使用第二種方法把一個對話框當成一個主窗口來使用。在接下來的例子中,我們將產生一個應用程序的模式對話框,您將會發現其中根本沒有消息循環或窗口處理過程,因爲它們根本沒有必要!
.386
.model flat,stdcall
option casemap:none
DlgProc proto :DWORD,:DWORD,:DWORD,:DWORD
include /masm32/include/windows.inc
include /masm32/include/user32.inc
include /masm32/include/kernel32.inc
includelib /masm32/lib/user32.lib
includelib /masm32/lib/kernel32.lib
.data
DlgName db "MyDialog",0
AppName db "Our Second Dialog Box",0
TestString db "Wow! I'm in an edit box now",0
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
buffer db 512 dup(?)
.const
IDC_EDIT equ 3000
IDC_BUTTON equ 3001
IDC_EXIT equ 3002
IDM_GETTEXT equ 32000
IDM_CLEAR equ 32001
IDM_EXIT equ 32002
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke DialogBoxParam, hInstance, ADDR DlgName,NULL, addr DlgProc, NULL
invoke ExitProcess,eax
DlgProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_INITDIALOG
invoke GetDlgItem, hWnd,IDC_EDIT
invoke SetFocus,eax
.ELSEIF uMsg==WM_CLOSE
invoke SendMessage,hWnd,WM_COMMAND,IDM_EXIT,0
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.IF lParam==0
.IF ax==IDM_GETTEXT
invoke GetDlgItemText,hWnd,IDC_EDIT,ADDR buffer,512
invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK
.ELSEIF ax==IDM_CLEAR
invoke SetDlgItemText,hWnd,IDC_EDIT,NULL
.ELSEIF ax==IDM_EXIT
invoke EndDialog, hWnd,NULL
.ENDIF
.ELSE
mov edx,wParam
shr edx,16
.if dx==BN_CLICKED
.IF ax==IDC_BUTTON
invoke SetDlgItemText,hWnd,IDC_EDIT,ADDR TestString
.ELSEIF ax==IDC_EXIT
invoke SendMessage,hWnd,WM_COMMAND,IDM_EXIT,0
.ENDIF
.ENDIF
.ENDIF
.ELSE
mov eax,FALSE
ret
.ENDIF
mov eax,TRUE
ret
DlgProc endp
end start
#include "resource.h"
#define IDC_EDIT 3000
#define IDC_BUTTON 3001
#define IDC_EXIT 3002
#define IDR_MENU1 3003
#define IDM_GETTEXT 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 Second Dialog Box"
MENU IDR_MENU1
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
END
IDR_MENU1 MENU
BEGIN
POPUP "Test Controls"
BEGIN
MENUITEM "Get Text", IDM_GETTEXT
MENUITEM "Clear Text", IDM_CLEAR
MENUITEM "", , 0x0800 /*MFT_SEPARATOR*/
MENUITEM "E&xit", IDM_EXIT
END
END
分析如下:
DlgProc proto :DWORD,:DWORD,:DWORD,:DWORD
我們已經定義了DlgProc函數的原型,所以可以用操作符ADDR來獲得它的地址(記得嗎,它可以在運行時動態地獲得標識符的有效地址):
invoke DialogBoxParam, hInstance, ADDR DlgName,NULL, addr DlgProc, NULL
上面的幾行調用了函數DialogBoxPAram,該函數有五個參數,分別是:實例句柄、對話框模板的名字、父窗口的句柄、對話框過程函數的地址、和對話框相關的數據。該函數產生一個模式對話框。如果不顯示地關閉該函數不會返回。
.IF uMsg==WM_INITDIALOG
invoke GetDlgItem, hWnd,IDC_EDIT
invoke SetFocus,eax
.ELSEIF uMsg==WM_CLOSE
invoke SendMessage,hWnd,WM_COMMAND,IDM_EXIT,0
除了不處理WM_CREATE消息外對話框的窗口處理過程函數和一般的窗口處理過程相似。該過程函數接收到的第一個消息是WM_INITDIALOG。通常把初始化的代碼放到此處。注意如果您處理該消息必須在eax中返回TRUE。內置的對話框管理函數不會把WM_DESTROY 消息發送到對話框的消息處理函數,所以如果我們想在對話框關閉時進行處理,就把它放到WM_CLOSE消息的處理中。在我們的例子中我們發送消息WM_COMMAND,並在參數wParam中放置IDM_EXIT,這和處理WM_CLOSE 消息效果一樣,在處理IDM_EXIT 中我們調用EndDialog函數。如果我們想要銷燬一個對話框,必須調用EndDialog函數,該函數並不會立即銷燬一個窗口,而是設置一個標誌位,然後對話框管理器會處理接下去的銷燬對話框動作。好,現在我們來看看資源文件,其中最顯著的變化是在指定菜單時我們不是用字符串指定該菜單的名稱而是用了一個常量 IDR_MENU1。在調用DialogBoxParam產生的對話框中掛接一個菜單必須這麼做,注意在該對話框模板中,在該標識符前必須加MENU關鍵字,這兩個例子中的顯著不同是後者沒有圖標,這可以在處理WM_INITDIALOG中發送消息WM_SETICON消息,然後在該消息處理代碼中作適當的處理即可。