Ch 09 對話盒(2)

上一章提到對話盒包含了許多的子視窗,吾人稱之為『控制元件』,小木偶利用建立視窗 API,CreateWindowEx,連續建立了數個在對話盒堛漱l視窗,當然這種做法並不是最好的方法,繁雜且麻煩,尤其是在安排子視窗的位置、大小,沒有經過幾次試驗,無法滿意。本章將把對話盒加入資源描述檔內,並用圖形化的資源編輯器調整對話盒中控制元件的性質,這樣就能很容易地撰寫對話盒程式了。


對話盒面板簡介

事實上,微軟早已在資源描述檔中定義了對話盒面板 ( dialog template ),其結構如下︰

name    DIALOG  x, y, cx, cy
STYLE   sytle
FONT    height,name,weight,italic
CAPTION caption_text
CLASS   class_name
BEGIN
 CONTROL   "text", ID, class_name, style, x, y, width, height, exStyle
    ……
END

其中第一行的 name 是對話盒名稱,這個名稱將要和 CreateDialogParam API 配合,CreateDialogParam 是用來把對話盒面板建立各子視窗的 API,稍後再解說他。name 之後是關鍵字,DIALOG,用來表示這個區塊是對話盒面板。DIALOG 之後的 x、y 是對話盒的位置,此位置是相對於螢幕左上角的位置。cx、cy 分別表示對話盒寬度與高度。

接下來的 STYLE、FONT、CAPTION、CLASS 分別表示風格、字形、標題、類別名稱。這些對話盒性質的描述中,以 CLASS 最為重要,它將要配合 CreateDialogParam API 產生整個對話盒。接下來以一對 BEGIN/END 包含對話盒內的各個子控制元件,每個子控制元件以

CONTROL  "text", ID, class_name, style, x, y, width, height, exStyle

的形式描述,"text" 是控制元件的預定文字,ID 是識別號碼,x、y 是子視窗的位置,width、height 是子視窗的寬與高,style 是子視窗風格。class_name 是在系統中已預先定義的視窗類別,可以用 STATIC 表示靜態類別,BUTTON 表示按鈕類別,EDIT 表示編輯框類別,COMBOBOX 表示複合類別,LISTBOX 表示列表盒類別,SCROLLBAR 表示捲軸類別等等。

不同類別的子控件可用的風格不盡相同,例如編輯框控件與按鈕控件看起來是截然不同的兩種的控件,然則它們都是子視窗,產生方法相同但具有不同的風格,而是窗類別種類至少有這六種,所使用風格更多,所以要瞭解它們不事件易事。有時即使是同種視窗類別,其外觀與使用方式亦相去甚遠,例如 BUTTON 類別中有檢驗盒、下壓式按鈕、圓形按鈕等數種控制元件。像這樣具有相同的視窗類別,卻因風格不同而外觀與使用方式也不同,僅以風格來區別,並不是一目了然的做法。下面的例子中,第一個表示下壓式按鈕控件,第二個是檢驗盒控件:

CONTROL  "確定"    ,1,"BUTTON",BS_PUSHBUTTON,100, 20, 50, 14
CONTROL  "向右對齊",2,"BUTTON",BS_CHECKBOX  , 10, 10, 50, 14

但是如果不詳細檢查其後的風格,很不容易判斷,有時風格是又臭又長,那麼判讀更為困難。所以 RC.EXE 也允許另一種表示方式子控件:

control-type  "text", ID, x, y, width, height [,styles]

這時 control-type 就能表示出各種子控件與部分風格了,例如上面兩個子控件就以

PUSHBUTTON  "確定"    ,1,100, 20, 50, 14
CHECKBOX    "向右對齊",2, 10, 10, 50, 14

表示,這樣就直接多了,可讀性也大大提高了。需要注意的是,像 PUSHBUTTON、CHECKBOX 這些並不是視窗類別,它們是視窗類別再加上某些風格而成。下表是常用的 class_name ( 子視窗類別 ) 與 control-type ( 控制元件 ) 關係:

視窗類別
class name
控制元件
control type
名稱內定風格
STATIC LTEXT向左對齊的字串 SS_LEFT,WS_GROUP
CTEXT 向中對齊的字串 SS_CENTER,WS_GROUP
RTEXT 向右對齊的字串 SS_RIGHT,WS_GROUP
ICON 圖示控件 SS_ICON
BUTTON PUSHBUTTON 下壓式按鈕 BS_PUSHBUTTON,WS_TABSTOP
DEFPUSHBUTTON 內定下壓式按鈕 BS_DEFPUSHBUTTON,WS_TABSTOP
CHECKBOX 檢驗盒 BS_CHECKBOX,WS_TABSTOP
AUTOCHECKBOX 自動檢驗盒 BS_AUTOCHECKBOX,WS_TABSTOP
RADIOBUTTON 圓形按鈕 BS_RADIOBUTTON,WS_TABSTOP
AUTORADIOBUTTON 自動圓形按鈕 BS_AUTORADIOBUTTON,WS_TABSTOP
GROUPBOX 群組 BS_GROUPBOX
EDIT EDITTEXT 編輯框 ES_LEFT,WS_BORDER,WS_TABSTOP

