Ch 10 對話盒(3)


模式對話盒與非模式對話盒

使用者在使用軟體時,常常會經由選單選擇某一功能後,而彈出一個對話盒,做更精細的選擇。例如,使用者想存檔時,一般在選單選擇『檔案』→『存檔』之後,會跳出一個對話盒,要求使用者輸入檔名,等輸入完成之後再按下『存檔』按鈕。假如這個存檔的對話盒沒有關閉,使用者無法使用父視窗上的任何功能。

但是另有一種對話盒,可以在未關閉的情形下,仍能對父視窗進行操作。例如,要在文件中搜尋特定字串,按下選單中的『Search』後,會彈出一個對話盒要求使用者輸入要尋找的字串,在輸入完畢之後按下『尋找』按鈕開始尋找。

很明顯的,上述這兩種對話盒不太一樣。以程式設計師的觀點來看,對話盒可分為兩種,即模式對話盒 ( modal dialog box ) 與非模式對話盒 ( modeless dialog box )。模式對話盒是指使用者必須將對話盒關閉後才能繼續操作原來的視窗,就像前面提到的要存檔時彈出的對話盒。而非模式對話盒則是不須關閉對話盒也能對原視窗操作,例如前面所提到的尋找字串的對話盒。

前一章所建立的對話盒是屬於非模式對話盒,從前一章的程式可以看出來,建立非模式對話盒是利用 CreateDialogParam API,摧毀非模式對話盒則使用 DestroyWindow API。又因為非模式對話盒的訊息迴圈就是原視窗的訊息迴圈,所以非模式對話盒不會抑制原來視窗的訊息,這也就是為何非模式對話盒不用關閉,也能操作原視窗,也正因為如此,常常得在訊息迴圈中加上 IsDialogMessage 來區別使用者的動作是對原視窗還是對對話盒。

模式對話盒是利用 DialogBoxParam API 建立的,結束非模式對話盒則是採用 EndDialog。當建立模式對話盒時,系統會為這個對話盒建立一個訊息迴圈,此訊息迴圈把所接收到的訊息發送給系統內部的對話盒管理器,對話盒管理器再呼叫程式的對話盒函式 ( dialog box procedure )。對話盒函式的地位相當於視窗函式,它也是一種『call back』函式,專門處理傳遞給對話盒的訊息,這些訊息經過對話盒函式處理後,最後仍必須返回系統,必須有一個傳回值給系統做為處理的依據,已經處理過的訊息傳回值應設為 TRUE,沒有處理過的傳回值應設為 FALSE,系統會自行處理沒有處理過的訊息,也因為這已回到系統中,所以不必在對話盒函式中呼叫 DefWindowProc API。

整理上述原理,欲建立一個模式對話盒,須呼叫 DialogBoxParam API,以及建立對話盒函式。結束時,得在處理 WM_CLOSE 訊息中呼叫 EndDialog API 以結束模式對話盒。至於建立訊息迴圈、註冊視窗、呼叫 DefWindowProc API 處理不感興趣的訊息等等步驟,都因為包含在系統內部,均省略了,故而減少了許多程式碼,對程式設計師來說,方便不少。

DialogBoxParam API

DialogBoxParam 的原型是:

int DialogBoxParam(
    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
   );

其中 hInstance、hWndParent 分別是父視窗的模組代碼及視窗代碼。lpTemplateName 對話盒面板的名稱位址,這個名稱必須和資源檔中的對話盒面板名稱相同。lpDialogFunc 是對話盒函式之位址,對話盒函式將會處理使用者對對話盒動作所產生的訊息。dwInitParam 是對話盒函式的初始值,當對話盒產生時,系統會對對話盒發出 WM_INITDIALOG 訊息,此訊息中的 lParam 會用 dwInitParam 代替。

EndDialog API

當使用者按下離開按鈕結束對話盒視窗時,對話盒呼叫 EndDialog 來結束對話盒。EndDialog 的原型為

BOOL EndDialog(
    HWND    hDlg,       // handle to dialog box
    int     nResult     // value to return
   );

hDlg 是要關閉的對話盒,nResult 是由對話盒函式返回到系統時的返回值。


再談控制元件

底下小木偶將說明幾種控制元件的用法,包括群組元件、複合元件、捲軸元件等等。

群組控制元件 ( GROUPBOX control )

GROUPBOX 控制元件會在對話盒上產生一個矩形區域,並且用黑線標示起來,此外還可以設定一個標題。一般而言,這個矩形區域圍住數個控制元件,被圍住的控制元件通常都是屬於同一類的或者是共同達成某個目的的。例如下圖中的 Word Pad 『檢視』『選項』對話盒堙A包含兩個群組控件。左側的是處理換行方式,包含三個圓形按鈕,使用者只能在這三個按鈕中挑選其中之一。右側的是顯示那幾種工具,有四個檢驗盒控件可供選擇,使用者可任意挑選數個也可以一個都不挑選。

群組控件

小木偶想,大概因為群組控件一般用來包括許多按鈕,所以 Windows 系統也把群組歸類為按鈕控件。一般在對話盒模板中可以用

GROUPBOX    text, id, x, y, width, height [, style [, extended-style]]
或
CONTROL     text, id, "BUTTON", style, x, y, width, height [, extended-style]

其中 text 是群組控件上方的文字,style 應該加上 BS_GROUPBOX,假如您用第一種格式定義,則因為已經使用 GROUPBOX 關鍵字,故可以不加上 BS_GROUPBOX。

GROUPBOX 控件可說是按鈕控件中最特殊的一種,因為它不處理任何鍵盤或滑鼠訊息,也也不送 WM_COMMAND 給父視窗。除了 GROPUBOX 控件之外,按鈕控件還有圓形按鈕控件 ( RADIOBUTTON control )、檢查盒控件 ( CHECKBOX control ) 以及前一章所說的下壓式按鈕 ( PUSHBUTTON control ) 等等。

圓形按鈕控制元件 ( RADIOBUTTON control )

圓形按鈕如上圖所示。一般而言,圓形按鈕是用來在眾多選項中選擇其中之一用的,使用者不能選兩個或兩個以上,也不能不選擇。當使用者選定眾多圓形按鈕其中之一時,程式會在圓形按鈕中顯示一圓點,就如同上述 WordPad 的『選項』對話盒中的『自動換行』中,使用者已選定『依視窗自動換行』的控件,而其他控件的圓點則不顯示。要使用圓形按鈕時,可先在對話盒模板定義它,格式是

CONTROL     text, id, "BUTTON", style, x, y, width, height [, extended-style]

