在前一課的子視窗控件範例中,您可能發現兩個困擾:無法用 Tab 鍵在控件間切換焦點,且背景色需手動協調。根本原因在於——子視窗控件原本就是為對話方塊設計的。對話方塊內建管理程序,自動處理鍵盤導航(Tab/Enter/Esc)、控件焦點切換、預設按鈕觸發等瑣事,讓開發者專注於商業邏輯。本課將示範兩種以對話方塊為主視窗的經典架構。
DialogBoxParam,進入專屬訊息迴圈,阻擋同應用程式其他視窗互動,直到 EndDialog 結束。適用於設定視窗、關鍵確認。CreateDialogParam,共用主訊息迴圈,需在迴圈中呼叫 IsDialogMessage 轉發鍵盤訊息。適用於尋找/取代、工具面板。在資源腳本(.rc)中定義對話方塊時,加入 CLASS "DLGCLASS" 陳述式;組語中註冊 WNDCLASSEX 時,將 cbWndExtra 設為 DLGWINDOWEXTRA,lpszClassName 對應 DLGCLASS。隨後呼叫 CreateDialogParam 建立非模態對話方塊,系統依模板自動建立所有子控件。主訊息迴圈需加入 IsDialogMessage 以啟用鍵盤導航。
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
mov wc.cbWndExtra,DLGWINDOWEXTRA
mov wc.lpszClassName,OFFSET ClassName
...
invoke CreateDialogParam,hInstance,ADDR DlgName,NULL,NULL,NULL
mov hDlg,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
此法完全不需註冊視窗類別、不需主訊息迴圈。DialogBoxParam 內部執行專屬模態迴圈,直接將訊息分派給 DlgProc。對話方塊程序回傳 TRUE(已處理)或 FALSE(交由預設處理),不呼叫 DefWindowProc。關閉時呼叫 EndDialog 而非 DestroyWindow。
MyDialog DIALOG 10, 10, 205, 60
STYLE ... (同方法一)
CAPTION "Our Second Dialog Box"
MENU IDR_MENU1 ; 以整數 ID 指定選單
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
MENUITEM "E&xit", IDM_EXIT
END
END
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
mov eax, TRUE
ret
.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
| 傳統視窗 API | 對話方塊專用 API | 說明 |
|---|---|---|
| GetWindowText | GetDlgItemText | 以控件 ID 而非句柄取得文字 |
| SetWindowText | SetDlgItemText | 以控件 ID 設定文字 |
| SendMessage(hCtrl, ...) | SendDlgItemMessage(hDlg, id, ...) | 簡化控件通訊 |
| CreateWindowEx | (資源模板自動建立) | 無需手動建立子控件 |
WM_INITDIALOG 是對話方塊建立後、顯示前的初始化時機,必須回傳 TRUE(除非自行設定焦點)。WM_DESTROY,關閉處理請放在 WM_CLOSE 或命令項目中呼叫 EndDialog。MENU IDR_MENU1 整數 ID 形式,不可用字串名稱。WM_INITDIALOG 中發送 WM_SETICON 訊息自行設定。對話方塊將視窗建立、控件佈局、鍵盤導航封裝為「黑盒子」,極大降低組語開發 Windows GUI 的複雜度。方法一適合需自訂視窗類別行為(如自訂圖示、游標、背景筆刷)的場合;方法二則是快速開發工具型對話方塊的最佳選擇。掌握兩種架構,即可靈活應對各類 GUI 專案需求。