靜態視窗類別的控制元件 ( Static Controls )

靜態視窗類別是在一塊矩形區域內顯示固定的文字或圖示資料。前面提到,在對話盒面板中定義子控件有兩種方式,一種較完整,一種較具可讀性。以前種方式表示時,格式如下:

CONTROL     "text", ID, class_name, style, x, y, width, height, exStyle

此時 CONTROL 是固定的,class_name 也是固定的,必為 STATIC,而 style 可以用 SS_LEFT、SS_CENTER、SS_RIGHT 分別表示文字向子視窗左邊、中間、右邊對齊 ( SS_ 表示 static style 的意思 )。例如

CONTROL     "輸入英吋:",1,"STATIC", SS_LEFT, 20, 20, 50, 12

以後種方式表示文字靜態元件時,格式如下:

control-type  "text", ID, x, y, width, height [,styles]

control-type 可以用 LTEXT、CTEXT、RTEXT 其中的任何一個,其意義分別表示其後所接的文字向左、向中、向右靠齊,其實就分別代表 SS_LEFT、SS_CENTER、SS_RIGHT 風格。至於所顯示的文字放在 text 內,並以一對『"』括起來,識別號碼可以是 0 到 65535 之間的正整數。例如:

LTEXT       "輸入英吋:", 1, 20, 20, 50, 12

其實這種定義子控件的方式和上面白色的定義是一樣的。至於其他常用的風格還有 SS_BLACKFRAME、SS_WHITEFRAME 等等,SS_BLACKFRAME 是顯示邊框顏色,此顏色和 COLOR_WINDOWFRAME 相同,而 SS_WHITEFRAME 則是和 COLOR_WINDOW 同色。當然在不衝突的情形下,這些風格可以用『|』使他們互相存在。

假如要顯示圖示時,control-type 用 ICON,"text" 為圖示名稱,並不是圖示檔名,至於圖示的來源是由資源檔中另一個段落,ICON,所指定,請參考第七章圖示資源的說明。你可以看看例子,小木偶不再贅述了。

按鈕視窗類別的控制元件 ( Button Controls )

按鈕控件有許多種樣子,最常見的是下壓式按鈕 ( PUSHBUTTON ),它是一個矩形區域,加上立體的外框,形成一個按鈕,可供使用者以滑鼠左鍵點選。其他還有內定下壓式按鈕 ( DEFPUSHBUTTON )、圓形按鈕 ( RADIOBUTTON )、檢驗盒 ( CHECKBOX )、群組 ( GROUPBOX ) 等等。如下圖所示:

說明按鈕控件
這一章先介紹下壓式按鈕及內定下壓式按鈕。內定的按鈕是指假如此對話盒還含有編輯框控制元件時,當使用者按下 Enter 鍵時,就相當和以滑鼠點選該按鈕一樣。在按鈕上的文字放在 text 堙A同樣的,也是以一對『"』括起來。定義下壓是按鈕時,可以用

PUSHBUTTON  "確定", 1, 100, 20, 50, 14
或
CONTROL     "確定", 1, BS_PUSHBUTTON, 100, 20, 50, 14

表示。

編輯框類別的控制元件 ( Edit Controls )

編輯框是一塊長方形區域,供使用者以鍵盤輸入文字,也可以用 Del 鍵、退位鍵 ( backspace )、Ctrl-C、Ctrl-V等鍵做刪除、退位刪除、拷貝、貼上等編輯動作。要使用編輯框子控件時,control-type 可以用 EDITTEXT 表示,例如:

EDITTEXT  3,99,45,50,12,ES_NUMBER

但用 EDITTEXT 時無法設定內定文字,也就是說螢幕一顯示對話盒時,編輯框無法顯示文字,假如要設定內定文字,那麼必須使用下面的方法:

CONTROL   "100",3,"EDIT",ES_NUMBER,99,45,50,12