圓形按鈕的風格 ( style ) 有兩種,BS_RADIOBUTTON 與 BS_AUTORADIOBUTTON。如果您用 BS_RADIOBUTTON 風格,系統並不會自動地把已選定的圓形按鈕中的圓點畫上,也不會把未選定的圓點清除掉,必須自行負責。這樣是一件很麻煩的事,尤其是圓形按鈕控件數量多時,因此 BS_AUTORADIOBUTTON 就孕育而生了,它會自動地畫上已選定控件的圓點並清除未選定控件的圓點。

您也可以用較簡潔的方式描述圓形按鈕控件:

RADIOBUTTON     text, id, x, y, wdith ,height , [ style ]
AUTORADIOBUTTON text, id, x, y, wdith ,height , [ style ]

其他常用的風格還有 WS_TABSTOP、WS_DISABLED、WS_GROUP 及 BS_TEXTLEFT。BS_TEXTLEFT 風格會使按鈕文字在圓點的左方。不過沒有 BS_RIGHTTEXT 風格,因為預設的就是如此排列。假如沒有指定任何風格,內定的風格是 WS_TABSTOP。

具有 WS_TABSTOP 風格的控件能夠讓使用者按下鍵盤上的 TAB 鍵或 Shft-TAB 鍵來獲得輸入焦點。意思是,使用者可以按下 TAB 鍵或 Shft-TAB 鍵,使輸入焦點輪流在每個具有 WS_TABSTOP 風格的控鍵上停住。

父視窗可以呼叫 SendMessage API 對圓形按鈕發出訊息。SendMessage 的原型是:

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

此時 Msg 參數常用的有 BM_SETCHECK、BM_GETCHECK、BM_CLICK 等訊息。BM_SETCHECK 是設定圓形按鈕或檢驗盒按鈕是否被選取,若想設為被選取則 wParam 為 BST_CHECKED,若想設為不被選取則 wParam 為 BST_UNCHECKED。

當 Msg 為 BM_GETCHECK 時,表示取得圓形按鈕或檢驗盒按鈕被選取的情形,wParam、lParam 皆設為零,被選取的情形由返回時 EAX 表示,若 EAX 為 BST_CHECKED 表示圓形按鈕或檢驗盒按鈕被選取,若為 BST_UNCHECKED 表示沒有被選取。

當 Msg 為 BM_CLICK 時,可以模擬使用者對按鈕按下滑鼠 ( 此訊息也可適用於下壓式按鈕 ),這個訊息會使按鈕先後收到 WM_LBUTTONDOWN 和 WM_LBUTTONUP 訊息,並且使父視窗收到通知碼為 BN_CLICKED 的 WM_COMMAND 訊息。使用 BM_CLICK 時,wParam、lParam 應設為零,並且必須在對話盒是處於正在作用中。

圓形按鈕一般是藉著 WM_COMMAND 訊息告訴父視窗發生了什麼事件,如前所述,lParam 是按鈕代碼,wParam 的低字組是按鈕識別碼,wParam 的高字組是通知碼。常用的通知碼有 BN_CLICK ( 表示使用者對按鈕按一下滑鼠鍵 )、BN_DBLCLK ( 使用者對按鈕雙擊滑鼠鍵,和 BN_DOUBLECLICKED 相同 )。

COMBOBOX 控制元件

複合控制元件 ( combo box control ) 是編輯框與列表兩種控制元件的結合。讓小木偶舉個例子說明複合元件,下圖是 IE 瀏覽器在開啟舊檔時所呈現的對話盒,

複合元件
其中在『開啟(O)︰』靜態元件的右邊為就是複合元件,當使用者按下複合元件右邊的下拉按鈕時,會出現一張清單,這時複合元件變成下圖︰
清單中含有許多字串選擇項可供選擇,使用者可以選擇其中一個,有時也可以在編輯框堛蔣翕擗J,有時則不能由編輯框中輸入。有時複合元件的清單是直接顯示而不須由下拉按鈕操縱。這些全部可以由程式設計師以風格 ( style ) 來指定。

使用複合元件時,首先得在對話盒面板堜w義複合元件,其格式和前一章所述略有不同,其格式如下:

COMBOBOX  ID, x, y, width, height [,styles]
或
CONTROL   text, ID, "COMBOBOX", style, x, y, width, height [, extended-style]

和前一章的格式相比,複合元件並沒有 "text" 這一欄,即使您用第二種完整的格式表示預設的 "text",似乎也會被忽略,而其他欄位則是相同。複合元件常用的風格 ( style ) 如下表:

風格 說   明
CBS_SIMPLE 在任何時候都顯示清單。
CBS_DROPDOWN 只有在使用者按下右邊的下拉鈕時,才顯示清單
CBS_DROPDOWNLIST 和 CBS_DROPDOWN 類似,但是使用此風格時,使用者無法修改編輯框內的資料,僅能由清單中選擇。
CBS_SORT 使清單內字串排序
CBS_AUOTHSCROLL 在編輯框內的輸入文字超過編輯框顯示範圍時,自動水平捲動,若沒有指定此風格,超過範圍後就不能在輸入資料。
WS_VSCROLL 使清單內字串能有垂直捲軸,若沒有指定此風格,超過範圍的字串就看不見而無法選擇。
WS_GROUP  
WS_TABSTOP 使用者按下鍵盤上的 TAB 鍵數次後,能夠變成輸入焦點。
WS_DISABLED 指定此風格後,此視窗就無作用而失效

上面的字首,CBS_是 combobox style 的縮寫,WS_ 是 window style 的縮寫。假如都沒有指定任何複合元件風格的話,內定的風格是 CBS_SIMPLE | WS_TABSTOP。

那麼要怎樣才能設定複合元件內的選擇項呢?答案是用 SendMessage API 把字串選擇項當做訊息中的一部份而傳給複合元件。前面曾提到,父視窗可以和子視窗互相傳遞訊息,靠 SendMessage。

當程式設計師要把選擇項傳給複合元件時,SendMessage 中,hWnd 參數是複合元件的視窗代碼,Msg 設為 CB_ADDSTRING 表示此訊息是在複合元件的清單中加入字串,wParam 無作用設為 NULL,lParam 是字串位址,此字串以零結尾,這樣就可以把選擇項字串傳給複合控制元件。小木偶把常用的,而且可以由父視窗傳給複合元件的訊息整理如下表:

訊息 說   明
CB_ADDSTRING 把選擇項字串填入複合控件,此時 wParam 設為 NULL,lParam 為字串位址,此字串須以零結尾。
CB_DELETESTRING 由複合控件的清單中刪除選擇項字串。此時 wParam 為要刪除字串的第幾項,由零開始。而 lParam 無作用設為零。
CB_DIR 把目前路徑下符合指定屬性與檔名的檔案名稱加入複合控件中。屬性由 wParam 指定。檔名由 lParam 所指向位址的字串決定,此字串可包含萬用字元且必須以零結尾。
CB_FINDSTRING 使複合元件清單內的選擇項字串和指定字串比對,若完全相同或指定字串為某個選擇項的前面數字元,則傳回該選擇項字串在清單的第幾個 ( 從零開始 ),若找不著則傳回 CB_ERR。wParam 表示由清單的第幾個選擇項開始比較,lParam 為指定字串位址,此字串須以零結尾。
CB_GETCOUNT 求得清單內的選擇項數。wParam、lParam 均無作用,設為零。
CB_GETCURSEL 傳回清單內被選擇的字串是在清單內的第幾個 ( 由零開始 )。若使用者還沒有選好,則傳回 CB_ERR。wParam、lParam 均無作用,設為零。
CB_GETLBTEXT 把某個選擇項字串複製到指定位址,wParam 是第幾個選擇項,lParam 是指定的位址。
CB_GETLBTEXTLEN 傳回某個選擇項字串長度,wParam 是第幾個選擇項,lParam無作用,設為零。
CB_RESETCONTENT 清除清單所有選擇項。wParam、lParam 均無作用,設為零。
CB_SELECTSTRING 與 CB_FINDSTRING 類似,但是 CB_SELECTSTRING 若尋找成功還會在編輯框內顯示找著的選擇項字串。
CB_SETCURSEL 選定清單內的選擇項,該選項會以高亮度顯示,原高亮度顯示的選擇項則恢復原狀。wParam 指定第幾個選擇字串被選定,lParam無作用,設為零。

父視窗可以對複合控制元件發出上表中的各種訊息,複合控制元件也可以對父視窗回應訊息。絕大部份的控制元件都是以 WM_COMMAND 訊息將訊息回應給父視窗,少有例外,複合子控件也是如此。當複合子控件對父視窗回應 WM_COMMAND 時,lParam 是子視窗代碼,wParam 的低字組是子視窗識別號碼,wParam 的高字組則是使用者對子控件的動作 ( 通知碼 ),對複合子控件來說,常用的通知碼有:

  1. CBN_DBLCLK:使用者從複合元件的清單中選定某選擇字串。
  2. CBN_DROPDOWN:使用者按下複合元件的下拉鈕。
  3. CBN_EDITCHANGE:使用者修改複合元件的編輯框內容。
  4. CBN_ERRSPACE:清單內的選擇項太多,以致記憶體不足。
  5. CBN_KILLFOCUS:複合元件失去輸入焦點,即不在作用中。
  6. CBN_SELCHANGE:使用者變更清單內已選定的選擇項字串。
  7. CBN_SELENDOK:使用者選定一個清單內的選項,或選定一個清單內的選項並且關閉清單。
  8. CBN_SETFOCUS:複合元件獲得輸入焦點。

SCROLLBAR 控制元件

捲軸元件是一塊矩形區域,兩端各有一個箭頭,中間有一個操縱桿 ( thumb ),可以在有陰影的滑動桿 ( shaded shaft ) 上以滑鼠拖曳,如下圖所示︰

捲軸元件和一般視窗的捲軸 ( 此捲軸稱為視窗捲軸,它位於視窗的右邊或下面邊,當顯示的資料量太大,螢幕無法容內時,使用者常常移動視窗捲軸,一次閱讀一部分的資料 ) 雖然在外觀上一樣,但是其性質卻是不同,捲軸元件本身就是視窗,具有視窗的所有特性;而視窗捲軸則是視窗的一部份,不要混淆了,小木偶在第十三章再詳談視窗捲軸。建立捲軸元件有數種方式,一般是在對話盒面板中描述,其格式為︰

SCROLLBAR id, x, y, width, height [, style [, extended-style]]
或
CONTROL   text, id, "SCROLLBAR", style, x, y, width, height [, extended-style]

捲軸元件也和複合元件一樣,如果您採用第一種方式定義,則不可出現 text,如果採用第二種完整的方法定義,卻必須設定 text 欄位,但是卻會被忽略。捲軸元件常用的風格,多以 SBS_ 為字首,SBS_ 是 scrollbar style 之縮寫,下表列出一些捲軸元件的風格︰

風格 說   明
SBS_HORZ 建立水平捲軸控制元件。
SBS_VERT 建立垂直捲軸控制元件。

其他可用的風格還有 WS_TABSTOP、WS_GROUP、WS_DISABLED 等等。若省略,內定為 SBS_HORZ。

定義好捲軸控制元件後,還得在程式中設定捲軸操縱桿 ( thumb ) 可移動的範圍及操縱桿的起始位置,這些工作一般是在對話盒剛建立之初進行,也就是收到 WM_INITDIALOG 訊息時完成。底下說明對捲軸控件常用的 API:

SetScrollRange API

設定操縱桿的範圍用 SetScrollRange API,這個 API 可以用在捲軸控件上,也可以用在視窗捲軸上,所以指定參數時不要混淆,否則無法達到效果。SetScrollRange 的原型是

BOOL SetScrollRange(
    HWND    hWnd,       // handle of window with scroll bar
    int     nBar,       // scroll bar flag
    int     nMinPos,    // minimum scrolling position
    int     nMaxPos,    // maximum scrolling position
    BOOL    bRedraw     // redraw flag
   );

hWnd 是指捲軸控件的視窗代碼或者含有視窗捲軸的視窗代碼。nBar 是捲軸種類,有三種可以使用:

  1. SB_CTL:捲軸控件,此時 hWnd 是指捲軸控件的視窗代碼。
  2. SB_HORZ:水平的視窗捲軸,此時 hWnd 是指含有此視窗捲軸的視窗代碼。
  3. SB_VERT:垂直的視窗捲軸,此時 hWnd 是指含有此視窗捲軸的視窗代碼。

nMinPos、nMaxPos 分別是最小值和最大值。bRedraw 用來表示更動操縱桿後是否要重新繪製,TRUE 表示要重繪,FALSE 表示不須重繪,重新繪製的動作不須由程式設計師撰寫,系統會自動繪製,節省了許多精力。如果執行成功,SetScrollRange 傳回 TRUE,否則傳回 FALSE。

SetScrollPos API

這個 API 是設定操縱桿位置,原型是:

int SetScrollPos(
    HWND    hWnd,       // handle of window with scroll bar
    int     nBar,       // scroll bar flag
    int     nPos,       // new position of scroll box
    BOOL    bRedraw     // redraw flag
   );

hWnd、nBar、bRedraw 的意義和 SetScrollRange 的參數一樣。nPos 是指新的操縱桿位置,必須介於 SetScrollRange 參數的 nMinPos 和 nMaxPos 之間。SetScrollPos 的傳回值存於 EAX,若執行成功,則 EAX 為舊的操縱桿位置,若失敗則 EAX 為零。

GetScrollPos API

GetScrollPos 是用來取得操縱桿位置,其原型為:

int GetScrollPos(
    HWND    hWnd,       // handle of window with scroll bar
    int     nBar        // scroll bar flags
   );

hWnd 和 nBar 和前面都相同。若 GetScrollPos 執行成功,則 EAX 為操縱桿位置,若失敗則 EAX 為 FALSE。

前述一般控制元件發出 WM_COMMAND 訊息給父視窗,但捲軸控制元件卻是個例外。捲軸控件不發出 WM_COMMAND 而是發出 WM_HSCROLL 與 WM_VSCROLL,其捲軸是水平抑或垂直而定;標準視窗上如果有視窗捲軸,使用者對視窗捲軸動作時,也會發出 WM_HSCROLL 與 WM_VSCROLL。底下僅就 WM_HSCROLL 訊息說明。

當 WM_HSCROLL 訊息傳給父視窗時,如果程式要處理這個訊息,最後要傳回 TRUE,否則傳回 FALSE。WM_HSCROLL 中,若 lParam 為 NULL,則表示此訊息由標準視窗的視窗捲軸發出;若不為 NULL,表示由捲軸控件發出,其數值為捲軸控件的視窗代碼。wParam 的高字組是捲軸操縱桿位置,低字組是是使用者對捲軸的動作,常用的有下面數種;

  1. SB_LEFT:操縱桿被移到最左邊。
  2. SB_RIGHT:操縱桿被移到最右邊。
  3. SB_LINELEFT:操縱桿被向左移一格。
  4. SB_LINERIGHT:操縱桿被向右移一格。
  5. SB_THUMBPOSITION:使用者以滑鼠拖放操縱桿。

範例:UNIT_CVT

底下小木偶介紹一個例子,UNIT_CVT.EXE,用它來說明模式對話盒的建立與更多的子控制元件的使用,包含複合元件、捲軸元件 ( scroll bar control )、群組元件 ( group box control )。UNIT_CVT.EXE 是一個長度、質量、溫度的公制與英制單位轉換程式,當程式執行時,顯示畫面如下圖:

主視窗

如果使用者按下『長度』選單後,會出現一個模式對話盒,如下圖:

一個模式對話盒:長度換算對話盒
,其中『選擇換算方式』是群組元件,包含『英制換算成公制單位』和『公制換算成英制單位』兩個圓形按鈕 ( AUTORADIOBUTTON )。如果按下『質量』選單,則出現的模式對話盒如下圖:
一個模式對話盒:質量換算對話盒
,其中『70』的控制元件就是一個複合元件。如果按下『溫度』選單出現的對話盒如下圖:
一個模式對話盒:溫度換算對話盒
,包含一個捲軸元件。

UNIT_CVT.ASM 內容如下:

        .386
        .model  flat,stdcall
        option  casemap:none

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

IDM_Exit        equ     2001    ;011 選單識別碼開始
IDM_L           equ     2002
IDM_M           equ     2003
IDM_T           equ     2004
IDC_MText1      equ     2101    ;015 控制元件識別碼開始
IDC_MText2      equ     2102
IDC_MCB         equ     2103
IDC_MP2K        equ     2104
IDC_MK2P        equ     2105
IDC_MCvt        equ     2106
IDC_MExit       equ     2107
IDC_MOpt        equ     2108
IDC_TSB         equ     2201
IDC_TText       equ     2202
IDC_TOpt        equ     2203
IDC_TF2C        equ     2204
IDC_TC2F        equ     2205
IDC_TExit       equ     2206
IDC_LFeet       equ     2301
IDC_LText1      equ     2302
IDC_LInch       equ     2303
IDC_LText2      equ     2304
IDC_LCm         equ     2305
IDC_LText3      equ     2306
IDC_LCvt        equ     2307
IDC_LExit       equ     2308
IDC_LOpt        equ     2309
IDC_LI2C        equ     2310
IDC_LC2I        equ     2311
nSBMin          equ     0       ;040 捲軸控制元件的最小值
nSBMax          equ     150     ;041 捲軸控制元件的最大值
nSBMiddle       equ     (nSBMax+nSBMin)/2       ;042捲軸控制元件的預設值

;函式原型宣告
WndProc         proto   :HWND,:UINT,:WPARAM,:LPARAM
LenDlgProc      proto   :HWND,:UINT,:WPARAM,:LPARAM
MassDlgProc     proto   :HWND,:UINT,:WPARAM,:LPARAM
TempDlgProc     proto   :HWND,:UINT,:WPARAM,:LPARAM
LenConvert      proto   :HWND
MassConvert     proto   :HWND
TempConvert     proto   :HWND
CvtST0_to_Str   proto   :LPSTR

;***************************************
        .DATA

UnitCvtClass    db          'UnitCvtClass',0
AppName         db          '單位換算',0
ExitText        db          '是否退出此程式?',0
ExitTitle       db          '詢問',0
IconName        db          'UnitCvtIcon',0
MenuName        db          'UnitCvtMenu',0     ;062 選單名稱
LenDlgName      db          'LenDlg',0          ;063 對話盒名稱
MassDlgName     db          'MassDlg',0
TempDlgName     db          'TempDlg',0
wc              WNDCLASSEX  <?>
msg             MSG         <?>
hInstance       HINSTANCE   ?
hMenu           HMENU       ?
hwnd            HWND        ?
hwnd_TSB        HWND        ?
nSBPos          dw          ?

bOption         db          7
;bit0:1英吋換算成公分  bit1:1英磅換算成公斤  bit2:1華氏換算成攝氏
;      0公分換算成英吋        0公斤換算成英磅        0攝氏換算成華氏

buffer          db          64 dup (0)
ten             dw          10
factor_c2f_diff dw          32          ;080 ℉=1.8℃+32
factor_c2f      dd          1.8
factor_i2c      dd          2.54        ;082 一英吋=2.54公分
factor_p2k      dd          0.454       ;083 一英磅=0.454公斤
inch_per_foot   dw          12          ;084 一英呎=12英吋
combobox_str    db          ' 70',0,' 75',0,' 80',0,' 85',0
                db          ' 90',0,' 95',0,'100',0,'105',0
                db          '110',0,'115',0,'120',0,'125',0
                db          '130',0,'135',0,'140',0,'145',0
strPound        db          '英磅'
strKg           db          '公斤'
strEqual        db          '='
strDC           db          '℃'
strDF           db          '℉'
;***************************************
        .CODE
