傳統視窗程式需手動建立子控制項、處理 Tab 鍵焦點切換、灰色背景繪製等瑣碎邏輯。對話框內建對話框管理器,自動處理鍵盤導航 (Tab/Enter/Esc)、控制項建立/銷毀、背景色協調 (預設 `COLOR_BTNFACE`)、訊息分發等「管家事務」。開發者只需專注於資料交互 (取得/設定控制項內容) 與業務邏輯。本文示範兩種將對話框提升為主視窗的架構:
| API | 用途 | 關鍵參數 |
|---|---|---|
| `CreateDialogParam` | 建立無模式對話框 | `hInstance, 模板名, 父視窗, 回調, 參數` → 傳回 `HWND` |
| `DialogBoxParam` | 建立模式對話框 (阻塞) | 同上,但不返回直到 `EndDialog` |
| `IsDialogMessage` | 讓管理器預處理鍵盤訊息 | `hDlg, &msg` → `TRUE` 已處理,`FALSE` 需自行 `Translate/Dispatch` |
| `SendDlgItemMessage` | 向控制項發送訊息 | `hDlg, ID, Msg, wParam, lParam` |
| `Get/SetDlgItemText` | 取得/設定編輯框文字 | 簡化版 `SendDlgItemMessage` (WM_GET/SETTEXT) |
| `CheckDlgButton` / `IsDlgButtonChecked` | 核取方塊/單選鈕狀態 | |
| `EndDialog` | 結束模式對話框 | `hDlg, 結果碼` |
wc.cbWndExtra = DLGWINDOWEXTRA ; 必須!告知系統此類關聯對話框模板 wc.lpszClassName = "DLGCLASS" ; 必須與 .rc 中 CLASS "DLGCLASS" 一致 wc.hbrBackground = COLOR_BTNFACE+1 ; 系統灰色背景刷
`DLGWINDOWEXTRA` 讓系統在視窗結構額外預留空間儲存對話框專用資料 (如控制項 ID 對應表)。
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
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
關鍵:`IsDialogMessage` 必須在 `Translate/Dispatch` 之前調用,否則 Tab/Enter 導航失效。
start:
invoke GetModuleHandle, NULL
mov hInstance, eax
invoke DialogBoxParam, hInstance, ADDR DlgName, NULL, ADDR DlgProc, NULL
invoke ExitProcess, eax
`DialogBoxParam` 內部建立視窗、跑模態迴圈、處理 `WM_INITDIALOG`~`WM_CLOSE` 全生命週期,直到 `EndDialog` 返回。
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
| 特性 | 模式一 (無模式+類) | 模式二 (模態+回調) |
|---|---|---|
| 視窗類註冊 | 需要 (`RegisterClassEx`) | 不需要 |
| 訊息迴圈 | 手寫 (`GetMessage` + `IsDialogMessage`) | 系統內建 (阻塞) |
| 回調簽名 | 標準 `WndProc` (回傳 `LRESULT`) | `DlgProc` (回傳 `TRUE/FALSE`) |
| 未處理訊息 | `DefWindowProc` | `FALSE` (勿呼叫 `DefWindowProc`) |
| 圖示/游標 | 視窗類 `hIcon`/`hCursor` 生效 | 需 `WM_INITDIALOG` 中 `WM_SETICON` 設定 |
| 選單綁定 | 視窗類 `lpszMenuName` | 模板 `MENU IDR_MENU1` (整數 ID) |
| 適用場景 | 主視窗、需與其他視窗並存、非阻塞 | 精靈、設定對話框、模態輸入、工具箱 |
對話框作為主視窗,以「內建管理器」替換「手工管家」,大幅降低 GUI 開發認知負荷。模式一適合需共存多視窗、自訂非客戶區繪製的主程式框架;模式二則是模態交互的極致精簡。實戰中常混用:主視窗採模式一,彈出設定/關於/精靈採模式二。掌握兩種架構的啟動流程、訊息分發差異、資源綁定細節,即可在 Win32 組合語言中高效構建專業級桌面應用。