這樣在一顯示對話盒時,編輯框內就會出現『100』了。編輯框的風格是以 ES_ ( edit style ) 為開頭,常用的有下列數種:

  1. 文字排列:ES_LEFT、ES_CENTER 與 ES_RIGHT 分別使編輯框內的文字向左、向中、向右對齊。
  2. 文字的大小寫:ES_LOWERCASE、ES_UPPERCASE 分別會讓使用者輸入的英文字母自動地轉換成小寫或大寫。有時要轉換成特殊的字集時,可用 ES_OEMCONVERT。
  3. 行數:一般不特別設定,編輯框內定只能輸入一行文字,當使用者按下 Enter 鍵表示輸入完成。如果要輸入多行文字,可以用 ES_MULTILINE,這樣按下 Enter 鍵時,會到下一行繼續輸入。
  4. 捲軸:當使用者輸入的文字超過編輯框大小時,可以加上 ES_AUTOHSCROLL 和 ES_AUTOVSCROLL 使編輯框自動地向水平或垂直捲動。ES_AUTOVSCROLL 只有在有 ES_MULTILINE 風格時才有效。
  5. 編框:內定的編輯框無邊框,可以加上 WS_BORDER 使編輯框變得較美觀。( 注意,所有的視窗可用邊框,所以是 WS_ 起頭,不是 ES_。)
  6. ES_NUMBER 風格:ES_NUMBER 使得編輯框僅能輸入數字。
  7. ES_READONLY 風格:使編輯框不能輸入文字,但是仍可使用各種編輯鍵,如拷貝、貼上。
  8. ES_PASSWORD 風格:讓使用者輸入的文字都顯示成『*』或黑點,常用在輸入密碼的時候。
  9. ES_NOHIDESEL:一般編輯框失去輸入焦點時,用滑鼠所選擇文字的反白會消失,但加上 ES_NOHIDESEL 風格後,則即使失去焦點也不會使反白消失。
  10. ES_WANTRETURN:可以讓具有 ES_MULTILINE 風格的編輯框,在使用者按下 Enter 鍵時,新增下一行。

資源編輯器

如前章所述,安排子控制元件的位置、大小是設計對話盒最麻煩的部份。最好是能有『所見即所得』的對話盒編輯器,能在桌面上以滑鼠點選、拖、拉的方法,就能建立出含有相對應的子控制元件對話盒的資源檔。

網際網路上有一些資源編輯器可以滿足這個需求。小木偶介紹一個共享軟體,Resource Builder,這個軟體是由 SIComponents 公司開發的軟體,最新版可到 SIComponents 公司下載,舊版可以到 The Programmer's Portal 網頁中的 bin tools 選項下載,或直接按此處下載。將此壓縮檔解開後,執行 SETUP.EXE 即可安裝。

安裝完畢,執行 Resource Builder 後,在 Resource Builder 畫面的左下方有幾個資源選項,Icon、Cursor、Bitmap……,假如您想建立對話盒,就把滑鼠移到 Dialog 選項,然後按下滑鼠右鍵,會出現一個彈出選單,再使滑鼠移到 Add 上,表示要在資源檔上加入對話盒。或者是在標題攔下的選單選擇『Resource』『Add…』,此時畫面如下:

選擇『Add…』後,工具列會改變,主畫面也會被分割成三部份,最左邊是可加入資源檔的選項,中間是工作區 ( 此處就是您所設計的對話盒,也就是您日後執行程式時所看到的畫面),最右邊是工作區埵U元件的性質 ( Properties ),如下圖所示︰
當您想加入一個控制元件時,可以到工具列選擇控制元件,然後移動滑鼠,在您想要的放置該控制元件的位置上按下滑鼠左鍵即可。您也可以在工作區中選擇其中一個控制元件,調整其大小、位置。如此依序把您要加入的控制元件添上,存檔即可。

看起來似乎很完美,不過 Resource Builder 一直到 2.1.2 版都對中文不相容。但是有解決的方法,那就是在設計控制元件時不要使用中文,存成檔案後 ( 此檔案是資源描述檔,是一種純文字檔 ),再用文書處理器稍加修改。小木偶把剛剛設計好的對話盒存成 inch.rc 檔,開啟後是像下面這樣︰

/*********************************************
File: D:\HOMEPAGE\SOURCE\INCH.RC
Generated by Resource Builder 2.0.
*********************************************/
// Below are important 3 lines, don't change them!
/*
OutputExt=res
*/

DIALOG_0 DIALOG 0, 0, 164, 71
STYLE DS_SETFONT |WS_POPUP |WS_CAPTION |WS_VISIBLE |WS_SYSMENU |
      WS_THICKFRAME |WS_MAXIMIZEBOX |WS_MINIMIZEBOX 
CAPTION "inch to cm"
FONT 8, "Tahoma"
LANGUAGE LANG_NEUTRAL, 0
BEGIN
  CONTROL "Picture0",0,"STATIC",SS_BLACKFRAME |WS_CHILD |
          WS_VISIBLE ,6,21,32,32
  CONTROL "Label0",1,"STATIC",SS_LEFT |WS_CHILD |
          WS_GROUP |WS_VISIBLE ,53,19,99,18
  CONTROL "input inch:",2,"STATIC",SS_LEFT |WS_CHILD |
          WS_GROUP |WS_VISIBLE ,53,47,41,8
  CONTROL "",3,"EDIT",ES_NUMBER |ES_LEFT |WS_CHILD |WS_BORDER |WS_TABSTOP |
          WS_VISIBLE ,99,45,50,12
  CONTROL "Button0",4,"BUTTON",BS_DEFPUSHBUTTON |BS_VCENTER |
          BS_CENTER |WS_CHILD |WS_TABSTOP |WS_VISIBLE ,20,61,50,14
  CONTROL "Button1",5,"BUTTON",BS_DEFPUSHBUTTON |BS_VCENTER |
          BS_CENTER |WS_CHILD |WS_TABSTOP |WS_VISIBLE ,90,61,50,14