;---------------------------------------
start:  invoke  GetModuleHandle,NULL    ;097 程式開始
        mov     hInstance,eax
        mov     wc.cbSize,SIZEOF WNDCLASSEX
        mov     wc.style,CS_HREDRAW or CS_VREDRAW
        mov     wc.lpfnWndProc,offset WndProc           ;101 視窗函式位址
        mov     wc.cbWndExtra,DLGWINDOWEXTRA
        mov     wc.hInstance,eax
        mov     wc.hbrBackground,COLOR_BTNFACE+1 
        mov     wc.lpszMenuName,offset MenuName
        mov     wc.lpszClassName,offset UnitCvtClass
        invoke  LoadIcon,hInstance,offset IconName      ;107 取得圖示
        mov     wc.hIcon,eax
        mov     wc.hIconSm,eax
        invoke  LoadCursor,NULL,IDC_ARROW
        mov     wc.hCursor,eax
        invoke  LoadMenu,hInstance,offset MenuName      ;112 取得選單
        mov     hMenu,eax
        invoke  RegisterClassEx,offset wc               ;114 註冊視窗類別
        invoke  CreateWindowEx,NULL,offset \            ;115 建立主視窗
                UnitCvtClass,offset AppName,\
                WS_OVERLAPPEDWINDOW,0,0,300,100,0,hMenu,hInstance,NULL        
        mov     hwnd,eax
        invoke  ShowWindow,hwnd,SW_SHOWNORMAL
        invoke  UpdateWindow,hwnd
.while TRUE
        invoke  GetMessage,offset msg,NULL,0,0
.break .if (!eax)
        invoke  TranslateMessage,offset msg
        invoke  DispatchMessage,offset msg
.endw
        mov     eax,msg.wParam
        invoke  ExitProcess,eax         ;128 結束程式
;---------------------------------------
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
;136 以下是處理使用者按下某個選單的程式
    .if ax==IDM_L
        invoke  DialogBoxParam,hInstance,offset \       ;138 按下『長度』選單
                LenDlgName,hWnd,addr LenDlgProc,NULL
    .elseif ax==IDM_M
        invoke  DialogBoxParam,hInstance,offset \       ;141 按下『質量』選單
                MassDlgName,hWnd,offset MassDlgProc,NULL
    .elseif ax==IDM_T
        invoke  DialogBoxParam,hInstance,offset \       ;144 按下『溫度』選單
                TempDlgName,hWnd,offset TempDlgProc,NULL
    .elseif ax==IDM_Exit
        invoke  MessageBox,hWnd,offset ExitText,offset \;147 按下『離開』選單
                ExitTitle,MB_YESNO or MB_ICONQUESTION
      .if eax==IDYES
        invoke  DestroyWindow,hWnd
      .endif
    .endif
  .endif
.else
        invoke  DefWindowProc,hWnd,uMsg,wParam,lParam
        ret
.endif
        xor     eax,eax 
        ret
WndProc endp    ;160 視窗函式結束
;---------------------------------------
LenDlgProc      proc    hWnd:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM
.if uMsg==WM_INITDIALOG         ;163 初始化長度換算對話盒
        invoke  GetDlgItem,hWnd,IDC_LI2C  ;164 取得『英制轉換成公制單位』的視窗代碼
        invoke  SendMessage,eax,BM_SETCHECK,BST_CHECKED,NULL    ;165 預設此子視窗
        or      bOption,1
        invoke  GetDlgItem,hWnd,IDC_LFeet
        invoke  SetFocus,eax
.elseif uMsg==WM_CLOSE          ;169 關閉長度換算對話盒
        invoke  EndDialog,hWnd,NULL
.elseif uMsg==WM_COMMAND        ;171 按下控制元件
        mov     eax,wParam
        mov     edx,eax
        shr     edx,16
  .if dx==BN_CLICKED            ;175 按下按鈕
    .if ax==IDC_LCvt            ;176 按下『換算』按鈕
        invoke  LenConvert,hWnd
    .elseif ax==IDC_LExit       ;178 按下『離開』按鈕
        invoke  EndDialog,hWnd,NULL
    .elseif ax==IDC_LI2C        ;180 按下『英制轉換成公制單位』按鈕
        or      bOption,1
    .elseif ax==IDC_LC2I        ;182 按下『公制轉換成英制單位』按鈕
        and     bOption,0feh
    .endif
  .endif
.else
        mov     eax,FALSE       ;187 未處理的訊息傳回 FALSE
        ret
.endif
        mov     eax,TRUE        ;190 已處理過的訊息傳回 TRUE
        ret
LenDlgProc      endp
;---------------------------------------
MassDlgProc     proc    hWnd:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM
        LOCAL   hwnd_MCB:HWND
.if uMsg==WM_INITDIALOG         ;196 初始化『質量換算』對話盒
        invoke  GetDlgItem,hWnd,IDC_MP2K
        invoke  SendMessage,eax,BM_SETCHECK,BST_CHECKED,NULL
        or      bOption,2
        invoke  GetDlgItem,hWnd,IDC_MCB ;200 取得複合元件視窗代碼
        mov     hwnd_MCB,eax
        mov     ecx,10h
        mov     edx,offset combobox_str ;203 EDX 指向要填入複合元件的字串位址
nxt:    push    ecx
        push    edx
        invoke  SendMessage,hwnd_MCB,CB_ADDSTRING,NULL,edx
        pop     edx
        pop     ecx
        add     edx,4
        loop    nxt
        invoke  SendMessage,hwnd_MCB,CB_SETCURSEL,ecx,ecx
        invoke  GetDlgItem,hWnd,IDC_MCB
        invoke  SetFocus,eax
.elseif uMsg==WM_CLOSE
        invoke  EndDialog,hWnd,NULL     ;214 摧毀質量換算對話盒
.elseif uMsg==WM_COMMAND
        mov     eax,wParam
        mov     edx,eax
        shr     edx,16
  .if dx==BN_CLICKED                    ;220 使用者按下按鈕
    .if ax==IDC_MCvt                    ;221 按下『換算』按鈕
        invoke  MassConvert,hWnd
    .elseif ax==IDC_MExit               ;223 按下『離開』按鈕
        invoke  EndDialog,hWnd,NULL
    .elseif ax==IDC_MP2K                ;225 按下『英磅換算成公斤』按鈕
        or      bOption,2
    .elseif ax==IDC_MK2P                ;227 按下『公斤換算成英磅』按鈕
        and     bOption,0fdh
    .endif
  .endif
.else
        mov     eax,FALSE       ;232 未處理的訊息傳回 FALSE
        ret
.endif
        mov     eax,TRUE        ;235 已處理過的訊息傳回 TRUE
        ret
MassDlgProc     endp
;---------------------------------------
TempDlgProc     proc    hWnd:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM
.if uMsg==WM_INITDIALOG         ;240 初始化『溫度換算』對話盒
        invoke  GetDlgItem,hWnd,IDC_TF2C
        invoke  SendMessage,eax,BM_SETCHECK,BST_CHECKED,NULL
        or      bOption,4
        invoke  GetDlgItem,hWnd,IDC_TSB
        mov     hwnd_TSB,eax
        invoke  SetScrollRange,eax,SB_CTL,nSBMin,nSBMax,TRUE
        invoke  SetScrollPos,hwnd_TSB,SB_CTL,nSBMiddle,TRUE
        mov     nSBPos,nSBMiddle
        invoke  SetFocus,hwnd_TSB
.elseif uMsg==WM_CLOSE          ;250 關閉『溫度換算』對話盒
        invoke  EndDialog,hWnd,NULL
.elseif uMsg==WM_COMMAND        ;252 由按鈕傳來的訊息
        mov     eax,wParam
        mov     edx,eax
        shr     edx,16
  .if dx==BN_CLICKED            ;256 按下按鈕
    .if ax==IDC_TExit           ;257 按下『離開』按鈕
        invoke  EndDialog,hWnd,NULL
    .elseif ax==IDC_TF2C        ;259 按下『華氏溫度換算成攝氏溫度』按鈕
        or      bOption,4
    .elseif ax==IDC_TC2F        ;261 按下『攝氏溫度換算成華氏溫度』按鈕
        and     bOption,0fbh
    .endif
  .endif
.elseif uMsg==WM_HSCROLL        ;265 由捲軸控制元件傳來的訊息
        mov     edx,lParam
  .if edx==hwnd_TSB
        mov     eax,wParam
        mov     edx,eax
        shr     edx,16
    .if ax==SB_THUMBPOSITION    ;271 滑動捲軸
        mov     nSBPos,dx
    .elseif ax==SB_RIGHT        ;273 在滑動捲軸按下 End 鍵
        mov     nSBPos,nSBMax
    .elseif ax==SB_LEFT         ;275 在滑動捲軸按下 Home 鍵
        mov     nSBPos,0
    .elseif ax==SB_LINERIGHT    ;277 在滑動捲軸按下鍵盤右鍵
        inc     nSBPos
      .if nSBPos>nSBMax
        mov     nSBPos,nSBMax
      .endif
    .elseif ax==SB_LINELEFT     ;282 在滑動捲軸按下鍵盤左鍵
      .if nSBPos!=nSBMin
        dec     nSBPos
      .endif
    .endif
        invoke  SetScrollPos,hwnd_TSB,SB_CTL,nSBPos,TRUE        ;287 重新設定捲軸位置
        invoke  TempConvert,hWnd;288 轉換
  .endif
.else
        mov     eax,FALSE
        ret
.endif
        mov     eax,TRUE 
        ret
TempDlgProc     endp
;---------------------------------------
;把 FPU 的 ST0 變成 ASCIIZ 字串(僅一位小數) ;298
;輸入-Addr_Str:字串位址
;      ST0:要轉換的數字
;輸出-EAX:字串之下一位址
CvtST0_to_Str   proc    uses edi Addr_Str:LPSTR
        LOCAL   pBCD:TBYTE
        LOCAL   bIfZero:BYTE
;bIfZero 是為了印出的格式所設計,例如當數值為 0100 時,第一
;個零不必印出來,但是第二、三個零則必須印出來,當出現第一個
;非零阿拉伯數字時,bIfZero=1,表示以後出現的零要顯示出來
        mov     edi,Addr_Str    ;308 EDI=字串位址
        lea     ecx,pBCD        ;309 ECX=壓縮的 BCD 位址
        mov     edx,ecx
        fimul   ten
        fbstp   pBCD
        add     ecx,9           ;313 由高位址開始
        mov     al,ss:[ecx]     ;314 檢查正負號
.if al==80h
        mov     ax,0d0a1h       ;316 '-'字串
        stosw
.endif
        dec     ecx
        mov     bIfZero,0       ;320 先假設高位數為零
n_dgt:  mov     al,ss:[ecx]
.if al!=0
  .if bIfZero==0        ;323 若 bIfZero=0,表示之前的高位數都為零
        test    al,0f0h ;324 若 bIfZero=0 且 AL不為零,則先檢查 AL
        jnz     h_nz    ;325 高位元是否為零,若為零,則低位元必不
        add     al,30h  ;326 為零,AL 加上 30H 後存入
        stosb
  .else
h_nz:   mov     ah,al
        shr     al,4
        and     ah,0fh
        add     ax,3030h
        stosw
  .endif
        mov     bIfZero,1
.else
  .if bIfZero!=0        ;337 若 AL=0,則檢查 bIfZero 是否為零
        jmp     h_nz    ;338 若 bIfZero 不為零且 AL=0,表示此零是在高位
  .endif                ;339 數有非零阿拉伯數字出現之後,須存入於字串中
.endif
        dec     ecx
        cmp     ecx,edx
        ja      n_dgt
        mov     al,[ecx]
        mov     ah,al
        shr     al,4
        add     al,'0'  ;347 AL=個位數
        stosb
        mov     al,'.'  ;349 AL=小數點
        stosb
        mov     al,ah
        and     al,0fh  ;352 AL=小數點下一位
.if al==0
        dec     edi
.else
        add     al,'0'
        stosb
.endif
        mov     al,0
        stosb
        mov     eax,edi ;361 EAX=字串結尾位址的下一位址
        ret
CvtST0_to_Str   endp    ;363
;---------------------------------------
LenConvert      proc    hWnd:HWND
        LOCAL   inch:DWORD
        LOCAL   cm:DWORD
        LOCAL   cw:WORD
        mov     cl,bOption
        and     cl,1    ;370 CL=1表示英制轉換成公制,否則為公制轉換成英制
.if cl==1
        invoke  GetDlgItemInt,hWnd,IDC_LFeet,NULL,FALSE ;372 EAX=英呎
        mul     inch_per_foot   ;373 英吋=12*英呎+英吋
        mov     inch,eax
        invoke  GetDlgItemInt,hWnd,IDC_LInch,NULL,FALSE
        add     inch,eax
        fild    inch
        fmul    factor_i2c      ;378 公分=2.54*英吋
        invoke  CvtST0_to_Str,offset buffer
        invoke  SetDlgItemText,hWnd,IDC_LCm,offset buffer
.else
        invoke  GetDlgItemInt,hWnd,IDC_LCm,NULL,FALSE
        mov     cm,eax
        fstcw   cw
        mov     ax,cw
        or      cw,0c00h        ;386 要得到英呎,故向零捨去
        push    eax             ;387 保存原控制字組
        fldcw   cw              ;--st --;--st1--;--st2--;
        fild    inch_per_foot   ;  12   ;
        fild    cm              ;  cm   ;  12   ;
        fdiv    factor_i2c      ; inch  ;  12   ;
        fld     st              ; inch  ; inch  ;  12   ;
        fdiv    st,st(2)        ;  ft   ; inch  ;  12   ;
        frndint                 ;int(ft); inch  ;  12   ;
        invoke  CvtST0_to_Str,offset buffer
        invoke  SetDlgItemText,hWnd,IDC_LFeet,offset buffer
        fprem                   ;inch\12;  12   ;
        invoke  CvtST0_to_Str,offset buffer
        invoke  SetDlgItemText,hWnd,IDC_LInch,offset buffer
        pop     eax             ;  12   ;
        fcomp   cm
        mov     cw,ax           ;402 恢復控制字組
        fldcw   cw