END

但是其中白色的部份是屬於標題或按鈕上的文字,應該換成中文,小木偶以 UtralEdit 將它修飾成自己喜歡的,同時加上選單及圖示,最重要的是要在對話盒段落處加上 CLASS 這一項必須添加上去,如下面所示︰

#include "c:\masm32\include\resource.h"

#define IDM_Exit    2001
#define IDM_Clear   2002
#define IDM_Calc    2003
#define IDM_Help    2004

InchMenu        MENU
BEGIN
  MENUITEM      "離開",IDM_Exit
  MENUITEM      "歸零",IDM_Clear
  MENUITEM      "計算",IDM_Calc
  MENUITEM      "說明",IDM_Help
END

InchIcon        ICON    INCH.ico

InchDlg DIALOG  0,0,170,80
STYLE   DS_SETFONT |WS_POPUP |WS_CAPTION |WS_VISIBLE |WS_SYSMENU |
        WS_THICKFRAME |WS_MAXIMIZEBOX |WS_MINIMIZEBOX 
CAPTION "英吋轉換成公分"
CLASS   "InchDlgClass"
FONT    9,"新細明體"
BEGIN
  ICON    "InchIcon",1001,9,8,32,32
  CONTROL "您可以輸入英吋,INCH.EXE 會換算成公分(cm)",1002,"STATIC",
          SS_LEFT |WS_CHILD |WS_GROUP |WS_VISIBLE ,51,6,110,21
  CONTROL "輸入英吋︰",1003,"STATIC",SS_LEFT |WS_CHILD |WS_GROUP |
          WS_VISIBLE ,51,32,43,8
  CONTROL "0",1004,"EDIT",ES_LEFT |WS_CHILD |WS_BORDER |WS_TABSTOP |
          WS_VISIBLE ,98,30,38,12
  CONTROL "計算",1005,"BUTTON",BS_DEFPUSHBUTTON |BS_VCENTER |BS_CENTER |
          WS_CHILD |WS_TABSTOP |WS_VISIBLE ,11,47,50,14
  CONTROL "離開",1006,"BUTTON",BS_DEFPUSHBUTTON |BS_VCENTER |BS_CENTER |
          WS_CHILD |WS_TABSTOP |WS_VISIBLE ,108,47,50,14
END

此外還有一些細節,例如控制元件的識別號、對話盒的高度都略加修飾了。修飾過後的 inch.rc 因為含有中文,就無法再以 Resource Builder 修改了。根據 inch.rc 的對話盒面板及選單所建立的視窗如下圖:


原始檔

底下是 inch.asm 原始碼:

        .386
        .model  flat,stdcall
        option  casemap:none

include         windows.inc
include         user32.inc
include         kernel32.inc
includelib      user32.lib
includelib      kernel32.lib

IDC_Icon        equ     1001    ;11 控制元件,圖示的 ID
IDC_String1     equ     1002    ;12 控制元件,字串的 ID
IDC_String2     equ     1003    ;13 控制元件,字串的 ID
IDC_Edit        equ     1004    ;14 控制元件,編輯框的 ID
IDC_Calc        equ     1005    ;15 控制元件,計算的 ID
IDC_Exit        equ     1006    ;16 控制元件,離開的 ID
IDM_Exit        equ     2001    ;17 選單,離開的 ID
IDM_Clear       equ     2002    ;18 選單,清除的 ID
IDM_Calc        equ     2003    ;19 選單,查詢的 ID
IDM_Help        equ     2004    ;20 選單,說明的 ID
eff_digt        equ     7       ;21 輸入英吋最大位數

WndProc         proto   :HWND,:UINT,:WPARAM,:LPARAM
inch2cm         proto   :DWORD
;***************************************
        .DATA
ClassName       db          'InchDlgClass',0
HelpText        db          'INCH.EXE 程式是用來把英吋換算成公分。'
                db          '你可以在底下輸入英吋(只有前7位有效),然'
                db          '後按下計算按鈕,程式將換算成公分。',0
HelpTitle       db          '說明',0
ExitText        db          '是否退出此程式?',0
ExitTitle       db          '詢問',0
CalcTitle       db          '換算結果',0
String1         db          ' 英吋 = '
String2         db          ' 公分',0
String3         db          '您輸入了不是數字的字元。',0
CalcText        db          40h dup (' '),0     ;38 計算結果字串
IconName        db          'InchIcon',0        ;39 圖示名稱
MenuName        db          'InchMenu',0        ;40 選單名稱
DlgName         db          'InchDlg',0         ;41 對話盒名稱
ClearText       db          '0',0
hInstance       HINSTANCE   ?
hDlg            HWND        ?
wc              WNDCLASSEX  <?>
msg             MSG         <?>
factor          dd          254
ten_power       dd          1000000000,100000000,10000000
                dd          1000000,100000,10000,1000,100,10
inch            db          11 dup (0)  ;50 使用者輸入之英吋字串
String1_Len     equ         offset String2-offset String1
String2_Len     equ         offset String3-offset String2
;***************************************
        .CODE
;---------------------------------------
inch2cm proc    uses ebx esi edi inch_addr:DWORD
        local   counter:BYTE    ;57 使用者輸入的數字,僅有 eff_digt 位有效
        local   fraction:BYTE   ;58 小數位數
        local   if_frac:BYTE    ;59 使用者是否曾輸入小數點,0表示不曾,1表示曾
        sub     eax,eax         ;60 EDX:EAX 將存放輸入之英吋數的十六進位數
        sub     edx,edx         ;   故先清除
        mov     esi,inch_addr   ;62 取得使用者輸入英吋字串之位址於 ESI
        mov     fraction,2      ;63 一英吋=2.54cm,2.54有兩位小數
        mov     ebx,eax         ;64 清除 EBX 高字組,EBX 將存放每次取得的數目
        mov     ecx,10          ;65 每取得一位數必須使 EDX:EAX=10*EDX:EAX+EBX
        mov     counter,al      ;66 counter 為現在取得第幾個位數
        mov     if_frac,al      ;67 先假設使用者沒有輸入小數點
        mov     edi,offset CalcText

next:   mov     bl,[esi]
        inc     esi
.if bl=='.'
        mov     if_frac,1
        mov     [edi],bl
.elseif bl==0
        jmp     finish
.else
        mov     [edi],bl;78 使 BL 存於 CalcText 字串中
        mul     ecx
        sub     bl,'0'  ;80 若 BL<0 或 BL>9 表示使用
        jb      not_n   ;81 者輸入了不是數字的字元
        cmp     bl,9
        ja      not_n
        add     eax,ebx
        adc     edx,0
  .if if_frac==1
        inc     fraction
  .endif
.endif
        inc     counter
        inc     edi
        cmp     counter,eff_digt
        je      finish
        jmp     next

not_n:  mov     eax,offset String3
        jmp     exit    ;97 退出 inch2cm 副程式

finish: mul     factor  ;99 EDX:EAX=EDX:EAX*254

        mov     esi,offset String1
        mov     cx,String1_Len
        rep     movsb

        push    edi     ;105 把 EDX:EAX 內的十六進位轉換成十進位 ASCII 字串
        mov     edi,offset inch ;106 因為 inch 字串已不用,權充為 cm 字串
next_power:
        mov     ebx,ten_power[ecx*4]
        div     ebx
        add     al,'0'
        inc     cx
        stosb
        mov     eax,edx
        sub     edx,edx
        cmp     cx,eff_digt+2
        jne     next_power
        add     al,'0'
        stosb

        mov     al,fraction
        mov     ecx,edi
        sub     ah,ah
        sub     ecx,offset inch ;123 ECX=公分字串的長度
        cwd
        mov     edx,edi
        sub     ecx,eax         ;126 ECX=公分字串整數部份的長度
        mov     ebx,eax         ;127 EBX=公分字串的小數部份長度
        mov     esi,offset inch ;128 ESI 指向公分字串
        dec     edx             ;129 EDX 指向公分字串之尾端
        pop     edi
        jcxz    no_int
nxt_1:  lodsb   ;132 使十進位的ASCII字串加上小數點
.if al=='0'
  .if ah==1     ;134 AH 表示是否曾出現過非零數字,出現過為 1
        stosb   ;135 若已出現過非零數字,之後再出現的零要存起來
  .endif
.else
        mov     ah,1    ;138 若整數部份出現第一個非零的數字,設定 AH 為 1
        stosb
.endif
        loop    nxt_1
.if ah==0
no_int: stosb   ;143 如果整數部份為零,加上『0』
.endif

.if ebx!=0
nxt_2:  cmp     byte ptr [edx],'0'
        jne     fnd_nz
        dec     ebx
        or      ebx,ebx
        jz      fnd_nz
        dec     edx
        jmp     nxt_2
.endif
fnd_nz: mov     ecx,ebx
        jcxz    no_pt
        mov     al,'.'
        stosb
.if ecx!=0
        rep     movsb
.endif
no_pt:  mov     esi,offset String2
        mov     ecx,String2_Len
        rep     movsb
        mov     eax,offset CalcText
exit:   ret
inch2cm endp
;---------------------------------------
deal_calc       proc    ;169 處理『計算』按鈕的副程式
        invoke  GetDlgItemText,hDlg,IDC_Edit,offset inch,eff_digt+1
        invoke  inch2cm,offset inch
        invoke  MessageBox,hDlg,eax,offset CalcTitle,MB_OK
        ret