.endif
        ret
LenConvert      endp
;---------------------------------------
MassConvert     proc    hWnd:HWND
        LOCAL   nComboBox:DWORD
        invoke  GetDlgItemInt,hWnd,IDC_MCB,NULL,FALSE
        mov     nComboBox,eax   ;411 EAX=複合元件中的數值
        fild    nComboBox
        invoke  CvtST0_to_Str,offset buffer     ;413 填入複合元件之數值
        dec     eax
        push    edi
        mov     edi,eax         ;416 EDI=存入位址
        fild    nComboBox
        mov     al,bOption
        mov     ecx,offset strPound     ;419 先假設英磅換成公斤
        and     al,2                    ;420 AL=2 表示英磅換成公斤
        mov     edx,offset strKg
.if al==2
        fmul    factor_p2k      ;423 1磅=0.454kg
.else
        xchg    ecx,edx         ;425 若公斤換成英磅則交換 ECX、EDX
        fdiv    factor_p2k
.endif
        mov     eax,[ecx]       ;428 填入 ECX 所指的字串
        stosd
        mov     ecx,offset strEqual
        mov     ax,[ecx]        ;431 填入『=』
        stosw
        mov     eax,edi
        pop     edi
        push    edx
        invoke  CvtST0_to_Str,eax       ;436 填入換算後之數值
        pop     edx
        dec     eax
        mov     ecx,[edx]
        mov     [eax],ecx
        add     eax,4
        mov     byte ptr [eax],0
        invoke  SetDlgItemText,hWnd,IDC_MText2,offset buffer
        ret
MassConvert     endp
;---------------------------------------
TempConvert     proc    hWnd:HWND
        LOCAL   nSBPosition:DWORD
        invoke  GetDlgItem,hWnd,IDC_TSB
        invoke  GetScrollPos,eax,SB_CTL ;450 取得捲軸位置
        mov     nSBPosition,eax
        fild    nSBPosition
        invoke  CvtST0_to_Str,offset buffer     ;453 存入捲軸數值
        dec     eax
        push    edi
        mov     edi,eax
        fild    nSBPosition
        mov     al,bOption
        mov     ecx,offset strDF
        and     al,4
        mov     edx,offset strDC
.if al==4
        fisub   factor_c2f_diff ;463 ℃=(℉-32)/1.8
        fdiv    factor_c2f
.else
        xchg    ecx,edx
        fmul    factor_c2f
        fiadd   factor_c2f_diff ;468 ℉=1.8℃+32
.endif
        mov     ax,[ecx]
        stosw
        mov     ecx,offset strEqual
        mov     ax,[ecx]
        stosw
        mov     eax,edi
        pop     edi
        push    edx
        invoke  CvtST0_to_Str,eax
        dec     eax
        pop     edx
        mov     cx,[edx]
        mov     [eax],cx
        inc     eax
        inc     eax
        mov     byte ptr [eax],0
        invoke  SetDlgItemText,hWnd,IDC_TText,offset buffer
        ret
TempConvert     endp
;***************************************
        end     start

UNIT_CVT.RC 內容如下:

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

#define IDM_Exit        2001
#define IDM_L           2002
#define IDM_M           2003
#define IDM_T           2004
#define IDC_MText1      2101
#define IDC_MText2      2102
#define IDC_MCB         2103
#define IDC_MP2K        2104
#define IDC_MK2P        2105
#define IDC_MCvt        2106
#define IDC_MExit       2107
#define IDC_MOpt        2108
#define IDC_TSB         2201
#define IDC_TText       2202
#define IDC_TOpt        2203
#define IDC_TF2C        2204
#define IDC_TC2F        2205
#define IDC_TExit       2206
#define IDC_LFeet       2301
#define IDC_LText1      2302
#define IDC_LInch       2303
#define IDC_LText2      2304
#define IDC_LCm         2305
#define IDC_LText3      2306
#define IDC_LCvt        2307
#define IDC_LExit       2308
#define IDC_LOpt        2309
#define IDC_LI2C        2310
#define IDC_LC2I        2311

UnitCvtMenu     MENU
BEGIN
  MENUITEM      "離開",IDM_Exit
  MENUITEM      "長度",IDM_L
  MENUITEM      "質量",IDM_M
  MENUITEM      "溫度",IDM_T
END

UnitCvtIcon     ICON    unit_cvt.ico

LenDlg  DIALOG  0,0,207,84
STYLE   DS_SETFONT |WS_POPUP |WS_CAPTION |WS_VISIBLE |WS_SYSMENU |WS_THICKFRAME
CAPTION "長度換算"
FONT    9,"新細明體"
BEGIN
  EDITTEXT      IDC_LFeet,5,48,34,12
  LTEXT         "英呎",IDC_LText1,43,50,17,10
  EDITTEXT      IDC_LInch,63,48,34,12
  LTEXT         "英吋=",IDC_LText2,102,50,26,10
  EDITTEXT      IDC_LCm,127,48,50,12
  LTEXT         "公分",IDC_LText3,183,50,21,10
  PUSHBUTTON    "換算",IDC_LCvt,34,64,50,14
  PUSHBUTTON    "離開",IDC_LExit,118,64,50,14
  GROUPBOX      "選擇換算方式",IDC_LOpt,7,0,179,44
  AUTORADIOBUTTON   "英制換算成公制單位",IDC_LI2C,19,12,152,12
  AUTORADIOBUTTON   "公制換算成英制單位",IDC_LC2I,19,27,152,12
END

MassDlg DIALOG  0,0,170,118
STYLE   DS_SETFONT |WS_POPUP |WS_CAPTION |WS_VISIBLE |WS_SYSMENU |WS_THICKFRAME 
CAPTION "質量換算"
FONT    9,"新細明體"
BEGIN
  LTEXT         "選擇數字",IDC_MText1,20,61,66,12
  COMBOBOX      IDC_MCB,91,59,65,70,CBS_DROPDOWNLIST |WS_VSCROLL
  PUSHBUTTON    "換算",IDC_MCvt,23,95,50,14
  PUSHBUTTON    "離開",IDC_MExit,99,96,50,14
  LTEXT         " ",IDC_MText2,20,77,160,12
  GROUPBOX      "選擇換算方式",IDC_MOpt,20,1,131,54
  AUTORADIOBUTTON   "英磅換算成公斤",IDC_MP2K,32,14,102,12
  AUTORADIOBUTTON   "公斤換算成英磅",IDC_MK2P,32,33,102,12