deal_calc       endp
;---------------------------------------
start:  invoke  GetModuleHandle,NULL
        mov     hInstance,eax
        mov     wc.cbSize,SIZEOF WNDCLASSEX
        mov     wc.style,CS_HREDRAW or CS_VREDRAW
        mov     wc.lpfnWndProc,offset WndProc
        mov     wc.cbWndExtra,DLGWINDOWEXTRA
        mov     wc.hInstance,eax
        mov     wc.hbrBackground,COLOR_BTNFACE+1 
        mov     wc.lpszMenuName,offset MenuName
        mov     wc.lpszClassName,offset ClassName
        invoke  LoadIcon,hInstance,offset IconName
        mov     wc.hIcon,eax
        mov     wc.hIconSm,eax
        invoke  LoadCursor,NULL,IDC_ARROW
        mov     wc.hCursor,eax
        invoke  RegisterClassEx,offset wc       ;191
        invoke  CreateDialogParam,hInstance,offset DlgName,NULL,NULL,NULL
        mov     hDlg,eax
        invoke  ShowWindow,hDlg,SW_SHOWNORMAL
        invoke  UpdateWindow,hDlg
        invoke  GetDlgItem,hDlg,IDC_Edit        ;196
        invoke  SetFocus,eax
.while TRUE
        invoke  GetMessage,offset msg,NULL,0,0
.break .if (!eax)
        invoke  IsDialogMessage,hDlg,offset msg
  .if eax==FALSE
        invoke  TranslateMessage,offset msg
        invoke  DispatchMessage,offset msg
  .endif
.endw
        mov     eax,msg.wParam
        invoke  ExitProcess,eax
;---------------------------------------
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
;216 以下是處理使用者按下某個選單的程式
    .if ax==IDM_Calc
        call    deal_calc
    .elseif ax==IDM_Clear
        invoke  SetDlgItemText,hWnd,IDC_Edit,offset ClearText   ;220 歸零
    .elseif ax==IDM_Help
        invoke  MessageBox,hWnd,offset HelpText,offset HelpTitle,MB_OK
    .elseif ax==IDM_Exit
        invoke  MessageBox,hWnd,offset ExitText,offset ExitTitle,\
                MB_YESNO or MB_ICONQUESTION
      .if eax==IDYES
        invoke  DestroyWindow,hWnd
      .endif
    .endif
  .else
;231 以下是處理使用者點選控制元件的程式
        mov     edx,wParam
        shr     edx,16
    .if dx==BN_CLICKED
      .if ax==IDC_Calc
        call    deal_calc
      .elseif ax==IDC_Exit
        invoke  SendMessage,hWnd,WM_COMMAND,IDM_Exit,0  ;238 傳遞訊息給對話盒
      .endif
    .endif
  .endif
.else
        invoke  DefWindowProc,hWnd,uMsg,wParam,lParam
        ret
.endif
        xor     eax,eax 
        ret

WndProc endp
;***************************************
        end     start

底下是 inch.mak 檔案:

ALL:inch.exe

inch.exe : inch.asm inch.res
        ml /coff inch.asm /link inch.res

inch.res : inch.rc inch.ico
        rc inch.rc

組譯時,在命令提示下輸入

D:\HomeHapg\SOURCE>nmake -f inch.mak [Enter]

即可得到 inch.exe。


解說

假如我們用對話盒面板建立對話盒以及其內的子控件的話,很顯然的必須用一個 API 來處理這些事情,那就是 CreateDialogParam 和 DialogBoxParam。此處先談 CreateDialogParam。

當我們使用資源編譯器,RC.EXE,編譯資源描述檔時,假如此資源描述檔內含有對話面板的話,那麼 RC.EXE 會產生一段二進位資料結構,包含 DLGTEMPLATEEX、FONTINFOEX 以及 DLGITEMTEMPLATEEX。DLGTEMPLATEEX 結構內包含了父視窗的資料,DLGITEMTEMPLATEEX 包含了子視窗的資料,FONTINFOEX 則是為此對話盒指定字型。這對話盒的名稱就是在資源描述檔中的對話盒名稱,其類別也在資源描述檔中描述。

當然,假如您沒有在資源描述檔定義控制元件,或者沒有定義字型,那麼就沒有 DLGITEMTEMPLATEEX 或是 FONTINFOEX 結構,不過無論如何,只要有 DIALOG 區段,就會有一個 DLGTEMPLATEEX 結構。然後我們以 CreateDialogParam API 依此結構建立各個子視窗。

CreateDialogParam 首先會檢查是否指定字型,如果有的話會根據 FONTINFOEX 的內容產生邏輯字型,然後再以 DLGTEMPLATEEX 內的資料,呼叫 CreateWindowEx API 建立對話盒視窗,再依據每個 DLGITEMTEMPLATEEX 內的資料,呼叫 CreateWindowEx 建立各個子視窗控件。等對話盒及所有子視窗都已產生後,CreateDialogParam 會發出 WM_INITDIALOG 訊息給對話盒,此訊息的 lParam 可以由 CreateDialogParam API 的第 5 個參數,dwInitParam 指定。

CreateDialogParam API

CreateDialogParam API 的原型如下:

HWND CreateDialogParam(
    HINSTANCE   hInstance,      // handle to application instance
    LPCTSTR     lpTemplateName, // identifies dialog box template
    HWND        hWndParent,     // handle to owner window
    DLGPROC     lpDialogFunc,   // pointer to dialog box procedure
    LPARAM      dwInitParam     // initialization value
   );

下面是 CreateDialogParam 參數說明:

第一個參數,hInstance,是執行實例代碼。

第二個參數是 lpTemplateName,它指向一個以 NULL 結尾的字串,這個字串就是對話盒名稱,也就是在資源描述檔中在 DIALOG 所指定的名稱。CraeteDialogParam 會依據此名稱,在 EXE 檔內尋找以這個對話盒為名稱的 DLGTEMPLATEEX 結構,然後再以這個結構的資料建立各子視窗。

第三個參數是擁有這個對話盒的父視窗代碼,假如沒有父視窗的話,就設為 NULL。

第四個參數,lpDialogFunc,是一個位址,指向對話盒函式位址,對話盒函式也屬於一種回呼函式 ( call back function ),它是用來處理對話盒訊息的。但是在這個例子堙A此函式被省略了,而是被 WNDCLASSEX 結構體中的 lpfnWndProc 所指定。

第五個參數是指定對話盒所收到 WM_INITDIALOG 訊息的 lParam。

當 CreateDialogParam 成功建立對話盒後,會在 EAX 暫存器中傳回對話盒代碼,在程式的第 193 行存入 hDlg 變數堙C這個對話盒代碼就像前幾章的視窗代碼一樣,往後假如要對此對話盒操作,必須用到 hDlg。

GetDlgItem API

第 196 行是取得子視窗,編輯框,的視窗代碼,這是為了一建立好對話盒及各子視窗,就能讓編輯框成為作用中的視窗,方便使用者輸入。取得子視窗代碼可用 GetDlgItem API,其原型為:

HWND GetDlgItem(
    HWND    hDlg,       // handle of dialog box
    int     nIDDlgItem  // identifier of control
    );

GetDlgItem 有兩個參數,hDlg 是指要取得在那一個對話盒內的子視窗,應該填入該對話盒的代碼。nIDDlgItem 是要取得的子視窗識別碼。如果 GetDlgItem 成功,則 EAX 會傳回子視窗的視窗代碼。在程式第 197 行,再以 SetFocus API 設定編輯框為作用中的子視窗。

訊息迴圈與 IsDialogMessage API

程式第 198 行到第 206 行是訊息迴圈,和以前不同的是多了呼叫 IsDialogMessage API 這一行。多了這一行是用來處理使用者按下方向鍵或者 TAB 鍵時,可以使各個子視窗輪流變成作用中,假如您不想讓使用者這樣做,也可以不加上這段程式。底下是 IsDialogMessage 的原型:

BOOL IsDialogMessage(
    HWND    hDlg,       // handle of dialog box
    LPMSG   lpMsg       // address of structure with message
   );

很明顯的,hDlg 是指要處理的對話盒代碼,lpMsg 是指訊息結構體的位址。當使用者按下方向鍵或者 TAB 鍵時,IsDialogMessage 會自行處理,使輸入焦點自動轉移,並且呼叫 TranslateMessage 和 DispatchMessage 這兩個 API,再傳回 TRUE,所以被 IsDialogMessage 處理過的訊息就不需要再 TranslateMessage 和 DispatchMessage 了。假如使用者不是按下方向鍵或 TAB 鍵,那麼訊息就不會被 IsDialogMessage 處理,並且傳回 FALSE。

程式第 210 行開始進入視窗函式,這個程式的視窗函式有很大一部份是處理 WM_COMMAND 的。與前幾章一樣,在 WM_COMMAND 訊息中,檢查 lParam 為零表示使用者按下選單,不為零表示使用者按下控制元件。選單的處理方式前幾章已經說明,此處不贅述,底下僅說明選擇『歸零』選單的理方式。

SetDlgItemText API

第 220 行,使用者選擇『歸零』時,程式必須使編輯框內的數值變為零。質言之,程式必須對控制元件設定標題或文字而使編輯框控制元件之數值變為零,這項工作可以用 SetDlgItemText API 達成。底下是 SetDlgItemText 之原型:

BOOL SetDlgItemText(
    HWND    hDlg,       // handle of dialog box
    int     nIDDlgItem, // identifier of control
    LPCTSTR lpString    // text to set
   );