END

TempDlg DIALOG  0,0,164,132
STYLE   DS_SETFONT |WS_POPUP |WS_CAPTION |WS_VISIBLE |WS_SYSMENU |WS_THICKFRAME
CAPTION "溫度換算"
FONT    9,"新細明體"
BEGIN
  SCROLLBAR     IDC_TSB,7,62,150,8
  PUSHBUTTON    "離開",IDC_TExit,96,106,50,14
  LTEXT         " ",IDC_TText,7,83,146,12
  GROUPBOX      "選擇換算方式",IDC_TOpt,10,4,143,51
  AUTORADIOBUTTON   "華氏溫度換算成攝氏溫度",IDC_TF2C,27,17,105,12
  AUTORADIOBUTTON   "攝氏溫度換算成華氏溫度",IDC_TC2F,27,34,105,12
END

UNIT_CVT.MAK 內容如下:

ALL:unit_cvt.exe

unit_cvt.exe:unit_cvt.asm unit_cvt.res
    ml /coff unit_cvt.asm /link /SUBSYSTEM:WINDOWS unit_cvt.res

unit_cvt.res:unit_cvt.rc
    rc unit_cvt.rc

說明

這個程式共約 500 行,對初學者而言算是相當大的程式,所以小木偶僅說明新的部份及主要結構,以免迷失在細節部份。這個程式的程式碼區段可分為主程式 ( 第 97∼128 行)、視窗函式 ( 第 130∼160 行 )、三個對話盒函式 ( LenDlgProc、MassDlgProc、TempDlgProc )、三個轉換副程式 ( LenConvert、MassConvert、TempConvert ) 及 CvtST0_to_Str 副程式 ( 第 298∼363 行 ),共九段程式組成。

主程式第 101 行指定視窗函式,然後產生父視窗再進入訊息迴圈。視窗函式所處理的訊息,大部分是處理使用者按下選單的動作,例如當使用者按下選單的『長度』選單時,就在畫面上產生一個模式對話盒,長度換算對話盒 ( LenDlg ),如前所述,產生模式對話盒用 DialogBoxParam API,而其對話盒函式為 LenDlgProc ( 第 138 行 )。同樣地,當使用者按下『質量』或『溫度』時,也會產生對應的對話盒以及與該對話盒被盒的對話盒函式 ( 第 141∼145 行 )。

在 UNIT_CVT.EXE 程式堙A三個對話盒函式所處理的訊息只有 WM_INITDIALOG、WM_CLOSE、WM_COMMAND 和 WM_HSCROLL 四種而已,這些已被對話盒函式處理過的訊息,在返回系統之前 ( 還記得嗎?對話盒函式也是一種 call back 函式 ) 必須傳回 TRUE 給系統,對尚未處理過的訊息,則傳回 FALSE 交由系統處理。

在 LenDlg 對話盒堙A有兩個圓形按鈕、三個編輯框、兩個下壓式按鈕比較重要,在 LenDlgProc 對話盒函式堙A主要也就是處理這幾個子控件。首先在 WM_INITDIALOG 訊息 ( 即對話盒產生之初 ),先假定換算方式為英制轉換成公制單位,故設定其子控件識別碼所表示的圓形按鈕被選取 ( 第 164∼165 行 )。此外,為了方便小木偶設一個變數 bOption,它用來記錄換算方式。在 WM_INITDIALOG 最後設定編輯框取得輸入焦點。

在 WM_COMMAND 訊息堶n處理的就是兩個圓形按鈕及兩個下壓式按鈕被使用者點選或壓下時的情形。當使用者壓下『換算』下壓式按鈕時,進入 LenConvert 副程式 ( 第 176∼177 行 ),這個副程式會依據 bOption 所記載的換算方式,取得編輯框之內容,做公制的長度單位與英制之間的換算,詳細情形小木偶就稍後再說明。處理使用者選取『英制轉換成公制單位』或『公制轉換成英制單位』比較簡單,因為這兩個圓形按鈕已設為 BS_AUTORADIOBUTTON,所以即使使用者選取另一個,系統能自行處理圓形按鈕中的圓點是否畫上或擦掉,程式只要切換 bOption 即可 ( 第 180∼183 行 )。

LenConvert 副程式首先根據 bOption 的內容判斷『英制轉換成公制單位』或『公制轉換成英制單位』,不管是那一種,都要取得編輯框內的數字然後經過運算後,再於另一個編輯框顯示出來。要取得編輯框內數字,在第八章時用 GetWindowText API,取得字串之後再運算成數值,這很麻煩,這次採用 GetDlgItemInt。

GetDlgItemInt API

GetDlgItemInt 可以把子控件內的文字轉換成整數,記錄在 EAX 媔レ^來,其原型為:

UINT GetDlgItemInt(
 HWND hDlg,         // handle to dialog box
 int  nIDDlgItem,   // control identifier
 BOOL *lpTranslated,// points to variable to receive success/failure indicator
 BOOL bSigned       // specifies whether value is signed or unsigned
   );

hDlg 是對話盒視窗代碼,nIDDlgItem 是想要取得數字的子控件識別碼,lpTranslated 是傳回值的位址,此傳回值只是表示自子控件取得的資料是否能成功的轉換成為數值,若成功則 lpTranslated 所指位址的內容會被設定為 TRUE,否則被設為 FALSE,假如 lpTranslated 設為 NULL 時,表示不傳回轉換是否正確。bSigned 表示是否要取正負號,設為 TRUE 時表示把子控件內文字轉換成有號數,此時若第一個字元為『-』,則 EAX 將傳回二的補數,表示負數;若子控件文字含有非阿拉伯數字字元時,會產生錯誤。bSigned 設為 FALSE 時,表示僅轉換成正數,若子控件含有『-』字元或其他非阿拉伯數字字元時,會產生錯誤。錯誤產生時,EAX 會被設為 FALSE。

假如使用者選擇『英制轉換成公制單位』的話,LenConvert 利用 GetDlgItemInt 取出英呎 ( IDC_LFeet ) 及英吋子控件 ( IDC_LInch ) 的數值,再全部換算成英吋 ( 一英呎等於 12 英吋 ),再換算成公分,最後顯示於公分子控件 ( IDC_LCm )。若使用者選擇『公制轉換成英制單位』,則自公分子控件取出公分數,先轉換成英吋,英吋除以 12,商為英呎數,顯示於英呎子控件;餘為英吋數,顯示於英吋子控件。這段程式實作於第 372 行到第 405 行。

至於 MassConvert 與 TempConvert 副程式的內容大致與 LenConvert 相似。只是在 TempConvert 副程式中,由捲軸控件取出數值時,用 GetScrollPos API。其餘程式應該不難,只是有點兒長,您只要有點耐心,相信可以解決。


到第九章回到首頁到第十一章