SetDlgItemText 的參數也顯而易見,hDlg 為對話盒代碼,nIDDlgItem 為對話盒中的控制元件識別碼,lpString 為要傳遞之字串位址,此字串須以 NULL 結尾。SetDlgItemText 能把 lpString 所指位址之字串,傳給 hDlg 所代表對話盒內的 nIDDlgItem 控制元件。

事實上,並不是只有這一種方法可以把訊息傳遞給編輯框,使編輯框之內容變為『0』。編輯框以及各種控制元件都是在對話盒堛漱l視窗,所以可以把它們當做視窗處理,換句話說,對視窗的動作也可以加諸於控制元件上。在 Win32 系統中,可以用 SendMessage API 對視窗發出訊息。

SendMessage API

SendMessage API 的原型為:

LRESULT SendMessage(
    HWND    hWnd,       // handle of destination window
    UINT    Msg,        // message to send
    WPARAM  wParam,     // first message parameter
    LPARAM  lParam      // second message parameter
   );

hWnd 是視窗代碼,意即訊息將發送到該視窗代碼所指的視窗窗。Msg 是訊息種類。wParam、lParam 是更進一步的訊息資料,與訊息種類有關。在這個例子中,要對編輯框發送訊息而使其內容變為『0』可以第 220 行改成

        invoke  GetDlgItem,hWnd,IDC_Edit
        invoke  SendMessage,eax,WM_SETTEXT,0,offset ClearText

使用 WM_SETTEXT 時,wParam 要設為零,lParam 是指向以零結尾的字串位址,視窗內容將會變成該字串。WM_SETTEXT 的對象如果是一般視窗,將會修改標題欄的內容;如果是按鈕類別的控件,將修改按鈕上的文字;如果是編輯框類別的控件,將修改編輯框的內容;如果是靜態類別的控件,將修改其文字。

另外,SendMessage 與 SetDlgItemText 都可修改子控件內容,差別在 SendMessage 須先取得子控件代碼,而 SetDlgItemText 則要取得子控件識別碼。一般而言,在對話盒堙A各種子控件識別碼立即可由資源檔堛疑悝O碼得知,所以一般使用 SetDlgItemText 較為方便。

還有一些常用的訊息可以發送給編輯框,控制某些編輯框的行為,這些訊息幾乎以 EM_ 開頭,意即 edit message 的意思。常用的如下表:

訊  息 wParam lParam 說  明
EM_SETLIMITTEXT 長度 0 設定編輯框所能輸入的限制,若 wParam 設為零
  對 Win 9x/Me 而言,單行編輯框是 32766 位元組,多行編輯框是 65535 位元組。
  對 NT/2k/XP 而言,單行編輯框是 7FFFFFFEH 位元組,多行編輯框是 0FFFFFFFFH 位元組。
EM_GETLIMITTEXT 0 0 取得編輯框所能編輯的最大限度,以字元為單位。
EM_SETPASSWORDCHAR 字元 0 設定顯示密碼的字元。若為 0,則 ES_PASSWORD 會被清除。
EM_GETPASSWORDCHAR 0 0 取得顯示密碼的字元。若 SendMessage 傳回 NULL,表示為設定顯示密碼字元。
EM_GETLINE 行編號 緩衝區位址 將所所指定的行編號內容拷貝到緩衝區。此行編號以零開始。
EM_GETLINECOUNT 0 0 取得多行編輯框之行數。若為 1,表示編輯框尚未輸入資料或僅一行資料。
WM_SETTEXT 0 字串位址 設定視窗的標題欄。如果改視窗的類別是『edit』,則修改編輯框內容。如果該視窗的類別是『button』,則修改按鈕上的文字。如果該視窗的類別是『static』,則修改字串……

按下按鈕,發出 WM_COMMAND 訊息

程式第 232 行到第 241 行是處理使用者按下控制元件的程式。參照第六章的資料,假如使用者以滑鼠點選按鈕,那麼系統會發出 WM_COMMAND 傳遞訊息給父視窗。此時在 WM_COMMAND 訊息內的 wParam 較低的 16 位元是按鈕的識別號碼,較高的 16 位元是使用者的動作,這個動作常見的有兩種:一種是 BN_CLICKED,表示使用者層按下過這個按鈕;另一種是 BN_DOUBLECLICKED,表示使用者曾雙擊過此按鈕。

這段程式實作於第 235 行與第 236 行。小木偶先檢查是否按下按鈕 ( wParam 較低的 16 位元是否為 BN_CLICKED ),再檢查按下那一個按鈕,IDC_Calc 或 IDE_Exit。

第 239 行,這一行是處理使用者按下『離開』控制元件,這一段程式其實和使用者由選單選擇離開是相同的。所以為了避免重複,小木偶用了一個技巧,即程式對自己發出 WM_COMMAND 訊息,而此訊息中的 wParam 及 lParam 分別填上離開選單的識別號碼以及零,那麼在進入下一次訊息迴圈時,程式會自行捕捉到 WM_COMMAND,而就好像使用者由選單選擇『離開』一樣。


到第八章回到首頁到第十章