11. 對話方塊

Post date: 2012/3/23 上午 05:39:08

11. 對話方塊

如果有很多輸入超出了功能表可以處理的程度,那麼我們可以使用對話方塊來取得輸入資訊。程式寫作者可以通過在某選項後面加上省略號(…)來表示該功能表項將啟動一個對話方塊。

對話方塊的一般形式是包含多種子視窗控制項的彈出式視窗,這些控制項的大小和位置在程式資源描述檔的「對話方塊模板」中指定。雖然程式寫作者能夠「手工」定義對話方塊模板,但是現在通常是在Visual C++ Developer Studio中以交談式操作的方式設計的,然後由Developer Studio建立對話方塊模板。

當程式呼叫依據模板建立的對話方塊時,Microsoft Windows 98負責建立彈出式對話方塊視窗和子視窗控制項,並提供處理對話方塊訊息(包括所有鍵盤和滑鼠輸入)的視窗訊息處理程式。有時候稱呼完成這些功能的Windows內部程式碼為「對話方塊管理器」。

Windows的內部對話方塊視窗訊息處理程式所處理的許多訊息也傳遞給您自己程式中的函式,這個函式即是所謂的「對話方塊程序」或者「對話程序」。對話程序與普通的視窗訊息處理程式類似,但是也存在著一些重要區別。一般來說,除了在建立對話方塊時初始化子視窗控制項,處理來自子視窗控制項的訊息以及結束對話方塊之外,程式寫作者不需要再給對話方塊程序增加其他功能。對話程序通常不處理WM_PAINT訊息,也不直接處理鍵盤和滑鼠輸入。

對話方塊這個主題的含義太廣了,因為它還包含子視窗控制項的使用。不過,我們已經在 第九章 研究了子視窗控制項。當您在對話方塊中使用子視窗控制項時, 第九章 所提到的許多工作都可以由Windows的對話方塊管理器來完成。尤其是,在程式COLORS1中遇到在捲動列之間切換輸入焦點的問題也不會在對話方塊中出現。Windows會處理對話方塊中的控制項之間切換輸入焦點所必需完成的全部工作。

不過,在程式中添加對話方塊要比添加圖示或者功能表更麻煩一些。我們將從一個簡單的對話方塊開始,讓您對各部分之間的相互聯繫有所瞭解。

模態對話方塊

對話方塊分為兩類:「模態的」和「非模態的」,其中模態對話方塊最為普遍。當您的程式顯示一個模態對話方塊時,使用者不能在對話方塊與同一個程式中的另一個視窗之間進行切換,使用者必須主動結束該對話方塊,這藉由通過按一下「OK」或者「Cancel」鍵來完成。不過,在顯示模態對話方塊時,使用者通常可以從目前的程式切換到另一個程式。而有些對話方塊(稱為「系統模態」)甚至連這樣的切換程式操作也不允許。在Windows中,顯示了系統模態對話方塊之後,要完成其他任何工作,都必須先結束該對話方塊。

建立「About」對話方塊

Windows程式即使不需要接收使用者輸入,也通常具有由功能表上的「About」選項啟動的對話方塊,該對話方塊用來顯示程式的名字、圖示、版權旗標和標記為「OK」的按鍵,也許還會有其他資訊(例如技術支援的電話號碼)。我們將要看到的第一個程式除了顯示一個「About」對話方塊外,別無它用。這個ABOUT1程式如程式11-1所示:

程式11-1 ABOUT1 ABOUT1.C /*------------------------------------------------------------------------ ABOUT1.C -- About Box Demo Program No. 1 (c) Charles Petzold, 1998 -------------------------------------------------------------------------*/ #include <windows.h> #include "resource.h" LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; BOOL CALLBACK AboutDlgProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT ("About1") ; MSG msg ; HWND hwnd ; WNDCLASS wndclass ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (hInstance, szAppName) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; wndclass.lpszMenuName = szAppName ; wndclass.lpszClassName = szAppName ; if (!RegisterClass (&wndclass)) { MessageBox (NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; } hwnd = CreateWindow ( szAppName, TEXT ("About Box Demo Program"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { static HINSTANCE hInstance ; switch (message) { case WM_CREATE : hInstance = ((LPCREATESTRUCT) lParam)->hInstance ; return 0 ; case WM_COMMAND : switch (LOWORD (wParam)) { case IDM_APP_ABOUT : DialogBox (hInstance, TEXT ("AboutBox"), hwnd, AboutDlgProc) ; break ; } return 0 ; case WM_DESTROY : PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; } BOOL CALLBACK AboutDlgProc (HWND hDlg, UINT message,WPARAM wParam, LPARAM lParam) { switch (message) { case WM_INITDIALOG : return TRUE ; case WM_COMMAND : switch (LOWORD (wParam)) { case IDOK : case IDCANCEL : EndDialog (hDlg, 0) ; return TRUE ; } break ; } return FALSE ; }

ABOUT1.RC (摘錄) //Microsoft Developer Studio generated resource script. #include "resource.h" #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// // Dialog ABOUTBOX DIALOG DISCARDABLE 32, 32, 180, 100 STYLE DS_MODALFRAME | WS_POPUP FONT 8, "MS Sans Serif" BEGIN DEFPUSHBUTTON "OK",IDOK,66,80,50,14 ICON "ABOUT1",IDC_STATIC,7,7,21,20 CTEXT "About1",IDC_STATIC,40,12,100,8 CTEXT "About Box Demo Program",IDC_STATIC,7,40,166,8 CTEXT "(c) Charles Petzold, 1998",IDC_STATIC,7,52,166,8 END ///////////////////////////////////////////////////////////////////////////// // Menu ABOUT1 MENU DISCARDABLE BEGIN POPUP "&Help" BEGIN MENUITEM "&About About1...", IDM_APP_ABOUT END END ///////////////////////////////////////////////////////////////////////////// // Icon ABOUT1 ICON DISCARDABLE "About1.ico"

RESOURCE.H (摘錄) // Microsoft Developer Studio generated include file. // Used by About1.rc #define IDM_APP_ABOUT 40001 #define IDC_STATIC -1

ABOUT1.ICO

藉由後面章節中介紹的方法,您還可以在程式中建立圖示和功能表。圖示和功能表的ID名均為「About1」。功能表有一個選項,它產生一條ID名為IDM_APP_ABOUT的WM_COMMAND訊息。這使得程式顯示的圖11-1所示的對話方塊。

對話方塊及其模板

要把一個對話方塊添加到Visual C++ Developer Studio會有的應用程式上,可以先從 Insert 功能表中選擇 Resource ,然後選擇 Dialog Box 。現在一個對話方塊出現在您的眼前,該對話方塊帶有標題列、標題(Dialog)以及 OK Cancel 按鈕。 Controls 工具列允許您在對話方塊中插入不同的控制項。

Developer Studio將對話方塊的ID設為標準的IDD_DIALOG1。您可以在此名稱上(或者在對話方塊本身)單擊右鍵,然後從功能表中選擇 Properties 。在本程式中,將ID改為「AboutBox」(帶有引號)。為了與我建立的對話方塊保持一致,請將 X Pos Y Pos 欄位改為32。這表示對話方塊相對於程式視窗顯示區域左上角的顯示位置待會會有關於對話方塊座標的詳細討論)。

現在,繼續在 Properties 對話方塊中選擇 Styles 頁面標籤。因為此對話方塊沒有標題列,所以不要選取 Title Bar 核取方塊。然後請單擊 Properties 對話方塊的 關閉 按鈕。

現在可以設計對話方塊了。因為不需要 Cancel 按鈕,所以先單擊該按鈕,然後按下鍵盤上的 Delete 鍵。接著單擊 OK 按鈕,將其移動到對話方塊的底部。在Developer Studio視窗下面的工具列上有一個小點陣圖,它可使控制項在視窗內水平居中對齊,請按下此鈕。

如果您要讓程式的圖示出現在對話方塊中,可以這樣做:先在浮動的 Controls 工具列中按下「 Pictures 」按鈕。將滑鼠移動到對話方塊的表面,按下左鍵,然後拉出一個矩形。這就是圖示將出現的位置。然後在次矩形上按下滑鼠右鍵,從功能表中選擇 Properties 。保持 ID IDC_STATIC 。此識別字在RESOURCE.H中定義為-1,用於程式中不使用的所有ID。將 Type 改為 Icon 。您可以在 Image 欄位輸入程式圖示的名稱,或者,如果您已經建立了一個圖示,那麼您也可以從下拉式清單方塊中選擇一個名稱(About1)。

對於對話方塊中的三個靜態字串,可以從 Controls 工具列中選擇 Static Text ,然後確定文字在對話方塊中的位置。右鍵單擊控制項,然後從功能表中選擇 Properties 。在 Properties 框的 Caption 欄位中輸入要顯示的文字。選擇 Styles 頁面標籤,從 Align Text 欄位選擇 Center

在添加這些字串的時候,若希望對話方塊可以更大一些,請先選中對話方塊,然後拖曳邊框。您也可以選擇並縮放控制項。通常用鍵盤上的游標移動鍵完成此操作會更容易些。箭頭鍵本身移動控制項,按下Shift鍵後按箭頭鍵,可以改變控制項的大小。所選控制項的座標和大小顯示在Developer Studio視窗的右下角。

如果您建立了一個應用程式,那麼以後在查看資源描述檔ABOUT1.RC時,您將發現Developer Studio建立的模板。我所設計的對話方塊模板如下:

圖11-1 程式ABOUT1的對話方塊

ABOUTBOX DIALOG DISCARDABLE 32, 32, 180, 100 STYLE DS_MODALFRAME | WS_POPUP FONT 8, "MS Sans Serif" BEGIN DEFPUSHBUTTON "OK",IDOK,66,80,50,14 ICON "ABOUT1",IDC_STATIC,7,7,21,20 CTEXT "About1",IDC_STATIC,40,12,100,8 CTEXT "About Box Demo Program",IDC_STATIC,7,40,166,8 CTEXT "(c) Charles Petzold, 1998",IDC_STATIC,7,52,166,8 END

第一行給出了對話方塊的名稱(這裏為ABOUTBOX)。如同其他資源,您也可以使用數字作為對話方塊的名稱。名稱後面是關鍵字DIALOG和DISCARDABLE以及四個數字。前兩個數字是對話方塊左上角的x、y座標,該座標在程式呼叫對話方塊時,是相對於父視窗顯示區域的。後兩個數字是對話方塊的寬度和高度。

這些座標和大小的單位都不是圖素。它們實際上依據一種特殊的座標系統,該系統只用於對話方塊模板。數字依據對話方塊使用字體的大小而定(這裏是8點的MS Sans Serif字體):x座標和寬度的單位是字元平均寬度的1/4;y座標和高度的單位是字元高度的1/8。因此,對這個對話方塊來說,對話方塊左上角距離主視窗顯示區域的左邊是5個字元,距離頂邊是2-1/2個字元。對話方塊本身寬40個字元,高10個字元。

這樣的座標系使得程式寫作者可以使用座標和大小來大致勾勒對話方塊的尺寸和外觀,而不管視訊顯示器的解析度是多少。由於系統字體字元的高度大致為其寬度的兩倍,所以,x軸和y軸的量度差不多相等。

模板中的STYLE敘述類似於CreateWindow呼叫中的style欄位。對於模態對話方塊,通常使用WS_POPUP和DS_MODALFRAME,我們將在稍後介紹其他的選項。

在BEGIN和END敘述(或者是左右大括弧,手工設計對話方塊模板時,您可能會使用)之間,定義出現在對話方塊中的子視窗控制項。這個對話方塊使用了三種型態的子視窗控制項,它們分別是DEFPUSHBUTTON(內定按鍵)、ICON(圖示)和CTEXT(文字居中)。這些敘述的格式為:

其中,後面的iStyle項是可選的,它使用Windows表頭檔案中定義的識別字來指定其他視窗樣式。

DEFPUSHBUTTON、ICON和CTEXT等識別字只可以在對話方塊中使用,它們是某種特定視窗類別和視窗樣式的縮寫。例如,CTEXT指示這個子視窗控制項類別是「靜態的」,其樣式為:

雖然前面沒有出現過WS_GROUP識別字,但是在 第九章的COLORS1程式 中已經出現過WS_CHILD、SS_CENTER和WS_VISIBLE視窗樣式,我們在建立靜態子視窗文字控制項時已經用到了它們。

對於圖示,文字欄位是程式的圖示資源名稱,它也在ABOUT1資源描述檔中定義。對於按鍵,文字欄位是出現在按鍵裏的文字,這個文字相同於在程式中建立子視窗控制項時呼叫CreateWindow所指定的第二個參數。

id欄位是子視窗在向其父視窗發送訊息(通常為WM_COMMMAND訊息)時用來標示它自身的值。這些子視窗控制項的父視窗就是對話方塊本身,它將這些訊息發送給Windows的一個視窗訊息處理程式。不過,這個視窗訊息處理程式也將這些訊息發送給您在程式中給出的對話方塊程序。ID值相同於我們在第九章建立子視窗時,在CreateWindow函式中使用的子視窗ID。由於文字和圖示控制項不向父視窗回送訊息,所以這些值被設定為IDC_STATIC,它在RESOURCE.H中定義為-1。按鍵的ID值為IDOK,它在WINUSER.H中定義為1。

接下來的四個數字設定子視窗的位置(相對於對話方塊顯示區域的左上角)和大小,它們是以系統字體平均寬度的1/4和平均高度的1/8為單位來表示的。對於ICON敘述,寬度和高度將被忽略。

對話方塊模板中的DEFPUSHBUTTON敘述,除了包含DEFPUSHBUTTON關鍵字所隱含的視窗樣式,還包含視窗樣式WS_GROUP。稍後討論該程式的第二個版本ABOUT2時,還會詳細說明WS_GROUP(以及相關的WS_TABSTOP樣式)。

對話方塊程序

您程式內的對話方塊程序處理傳送給對話方塊的訊息。儘管看起來很像是視窗訊息處理程式,但是它並不是真實的視窗訊息處理程式。對話方塊的視窗訊息處理程式在Windows內部定義,這個視窗程序呼叫您編寫的對話方塊程序,把它所接收到的許多訊息作為參數。下面是ABOUT1的對話方塊程序:

control-type "text" id, xPos, yPos, xWidth, yHeight, iStyle

WS_CHILD | SS_CENTER | WS_VISIBLE | WS_GROUP

BOOL CALLBACK AboutDlgProc (HWND hDlg, UINT message,WPARAM wParam, LPARAM lParam) { switch (message) { case WM_INITDIALOG : return TRUE ; case WM_COMMAND : switch (LOWORD (wParam)) { case IDOK : case IDCANCEL : EndDialog (hDlg, 0) ; return TRUE ; } break ; } return FALSE ; }

該函式的參數與常規視窗訊息處理程式的參數相同,與視窗訊息處理程式類似,對話方塊程序都必須定義為一個CALLBACK(callback)函式。儘管我使用了hDlg作為對話方塊視窗的代號,但是您也可以按照您自己的意思使用hwnd。首先,讓我們來看一下這個函式與視窗訊息處理程式的區別:

  • 視窗訊息處理程式傳回一個LRESULT。對話方塊傳回一個BOOL,它在Windows表頭檔案中定義為int型態。
  • 如果視窗訊息處理程式不處理某個特定的訊息,那麼它將呼叫DefWindowProc。如果對話方塊程序處理一個訊息,那麼它傳回TRUE(非0),如果不處理,則傳回FALSE(0)。
  • 對話方塊程序不需要處理WM_PAINT或WM_DESTROY訊息。對話方塊程序不接收WM_CREAT訊息,而是在特殊的WM_INITDIALOG訊息處理期間,對話方塊程序執行初始化操作。

WM_INITDIALOG訊息是對話方塊接收到的第一個訊息,這個訊息只發送給對話方塊程序。如果對話方塊程序傳回TRUE,那麼Windows將輸入焦點設定給對話方塊中第一個具有WS_TABSTOP樣式(我們將在ABOUT2的討論中加以解釋)的子視窗控制項。在這個對話方塊中,第一個具有WS_TABSTOP樣式的子視窗控制項是按鍵。另外,對話方塊程序也可以在處理WM_INITDIALOG時使用SetFocus來將輸入焦點設定為對話方塊中的某個子視窗控制項,然後傳回FALSE。

此外,對話方塊程序只處理WM_COMMAND訊息。這是當按鍵被滑鼠點中,或者在按鈕具有輸入焦點的情況下按下空白鍵時,按鍵控制項發送給其父視窗的訊息。這個控制項的ID(我們在對話方塊模板中將其設定為IDOK)在wParam的低字組中。對於這個訊息,對話方塊程序呼叫EndDialog,它告訴Windows清除對話方塊。對於所有其他訊息,對話方塊程序傳回FALSE,並告訴Windows內部的對話方塊視窗訊息處理程式:我們的對話方塊程序不處理這些訊息。

模態對話方塊的訊息不通過您程式的訊息佇列,所以不必擔心對話方塊中鍵盤加速鍵的影響。

啟動對話方塊

在WndProc中處理WM_CREATE訊息時,ABOUT1取得程式的執行實體代號並將它放在靜態變數中:

ABOUT1檢查WM_COMMAND訊息,以確保訊息wParam的低位元字等於IDM_APP_ABOUT。當它獲得這樣一個訊息時,程式呼叫DialogBox:

該函式需要執行實體代號(在處理WM_CREATE時儲存的)、對話方塊名稱(在資源描述檔中定義的)、對話方塊的父視窗(也是程式的主視窗)和對話方塊程序的位址。如果您使用一個數字而不是對話方塊模板名稱,那麼可以用MAKEINTRESOURCE巨集將它轉換為一個字串。

從功能表中選擇「About About1」,將顯示圖11-2所示的對話方塊。您可以使用滑鼠單擊「OK」按鈕、按空白鍵或者按Enter鍵來結束這個對話方塊。對任何包含內定按鈕的對話方塊,在按下Enter鍵或空白鍵之後,Windows發送一個WM_COMMAND訊息給對話方塊,並令wParam的低字組等於內定按鍵的ID,此時的ID為IDOK。按下Escape鍵也可以關閉對話方塊,這時Windows將發送一個WM_COMMAND訊息,並令ID等於IDCANCEL。

直到對話方塊結束之後,用來顯示對話方塊的DialogBox才將控制權傳回給WndProc。DialogBox的傳回值是對話方塊程序內部呼叫的EndDialog函式的第二個參數(這個值未在ABOUT1中使用,但會在ABOUT2中使用)。然後,WndProc可以將控制權傳回給Windows。

即使在顯示對話方塊時,WndProc也可以繼續接收訊息。實際上,您可以從對話方塊程序內部給WndProc發送訊息。ABOUT1的主視窗是彈出式對話方塊視窗的父視窗,所以AboutDlgProc中的SendMessage呼叫可以使用如下敘述來開始:

不同的主題

雖然Visual C++ Developer Studio中的對話方塊編輯器和其他資源編輯器,使我們幾乎不用考慮資源描述的寫作問題,但是學習一些資源描述的語法還是有用的。尤其對於對話方塊模板來說,知道了語法,您就可以近一步瞭解對話方塊的範圍和限制。甚至當它不能滿足您的需要時,您還可以自己建立一個對話方塊模板(就像 本章後面的HEXCALC程式 )。資源編譯器和資源描述語法的文件位於/Platform SDK/Windows Programming Guidelines/Platform SDK Tools/Compiling/Using the Resource Compiler。

在Developer Studio的「Properties」對話方塊中指定了對話方塊的視窗樣式,它翻譯成對話方塊模板中的STYLE敘述。對於ABOUT1,我們使用模態對話方塊最常用的樣式;

然而,您也可以嘗試其他樣式。有些對話方塊有標題列,標題列用於指出對話方塊的用途,並允許使用者通過滑鼠在顯示幕上移動對話方塊。此樣式為WS_CAPTION。如果您使用WS_CAPTION,那麼DIALOG敘述中所指定的x座標和y座標是對話方塊顯示區域的座標,並相對於父視窗顯示區域的左上角。標題列將在y座標之上顯示。

如果使用了標題列,那麼您可以用CAPTION敘述將文字放入標題中。在對話方塊模板中,CAPTION敘述在STYLE敘述的後面:

另外,在對話方塊程序處理WM_INITDIALOG訊息處理期間,您還可以呼叫:

如果您使用WS_CAPTION樣式,也可以添加一個WS_SYSMENU樣式的系統功能表按鈕。此樣式允許使用者從系統功能表中選擇 Move Close

Properties 對話方塊的 Border 清單方塊中選擇 Resizing (相同於樣式WS_THICKFRAME),允許使用者縮放對話方塊,僅管此操作並不常用。如果您不介意更特殊一點的話,還可以著為此對話方塊樣式添加最大化方塊。

您甚至可以給對話方塊添加一個功能表。這時對話方塊模板將包括下面的敘述:

其參數不是功能表的名稱,就是資源描述中的功能表號。模態對話方塊很少使用功能表。如果使用了功能表,那麼您必須確保功能表和對話方塊控制項中的所有ID都是唯一的;或者不是唯一的,卻表達了相同的命令。

FONT敘述使您可以設定非系統字體,以供對話方塊文字使用。這在過去的對話方塊中不常用,但現在卻非常普遍。事實上,在內定情況下,Developer Studio為您建立的每一個對話方塊都選用8點的MS Sans Serif字體。一個Windows程式能把自己外觀打點得非常與眾不同,這只需為程式的對話方塊及其他文字輸出單獨準備一種字體即可。

儘管對話方塊視窗訊息處理程式通常位於Windows內部,但是您也可以使用自己編寫的視窗訊息處理程式來處理對話方塊訊息。要這樣做,您必須在對話方塊模板中指定一個視窗類別名:

這種用法很少見,但是在 本章後面所示的HEXCALC程式 中我們將用到它。

當您使用對話方塊模板的名稱來呼叫DialogBox時,Windows通過呼叫普通的CreateWindow函式來完成建立彈出式視窗所需要完成的一切操作。Windows從對話方塊模板中取得視窗的座標、大小、視窗樣式、標題和功能表,從DialogBox的參數中獲得執行實體代號和父視窗代號。它所需要的唯一其他資訊是一個視窗類別(假設對話方塊模板不指定視窗類別的話)。Windows為對話方塊註冊一個專用的視窗類別,這個視窗類別的視窗訊息處理程式可以存取對話方塊程序位址(該位址是您在DialogBox呼叫中指定的),所以它可以使程式獲得該彈出式視窗所接收的訊息。當然,您可以通過自己建立彈出式視窗來建立和維護自己的對話方塊。不過,使用DialogBox則更簡單。

也許您希望受益於Windows對話方塊管理器,但不希望(或者能夠)在資源描述中定義對話方塊模板,也可能您希望程式在執行時可以動態地建立對話方塊。這時可以完成這種功能的函式是DialogBoxIndirect,此函式用資料結構來定義模板。

在ABOUT1.RC的對話方塊模板中,我們使用縮寫CTEXT、ICON和DEFPUSHBUTTON來定義對話方塊所需要的三種型態的子視窗控制項。您還可以使用其他型態,每種型態都隱含一個特定的預先定義視窗類別和一種視窗樣式。下表顯示了與一些控制項型態相同的視窗類別和視窗樣式:

hInstance = ((LPCREATESTRUCT) lParam)->hInstance ;

DialogBox (hInstance, TEXT ("AboutBox"), hwnd, AboutDlgProc) ;

SendMessage (GetParent (hDlg), . . . ) ;

STYLE WS_POPUP | DS_MODALFRAME

CAPTION "Dialog Box Caption"

SetWindowText (hDlg, TEXT ("Dialog Box Caption")) ;

MENU menu-name

CLASS "class-name"

表11-1

資源編譯器是唯一能夠識別這些縮寫的程式。除了表中所示的視窗樣式外,每個控制項還具有下面的樣式:

對於這些控制項型態,除了EDITTEXT、SCROLLBAR、LISTBOX和COMBOBOX之外,控制項敘述的格式為:

對於EDITTEXT、SCROLLBAR、LISTBOX和COMBOBOX,其格式為:

其中沒有文字欄位。在這兩種敘述中,iStyle參數都是選擇性的。

第九章 ,我討論了確定預先定義子視窗的寬度和高度的規則。您可能需要回到 第九章 去參考這些規則,這時請記住:對話方塊模板中指定大小的單位為平均字元寬度的1/4,及平均字元高度的1/8。

控制項敘述的 style 欄位是可選的。它允許您包含其他視窗樣式識別字。例如,如果您想建立在正方形框左邊包含文字的核取方塊,那麼可以使用:

注意,控制項型態EDITTEXT會自動添加一個邊框。如果您想建立一個沒有邊框的子視窗編輯控制項,您可以使用:

資源編譯器也承認與下面敘述類似的專用控制項敘述:

此敘述允許您通過指定視窗類別和完整的視窗樣式,來建立任意型態的子視窗控制項。例如,要取代:

您可以使用:

當編譯資源描述檔時,這兩條敘述在.RES和.EXE檔案中的編碼是相同的。在Developer Studio中,您可以使用 Controls 工具列中的 Custom Control 選項來建立此敘述。在ABOUT3程式中,我向您展示了如何用此選項建立一個控制項,且在您的程式中已定義了該控制項的視窗類別。

當您在對話方塊模板中使用CONTROL敘述時,不必包含WS_CHILD和WS_VISIBLE樣式。在建立子視窗時,Windows已經包含了這些視窗樣式。CONTROL敘述的格式也說明Windows對話方塊管理器在建立對話方塊時就完成了此項操作。首先,就像我前面所討論的,它建立一個彈出式視窗,其父視窗代號在DialogBox函式中提供。然後,對話方塊管理器為對話方塊模板中的每個控制項建立一個子視窗。所有這些控制項的父視窗均是這個彈出式對話方塊。上面給出的CONTROL敘述被轉換成一個CreateWindow呼叫,形式如下所示:

WS_CHILD | WS_VISIBLE

control-type "text", id, xPos, yPos, xWidth, yHeight, iStyle

control-type id, xPos, yPos, xWidth, yHeight, iStyle

CHECKBOX "text", id, xPos, yPos, xWidth, yHeight, BS_LEFTTEXT

EDITTEXT id, xPos, yPos, xWidth, yHeight, NOT WS_BORDER

CONTROL "text", id, "class", iStyle, xPos, yPos, xWidth, yHeight

PUSHBUTTON "OK", IDOK, 10, 20, 32, 14

CONTROL "OK", IDOK, "button", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_TABSTOP, 10, 20, 32, 14

hCtrl =CreateWindow (TEXT ("button"), TEXT ("OK"), WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON, 10 * cxChar / 4, 20 * cyChar / 8, 32 * cxChar / 4, 14 * cyChar / 8, hDlg, IDOK, hInstance, NULL) ;

其中,cxChar和cyChar是系統字體字元的寬度和高度,以圖素為單位。hDlg參數是從建立該對話方塊視窗的CreateWindow呼叫傳回的值;hInstance參數是從DialogBox呼叫獲得的。

更複雜的對話方塊

ABOUT1中的簡單對話方塊展示了設計和執行一個對話方塊的要點,現在讓我們來看一個稍微複雜的例子。程式11-2給出的ABOUT2程式展示了如何在對話方塊程序中管理控制項(這裏用單選按鈕)以及如何在對話方塊的顯示區域中繪圖。

程式11-2 ABOUT2 ABOUT2.C /*-------------------------------------------------------------------------- ABOUT2.C -- About Box Demo Program No. 2 (c) Charles Petzold, 1998 ---------------------------------------------------------------------------*/ #include <windows.h> #include "resource.h" LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; BOOL CALLBACK AboutDlgProc (HWND, UINT, WPARAM, LPARAM) ; int iCurrentColor = IDC_BLACK, iCurrentFigure = IDC_RECT ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT ("About2") ; MSG msg ; HWND hwnd ; WNDCLASS wndclass ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (hInstance, szAppName) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; wndclass.lpszMenuName = szAppName ; wndclass.lpszClassName = szAppName ; if (!RegisterClass (&wndclass)) { MessageBox ( NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; } hwnd = CreateWindow ( szAppName, TEXT ("About Box Demo Program"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } void PaintWindow (HWND hwnd, int iColor, int iFigure) { static COLORREF crColor[8] = { RGB ( 0, 0, 0), RGB ( 0, 0, 255), RGB ( 0, 255, 0), RGB ( 0, 255, 255), RGB (255, 0, 0), RGB (255, 0, 255), RGB (255, 255, 0), RGB (255, 255, 255)} ; HBRUSH hBrush ; HDC hdc ; RECT rect ; hdc = GetDC (hwnd) ; GetClientRect (hwnd, &rect) ; hBrush = CreateSolidBrush (crColor[iColor - IDC_BLACK]) ; hBrush = (HBRUSH) SelectObject (hdc, hBrush) ; if (iFigure == IDC_RECT) Rectangle (hdc, rect.left, rect.top, rect.right, rect.bottom) ; else Ellipse (hdc, rect.left, rect.top, rect.right, rect.bottom) ; DeleteObject (SelectObject (hdc, hBrush)) ; ReleaseDC (hwnd, hdc) ; } void PaintTheBlock (HWND hCtrl, int iColor, int iFigure) { InvalidateRect (hCtrl, NULL, TRUE) ; UpdateWindow (hCtrl) ; PaintWindow (hCtrl, iColor, iFigure) ; } LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam) { static HINSTANCE hInstance ; PAINTSTRUCT ps ; switch (message) { case WM_CREATE: hInstance = ((LPCREATESTRUCT) lParam)->hInstance ; return 0 ; case WM_COMMAND: switch (LOWORD (wParam)) { case IDM_APP_ABOUT: if (DialogBox (hInstance, TEXT ("AboutBox"), hwnd, AboutDlgProc)) InvalidateRect (hwnd, NULL, TRUE) ; return 0 ; } break ; case WM_PAINT: BeginPaint (hwnd, &ps) ; EndPaint (hwnd, &ps) ; PaintWindow (hwnd, iCurrentColor, iCurrentFigure) ; return 0 ; case WM_DESTROY: PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; } BOOL CALLBACK AboutDlgProc (HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { static HWND hCtrlBlock ; static int iColor, iFigure ; switch (message) { case WM_INITDIALOG: iColor = iCurrentColor ; iFigure = iCurrentFigure ; CheckRadioButton (hDlg, IDC_BLACK, IDC_WHITE, iColor) ; CheckRadioButton (hDlg, IDC_RECT, IDC_ELLIPSE, iFigure) ; hCtrlBlock = GetDlgItem (hDlg, IDC_PAINT) ; SetFocus (GetDlgItem (hDlg, iColor)) ; return FALSE ; case WM_COMMAND: switch (LOWORD (wParam)) { case IDOK: iCurrentColor = iColor ; iCurrentFigure = iFigure ; EndDialog (hDlg, TRUE) ; return TRUE ; case IDCANCEL: EndDialog (hDlg, FALSE) ; return TRUE ; case IDC_BLACK: case IDC_RED: case IDC_GREEN: case IDC_YELLOW: case IDC_BLUE: case IDC_MAGENTA: case IDC_CYAN: case IDC_WHITE: iColor = LOWORD (wParam) ; CheckRadioButton (hDlg, IDC_BLACK, IDC_WHITE, LOWORD (wParam)) ; PaintTheBlock (hCtrlBlock, iColor, iFigure) ; return TRUE ; case IDC_RECT: case IDC_ELLIPSE: iFigure = LOWORD (wParam) ; CheckRadioButton (hDlg, IDC_RECT, IDC_ELLIPSE, LOWORD (wParam)) ; PaintTheBlock (hCtrlBlock, iColor, iFigure) ; return TRUE ; } break ; case WM_PAINT: PaintTheBlock (hCtrlBlock, iColor, iFigure) ; break ; } return FALSE ; }

ABOUT2.RC (摘錄) //Microsoft Developer Studio generated resource script. #include "resource.h" #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// // Dialog ABOUTBOX DIALOG DISCARDABLE 32, 32, 200, 234 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION FONT 8, "MS Sans Serif" BEGIN ICON "ABOUT2",IDC_STATIC,7,7,20,20 CTEXT "About2",IDC_STATIC,57,12,86,8 CTEXT "About Box Demo Program",IDC_STATIC,7,40,186,8 LTEXT "",IDC_PAINT,114,67,74,72 GROUPBOX "&Color",IDC_STATIC,7,60,84,143 RADIOBUTTON "&Black",IDC_BLACK,16,76,64,8,WS_GROUP | WS_TABSTOP RADIOBUTTON "B&lue",IDC_BLUE,16,92,64,8 RADIOBUTTON "&Green",IDC_GREEN,16,108,64,8 RADIOBUTTON "Cya&n",IDC_CYAN,16,124,64,8 RADIOBUTTON "&Red",IDC_RED,16,140,64,8 RADIOBUTTON "&Magenta",IDC_MAGENTA,16,156,64,8 RADIOBUTTON "&Yellow",IDC_YELLOW,16,172,64,8 RADIOBUTTON "&White",IDC_WHITE,16,188,64,8 GROUPBOX "&Figure",IDC_STATIC,109,156,84,46,WS_GROUP RADIOBUTTON "Rec&tangle",IDC_RECT,116,172,65,8,WS_GROUP | WS_TABSTOP RADIOBUTTON "&Ellipse",IDC_ELLIPSE,116,188,64,8 DEFPUSHBUTTON "OK",IDOK,35,212,50,14,WS_GROUP PUSHBUTTON "Cancel",IDCANCEL,113,212,50,14,WS_GROUP END ///////////////////////////////////////////////////////////////////////////// // Icon ABOUT2 ICON DISCARDABLE "About2.ico" ///////////////////////////////////////////////////////////////////////////// // Menu ABOUT2 MENU DISCARDABLE BEGIN POPUP "&Help" BEGIN MENUITEM "&About", IDM_APP_ABOUT END END

RESOURCE.H (摘錄) // Microsoft Developer Studio generated include file. // Used by About2.rc #define IDC_BLACK 1000 #define IDC_BLUE 1001 #define IDC_GREEN 1002 #define IDC_CYAN 1003 #define IDC_RED 1004 #define IDC_MAGENTA 1005 #define IDC_YELLOW 1006 #define IDC_WHITE 1007 #define IDC_RECT 1008 #define IDC_ELLIPSE 1009 #define IDC_PAINT 1010 #define IDM_APP_ABOUT 40001 #define IDC_STATIC -1

ABOUT2.ICO

ABOUT2中的About框有兩組單選按鈕。一組用來選擇顏色,另一組用來選擇是矩形還是橢圓形。所選的矩形或者橢圓顯示在對話方塊內,其內部以目前選擇的顏色著色。使用者按下「OK」按鈕後,對話方塊會終止,程式的視窗訊息處理程式在它自己的顯示區域內繪出所選圖形。如果您按下「Cancel」,則主視窗的顯示區域會保持原樣。對話方塊如圖11-2所示。儘管ABOUT2使用預先定義的識別字IDOK和IDCANCEL作為兩個按鍵,但是每個單選按鈕均有自己的識別字,它們以字首IDC開頭(用於控制項的ID)。這些識別字在RESOURCE.H中定義。

當您在ABOUT2對話方塊中建立單選按鈕時,請按顯示順序建立。這能保證Developer Studio依照順序定義識別字的值,程式將使用這些值。另外,每個單選按鈕都不要選中「Auto」選項。「Auto Radio Button」需要的程式碼較少,但基本上處理起來更深奧些。然後請依照ABOUT2.RC中的定義來設定它們的識別字。

選中「Properties」對話方塊中下列物件的「Group」選項:「OK」和「Cancel」按鈕、「Figure」分組方塊、每個分組方塊中的第一個單選按鈕(「Black」和「Rectangle」)。選中這兩個單選按鈕的「Tab Stop」核取方塊。

當您有全部控制項在對話方塊中的近似位置和大小時,就可以從「Layout」功能表選擇「Tab Order」選項。按ABOUT2.RC資源描述中顯示的順序單擊每一個控制項。

使用對話方塊控制項

第九章 中,您會發現大多數子視窗控制項發送WM_COMMAND訊息給其父視窗(唯一例外的是捲動列控制項)。您還看到,經由發送訊息給子視窗控制項,父視窗可以改變子視窗控制項的狀態(例如,選擇或不選擇單選按鈕、核取方塊)。您也可以用類似方法在對話方塊程序中改變控制項。例如,如果您設計了一系列單選按鈕,就可以發送訊息給它們,以選擇或者不選擇這些按鈕。不過,Windows也提供了幾種使用對話方塊控制項的簡單辦法。我們來看一看對話方塊程序與子視窗控制項相互通信的方式。

ABOUT2的對話方塊模板顯示在程式11-2的ABOUT2.RC資源描述檔中。GROUPBOX控制項只是一個帶標題(標題為「Color」或者「Figure」)的分組方塊,每組單選按鈕都由這樣的分組方塊包圍。前一組的八個單選按鈕是互斥的,第二組的兩個單選按鈕也是如此。

當用滑鼠單擊其中一個單選按鈕時(或者當單選按鈕擁有輸入焦點時按空白鍵),子視窗向其父視窗發送一個WM_COMMAND訊息,訊息的wParam的低字組被設為控制項的ID,wParam的高字組是一個通知碼,lParam值是控制項的視窗代號。對於單選按鈕,這個通知碼是BN_CLICKED或者0。然後Windows中的對話方塊視窗訊息處理程式將這個WM_COMMAND訊息發送給ABOUT2.C內的對話方塊程序。當對話方塊程序收到一個單選按鈕的WM_COMMAND訊息時,它為此按鈕設定選中標記,並為組中其他按鈕清除選中標記。

您可能還記得在 第九章 中已經提過,選中和不選中按鈕均需要向子視窗控制項發送BM_CHECK訊息。要設定一個按鈕選中標記,您可以使用:

要消除選中標記,您可以使用:

其中hwndCtrl參數是子視窗按鈕控制項的視窗代號。

但是在對話方塊程序中使用這種方法是時有點問題的,因為您不知道所有單選按鈕的視窗代號,只是從您獲得的訊息中知道其中一個代號。幸運的是,Windows為您提供了一個函式,可以用對話方塊代號和控制項ID來取得一個對話方塊控制項的視窗代號:

(您也可以使用如下函式,從視窗代號中取得控制項的ID值:

但是在大多數情況下這是不必要的。)

您會注意到,在程式11-2所示的表頭檔案ABOUT2.H中,八種顏色的ID值是從IDC_BLACK到IDC_WHITE連續變化的,這種安排在處理來自單選按鈕的WM_COMMAND訊息時將會很有用。在第一次嘗試選中或者不選中單選按鈕時,您可能會在對話方塊程序中編寫如下的程式:

SendMessage (hwndCtrl, BM_SETCHECK, 1, 0) ;

SendMessage (hwndCtrl, BM_SETCHECK, 0, 0) ;

hwndCtrl = GetDlgItem (hDlg, id) ;

id = GetWindowLong (hwndCtrl, GWL_ID) ;

圖11-2 ABOUT2程式的對話方塊

static int iColor ; 其他行程式 case WM_COMMAND: switch (LOWORD (wParam)) { 其他行程式 case IDC_BLACK: case IDC_RED: case IDC_GREEN: case IDC_YELLOW: case IDC_BLUE: case IDC_MAGENTA: case IDC_CYAN: case IDC_WHITE: iColor = LOWORD (wParam) ; for (i = IDC_BLACK, i <= IDC_WHITE, i++) SendMessage (GetDlgItem (hDlg, i), BM_SETCHECK, i == LOWORD (wParam), 0) ; return TRUE ; 其他行程式

這種方法能讓人滿意地執行。您將新的顏色值儲存在iColor中,並且還建立了一個迴圈,輪流使用所有八種顏色的ID值。您取得每個單選按鈕控制項的視窗代號,並用SendMessage給每個代號發送一條BM_SETCHECK訊息。只有對於向對話方塊視窗訊息處理程式發送WM_COMMAND訊息的按鈕,這個訊息的wParam值才被設定為1。

第一種簡化的方法是使用專門的對話方塊程序SendDlgItemMessage:

它相同於:

現在,迴圈將變成這樣:

稍微有些改進。但是真正的重大突破要等到使用了CheckRadioButton函式時才會出現:

這個函式將ID在idFirst到idLast之間的所有單選按鈕的選中標記都清除掉,除了ID為idCheck的單選按鈕,因為它是被選中的。這裏,所有ID必須是連續的。從此我們可以完全擺脫迴圈,並使用:

這正是ABOUT2對話方塊程序所採用的方法。

在使用核取方塊時,也提供了類似的簡化函式。如果您建立了一個「CHECKBOX」對話方塊視窗控制項,那麼可以使用如下的函式來設定和清除選中標記:

如果iCheck設定為1,那麼按鈕被選中;如果設定為0,那麼按鈕不被選中。您可以使用如下的方法來取得對話方塊中某個核取方塊的狀態:

在對話方塊程序中,您既可以將選中標記的目前狀態儲存在一個靜態變數中,又可以在收到一個WM_COMMAND訊息後,使用如下方法觸發按鈕:

如果您定義了BS_AUTOCHECKBOX控制項,那麼完全沒有必要處理WM_COMMAND訊息。在終止對話方塊之前,您只要使用IsDlgButtonChecked就可以取得按鈕目前的狀態。不過,如果您使用BS_AUTORADIOBUTTON樣式,那麼IsDlgButtonChecked就不能令人滿意了,因為需要為每個單選按鈕都呼叫它,直到函式傳回TRUE。實際上,您還要攔截WM_COMMAND訊息來追蹤按下的按鈕。

「OK」和「Cancel」按鈕

ABOUT2有兩個按鍵,分別標記為「OK」和「Cancel」。在ABOUT2.RC的對話方塊模板中,「OK」按鈕的ID值為IDOK(在WINUSER.H中被定義為1),「Cancel」按鈕的ID值為IDCANCEL(定義為2),「OK」按鈕是內定的:

在對話方塊中,通常都這樣安排「OK」和「Cancel」按鈕:將「OK」按鈕作為內定按鈕有助於用鍵盤介面終止對話。一般情況下,您通過單擊兩個滑鼠按鍵之一,或者當所期望的按鈕具有輸入焦點時按下Spacebar來終止對話方塊。不過,如果使用者按下Enter,對話方塊視窗訊息處理程式也將產生一個WM_COMMAND訊息,而不管哪個控制項具有輸入焦點。wParam的低字組被設定為對話方塊中內定按鍵的ID值,除非另一個按鍵擁有輸入焦點。在後一種情況下,wParam的低字組被設定為具有輸入焦點之按鍵的ID值。如果對話方塊中沒有內定按鍵,那麼Windows向對話方塊程序發送一個WM_COMMAND訊息,訊息中wParam的低字組被設定為IDOK。如果使用者按下Esc鍵或者Ctrl-Break鍵,那麼Windows令wParam等於IDCANCEL,並給對話方塊程序發送一個WM_COMMAND訊息。所以,您不用在對話方塊程序中加入單獨的處理鍵盤操作,因為通常終止對話方塊的按鍵會由Windows將這兩個按鍵動作轉換為WM_COMMAND訊息。

AboutDlgProc函式通過呼叫EndDialog來處理這兩種WM_COMMAND訊息:

SendDlgItemMessage (hDlg, id, iMsg, wParam, lParam) ;

SendMessage (GetDlgItem (hDlg, id), id, wParam, lParam) ;

for (i = IDC_BLACK, i <= IDC_WHITE, i++) SendDlgItemMessage (hDlg, i, BM_SETCHECK, i == LWORD (wParam), 0) ;

CheckRadioButton (hDlg, idFirst, idLast, idCheck) ;

CheckRadioButton (hDlg, IDC_BLACK, IDC_WHITE, LOWORD (wParam)) ;

CheckDlgButton (hDlg, idCheckbox, iCheck) ;

iCheck = IsDlgButtonChecked (hDlg, idCheckbox) ;

CheckDlgButton (hDlg, idCheckbox, !IsDlgButtonChecked (hDlg, idCheckbox)) ;

DEFPUSHBUTTON "OK",IDOK,35,212,50,14 PUSHBUTTON "Cancel",IDCANCEL,113,212,50,14

switch (LWORD (wParam)) { case IDOK: iCurrentColor = iColor ; iCurrentFigure = iFigure ; EndDialog (hDlg, TRUE) ; return TRUE ; case IDCANCEL : EndDialog (hDlg, FALSE) ; return TRUE ;

ABOUT2的視窗訊息處理程式在程式的顯示區域中繪製矩形或橢圓時,使用了整體變數iCurrentColor和iCurrentFigure。AboutDlgProc在對話方塊中畫圖時使用了靜態區域變數iColor和iFigure。

注意EndDialog的第二個參數的值不同,這個值是在WndProc中作為原DialogBox函式的傳回值傳回的:

case IDM_ABOUT: if (DialogBox (hInstance, TEXT ("AboutBox"), hwnd, AboutDlgProc)) InvalidateRect (hwnd, NULL, TRUE) ; return 0 ;

如果DialogBox傳回TRUE(非0),則意味著按下了「OK」按鈕,然後需要使用新的顏色來更新WndProc顯示區域。當AboutDlgProc收到一個WM_COMMAND訊息並且訊息的wParam的低字組等於IDOK時,AboutDlgProc將圖形和顏色儲存在整體變數iCurrentColor和iCurrentFigure中。如果DialogBox傳回FALSE,則主視窗繼續使用iCurrentColor和iCurrentFigure的原始設定。

TRUE和FALSE通常用於EndDialog呼叫中,以告知主視窗訊息處理程式使用者是用「OK」還是用「Cancel」來終止對話方塊的。不過,EndDialog的參數實際上是一個int值,而DialogBox也傳回一個int值。所以,用這種方法能比僅用TRUE或者FALSE傳回更多的資訊。

避免使用整體變數

在ABOUT2中使用整體變數可能會、也可能不會影響您。一些程式寫作者(包括我自己)較喜歡少用整體變數。ABOUT2中的整體變數iCurrentColor和iCurrentFigure看來使用得完全合法,因為它們必須同時在視窗訊息處理程式和對話方塊程序中使用。不過,在一個有一大堆對話方塊的程式中,每個對話方塊都可能改變一堆變數的值,使整體變數的數量容易用得過多。

您可能更喜歡將程式中的對話方塊與資料結構相聯繫,該資料結構含有對話方塊可以改變的所有變數。您將在typedef敘述中定義這些結構。例如,在ABOUT2中,可以定義與「About」方塊相聯繫的結構:

typedef struct { int iColor, iFigure ; } ABOUTBOX_DATA ;

在WndProc中,您可以依據此結構來定義並初始化一個靜態變數:

在WndProc中也是這樣,用ad.iColor和ad.iFigure替換了所有的iCurrentColor和iCurrentFigure。呼叫對話方塊時,使用DialogBoxParam而不用DialogBox。此函式的第五個參數可以是任意的32位元值。一般來說,此值設定為指向一個結構的指標,在這裏是WndProc中的ABOUTBOX_DATA結構。

static ABOUTBOX_DATA ad = { IDC_BLACK, IDC_RECT } ;

case IDM_ABOUT: if (DialogBoxParam (hInstance, TEXT ("AboutBox"), hwnd, AboutDlgProc, &ad)) InvalidateRect (hwnd, NULL, TRUE) ; return 0 ;

這是關鍵:DialogBoxParam的最後一個參數是作為WM_INITDIALOG訊息中的lParam傳遞給對話方塊程序的。

對話方塊程序有兩個ABOUTBOX_DATA結構型態的靜態變數(一個結構和一個指向結構的指標):

在AboutDlgProc中,此定義代替了iColor和iFigure的定義。在WM_INITDIALOG訊息的開始部分,對話方塊程序根據lParam設定了這兩個變數的值:

第一道敘述中,pad設定為lParam的指標。亦即,pad實際是指向在WndProc定義的ABOUTBOX_DATA結構。第二個參數完成了從WndProc中的結構,到DlgProc中的區域結構的欄位對欄位內容複製。

現在,除了使用者按下「OK」按鈕時所用的程式碼以之外,所有的AboutDlgProc都用ad.iColor和ad.iFigure替換了iFigure和iColor。這時,將區域結構的內容複製回WndProc中的結構:

static ABOUTBOX_DATA ad, * pad ;

pad = (ABOUTBOX_DATA *) lParam ; ad = * pad ;

case IDOK: * pad = ad ; EndDialog (hDlg, TRUE) ; return TRUE ;

Tab停留和分組

第九章 ,我們利用視窗子類別化為COLORS1增加功能,使我們能夠按下Tab鍵從一個捲動列轉移到另一個捲動列。在對話方塊中,視窗子類別化是不必要的,因為Windows完成了將輸入焦點從一個控制項移動到另一個控制項的所有工作。儘管如此,您必須在對話方塊模板中使用WS_TABSTOP和WS_GROUP視窗樣式達到此目的。對於所有想要使用Tab鍵存取的控制項,都要在其視窗樣式中指定WS_TABSTOP。

如果參閱 表11-1 ,您就會注意到許多控制項將WS_TABSTOP定義為內定樣式,其他一些則沒有將它作為內定樣式。一般而言,不包含WS_TABSTOP樣式的控制項(特別是靜態控制項)不應該取得輸入焦點,因為即使有了輸入焦點,它們也不能完成操作。除非在處理WM_INITDIALOG訊息時您將輸入焦點設定給一個特定的控制項,並從訊息中傳回FALSE。否則Windows將輸入焦點設定為對話方塊內第一個具有WS_TABSTOP樣式的控制項。

Windows給對話方塊增加的第二個鍵盤介面包括游標移動鍵,這種介面對於單選按鈕有特殊的重要性。如果您使用Tab鍵移動到某一組內目前選中的單選按鈕,那麼,就需要使用游標移動鍵,將輸入焦點從該單選按鈕移動到組內其他單選按鈕上。使用WS_GROUP視窗樣式即可獲得這個功能。對於對話方塊模板中的特定控制項序列,Windows將使用游標移動鍵把輸入焦點從第一個具有WS_GROUP樣式的控制權切換到下一個具有WS_GROUP樣式的控制項中。如果有必要,Windows將從對話方塊的最後一個控制項迴圈到第一個控制項,以便找到分組的結尾。

在內定設定下,控制項LTEXT、CTEXT、RTEXT和ICON包含有WS_GROUP樣式,這種樣式方便地標記了分組的結尾。您必須經常將WS_GROUP樣式加到其他型態的控制項中。

讓我們來看一看ABOUT2.RC中的對話方塊模板。四個具有WS_TABSTOP樣式的控制項是每個組的第一個單選按鈕(明顯地包含)和兩個按鍵(內定設定)。在第一次啟動對話方塊時,您可以使用Tab鍵在這四個控制項之間移動。

在每組單選按鈕中,您可以使用游標移動鍵切換輸入焦點並改變選中標記。例如, Color 下拉式清單方塊的第一個單選按鈕( Black )和 Figure 下拉式清單方塊都具有WS_GROUP樣式。這意味著您可以用游標移動鍵將焦點從「Black」單選按鈕移動到 Figure 分組方塊中。類似的情形, Figure 分組方塊的第一個單選按鈕( Rectangle )和DEFPUSHBUTTON都具有WS_GROUP樣式,所以您可以使用游標移動鍵在組內兩個單選按鈕- Rectangle Ellipse 之間移動。兩個按鍵都有WS_GROUP樣式,以阻止游標移動鍵在按鍵具有輸入焦點時起作用。

使用ABOUT2時,Windows的對話方塊管理器在兩組單選按鈕中完成一些相當複雜的處理。正如所預期的那樣,處於單選按鈕組內時,游標移動鍵切換輸入焦點,並給對話方塊程序發送WM_COMMAND訊息。但是,當您改變了組內選中的單選按鈕時,Windows也給新選中的單選按鈕設定了WS_TABSTOP樣式。當您下一次使用Tab切換到這一組後,Windows將會把輸入焦點設定為選中的單選按鈕。

文字欄位中的「&」將導致緊跟其後的字母以底線顯示,這就增加了另一種鍵盤介面,您可以通過按底線字母來將輸入焦點移動到任意單選按鈕上。透過按下C(代表 Color 下拉式清單方塊)或者F(代表 Figure 下拉式清單方塊),您可以將輸入焦點移動到相對應組內目前選中的單選按鈕上。

儘管程式寫作者通常讓對話方塊管理器來完成這些工作,但是Windows提供了兩個函式,以便程式寫作者找尋下一個或者前一個Tab鍵停留項或者組項。這些函式為:

如果bPrevious為TRUE,那麼函式傳回前一個Tab鍵停留項或組項;如果為FALSE,則傳回下一個Tab鍵停留項或者組項。

在對話方塊上畫圖

ABOUT2還完成了一些相對說來很特別的事情,亦即在對話方塊上畫圖。讓我們來看一看它是怎樣做的。在ABOUT2.RC的對話方塊模板內,使用位置和大小為我們想要畫圖的區域定義了一塊空白文字控制項:

這個區域為18個字元寬和9個字元高。由於這個控制項沒有文字,所以視窗訊息處理程式為「靜態」類別所做的工作,只是在必須重繪這個子視窗控制項時清除其背景。

在目前顏色或圖形選擇發生改變,或者對話方塊自身獲得一個WM_PAINT訊息時,對話方塊程序呼叫PaintTheBlock,這個函式在ABOUT2.C中:

在AboutDlgProc中,視窗代號hCtrlBlock已經在處理WM_INITDIALOG訊息時被設定:

下面是PaintTheBlock函式:

hwndCtrl = GetNextDlgTabItem (hDlg, hwndCtrl, bPrevious) ;

hwndCtrl = GetNextDlgGroupItem (hDlg, hwndCtrl, bPrevious) ;

LTEXT "" IDC_PAINT, 114, 67, 72, 72

PaintTheBlock (hCtrlBlock, iColor, iFigure) ;

hCtrlBlock = GetDlgItem (hDlg, IDD_PAINT) ;

void PaintTheBlock (HWND hCtrl, int iColor, int iFigure) { InvalidateRect (hCtrl, NULL, TRUE) ; UpdateWindow (hCtrl) ; PaintWindow (hCtrl, iColor, iFigure) ; }

這個函式使得子視窗控制項無效,並為控制項視窗訊息處理程式產生一個WM_PAINT訊息,然後呼叫ABOUT2中的另一個函式PaintWindow 。

PaintWindow函式取得一個裝置內容代號,並將其放到hCtrl中,畫出所選圖形,根據所選顏色用一個著色畫刷填入圖形。子視窗控制項的大小從GetClientRect獲得。儘管對話方塊模板以字元為單位定義了控制項的大小,但GetClientRect取得以圖素為單位的尺寸。您也可以使用函式MapDialogRect將對話方塊中的字元座標轉換為顯示區域中的圖素座標。

我們並非真的繪製了對話方塊的顯示區域,實際繪製的是子視窗控制項的顯示區域。每當對話方塊得到一個WM_PAINT訊息時,就令子視窗控制項的顯示區域失效,並更新它,使它確信現在其顯示區域又有效了,然後在其上畫圖。

將其他函式用於對話方塊

大多數可以用在子視窗的函式也可以用於對話方塊中的控制項。例如,如果您想搗亂的話,那麼可以使用MoveWindow在對話方塊內移動控制項,強迫使用者用滑鼠來追蹤它們。

有時,您需要根據其他控制項的設定,動態地啟用或者禁用某些控制項,這需要呼叫:

當bEnable為TRUE(非0)時,它啟用控制項;當bEnable為FALSE(0)時,它禁用控制項。在控制項被禁用時,它不再接收鍵盤或者滑鼠輸入。您不能禁用一個擁有輸入焦點的控制項。

定義自己的控制項

儘管Windows承攬了許多維護對話方塊和子視窗控制項的工作,它同時也為您提供了各種加入程式碼的方法。前面我們已經看到了在對話方塊上繪圖的方法。您也可以使用 第九章 中討論的視窗子類別化來改變子視窗控制項的操作。

您還可以定義自己的子視窗控制項,並將它們用到對話方塊中。例如,假定您特別不喜歡普通的矩形按鍵,而傾向於建立橢圓形按鍵,那麼您可以通過註冊一個視窗類別,並使用自己編寫的視窗訊息處理程式處理來自您所建立視窗的訊息,從而建立橢圓形按鍵。在Developer Studio中,您可以在與自訂控制項相聯繫的「Properties」對話方塊中指定這個視窗類別,這將轉換成對話方塊模板中的CONTROL敘述。程式11-3所示的ABOUT3程式正是這樣做的。

EnableWindow (hwndCtrl, bEnable) ;

程式11-3 ABOUT3 ABOUT3.C /*----------------------------------------------------------------------------- ABOUT3.C -- About Box Demo Program No. 3 (c) Charles Petzold, 1998 ----------------------------------------------------------------------------*/ #include <windows.h> #include "resource.h" LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; BOOL CALLBACK AboutDlgProc (HWND, UINT, WPARAM, LPARAM) ; LRESULT CALLBACK EllipPushWndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT ("About3") ; MSG msg ; HWND hwnd ; WNDCLASS wndclass ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (hInstance, szAppName) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; wndclass.lpszMenuName = szAppName ; wndclass.lpszClassName = szAppName ; if (!RegisterClass (&wndclass)) { MessageBox ( NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; } wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = EllipPushWndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = NULL ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1) ; wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = TEXT ("EllipPush") ; RegisterClass (&wndclass) ; hwnd = CreateWindow ( szAppName, TEXT ("About Box Demo Program"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam) { static HINSTANCE hInstance ; switch (message) { case WM_CREATE : hInstance = ((LPCREATESTRUCT) lParam)->hInstance ; return 0 ; case WM_COMMAND : switch (LOWORD (wParam)) { case IDM_APP_ABOUT : DialogBox (hInstance, TEXT ("AboutBox"), hwnd, AboutDlgProc) ; return 0 ; } break ; case WM_DESTROY : PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; } BOOL CALLBACK AboutDlgProc (HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_INITDIALOG : return TRUE ; case WM_COMMAND : switch (LOWORD (wParam)) { case IDOK : EndDialog (hDlg, 0) ; return TRUE ; } break ; } return FALSE ; } LRESULT CALLBACK EllipPushWndProc ( HWND hwnd, UINT message,WPARAM wParam, LPARAM lParam) { TCHAR szText[40] ; HBRUSH hBrush ; HDC hdc ; PAINTSTRUCT ps ; RECT rect ; switch (message) { case WM_PAINT : GetClientRect (hwnd, &rect) ; GetWindowText (hwnd, szText, sizeof (szText)) ; hdc = BeginPaint (hwnd, &ps) ; hBrush = CreateSolidBrush (GetSysColor (COLOR_WINDOW)) ; hBrush = (HBRUSH) SelectObject (hdc, hBrush) ; SetBkColor (hdc, GetSysColor (COLOR_WINDOW)) ; SetTextColor (hdc, GetSysColor (COLOR_WINDOWTEXT)) ; Ellipse (hdc, rect.left, rect.top, rect.right, rect.bottom) ; DrawText (hdc, szText, -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER) ; DeleteObject (SelectObject (hdc, hBrush)) ; EndPaint (hwnd, &ps) ; return 0 ; case WM_KEYUP : if (wParam != VK_SPACE) break ;// fall through case WM_LBUTTONUP : SendMessage (GetParent (hwnd), WM_COMMAND, GetWindowLong (hwnd, GWL_ID), (LPARAM) hwnd) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }

ABOUT3.RC (摘錄) //Microsoft Developer Studio generated resource script. #include "resource.h" #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// // Dialog ABOUTBOX DIALOG DISCARDABLE 32, 32, 180, 100 STYLE DS_MODALFRAME | WS_POPUP FONT 8, "MS Sans Serif" BEGIN CONTROL "OK",IDOK,"EllipPush",WS_GROUP | WS_TABSTOP,73,79,32,14 ICON "ABOUT3",IDC_STATIC,7,7,20,20 CTEXT "About3",IDC_STATIC,40,12,100,8 CTEXT "About Box Demo Program",IDC_STATIC,7,40,166,8 CTEXT "(c) Charles Petzold, 1998",IDC_STATIC,7,52,166,8 END ///////////////////////////////////////////////////////////////////////////// // Menu ABOUT3 MENU DISCARDABLE BEGIN POPUP "&Help" BEGIN MENUITEM "&About About3...", IDM_APP_ABOUT END END ///////////////////////////////////////////////////////////////////////////// // Icon ABOUT3 ICON DISCARDABLE "icon1.ico"

RESOURCE.H (摘錄) // Microsoft Developer Studio generated include file. // Used by About3.rc #define IDM_APP_ABOUT 40001 #define IDC_STATIC -1

ABOUT3.ICO

我們所註冊的視窗類別叫做「EllipPush」(橢圓形按鍵)。在Developer Studio的對話方塊編輯器中,刪除「Cancel」和「OK」按鈕。要添加依據此視窗類別的控制項,請從「 Controls 」工具列選擇「 Custom Control 」。在此控制項的「 Properties 」對話方塊的「 Class 」欄位輸入「 EllipPush 」。在對話方塊模板中我們沒有使用DEFPUSHBUTTON敘述,而是用CONTROL敘述來指定此視窗類別:

當在對話方塊中建立子視窗控制項時,對話方塊管理器把這個視窗類別用於CreateWindow呼叫中。

ABOUT3.C程式在WinMain中註冊了EllipPush視窗類別:

CONTROL "OK" IDOK, "EllipPush", TABGRP, 64, 60, 32, 14

wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = EllipPushWndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = NULL ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1) ; wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = TEXT ("EllipPush") ; RegisterClass (&wndclass) ;

該視窗類別指定視窗訊息處理程式為EllipPushWndProc,在ABOUT3.C中正是這樣。

EllipPushWndProc視窗訊息處理程式只處理三種訊息:WM_PAINT、WM_KEYUP和WM_LBUTTONUP。在處理WM_PAINT訊息時,它從GetClientRect中取得視窗的大小,從GetWindowText中取得顯示在按鍵上的文字,用Windows函式Ellipse和DrawText來輸出橢圓和文字。

WM_KEYUP和WM_LBUTTONUP訊息的處理非常簡單:

case WM_KEYUP : if (wParam != VK_SPACE) break ; // fall through case WM_LBUTTONUP : SendMessage (GetParent (hwnd), WM_COMMAND, GetWindowLong (hwnd, GWL_ID), (LPARAM) hwnd) ; return 0 ;

視窗訊息處理程式使用GetParent來取得其父視窗(即對話方塊)的代號,並發送一個WM_COMMAND訊息,訊息的wParam等於控制項的ID,這個ID是用GetWindowLong取得的。然後,對話方塊視窗訊息處理程式將這個訊息傳給ABOUT3內的對話方塊程序,結果得到一個使用者自訂的按鍵,如圖11-3所示。您可以用同樣的方法來建立其他自訂對話方塊控制項。

這就是全部要做的嗎?其實不然。通常,對於維護子視窗控制項所需要的處理而言,EllipPushWndProc只是一個空架子。例如,按鈕不會像普通的按鍵那樣閃爍。要翻轉按鍵內的顏色,視窗訊息處理程式必須處理WM_KEYDOWN(來自空白鍵)和WM_LBUTTONDOWN訊息。視窗訊息處理程式還必須在收到WM_LBUTTONDOWN訊息時攔截滑鼠,並且,如果當按鈕還處於按下狀態,而滑鼠移到了子視窗的顯示區域之外,那麼得要釋放滑鼠攔截(並將按鈕的內部顏色回復為正常狀態)。只有在滑鼠被攔截時鬆開該按鈕,子視窗才會給其父視窗送回一個WM_COMMAND訊息。

EllipPushWndProc也不處理WM_ENABLE訊息。如上所述,對話方塊程序可以使用EnableWindow函式來禁用某視窗。於是,子視窗將顯示灰色文字,而不再是黑色文字,以表示它已經被禁用,並且不能再接收任何訊息了。

如果子視窗控制項的視窗訊息處理程式需要為所建立的每個視窗存放各自不同的資料,那麼它可以通過使用視窗類別結構中的cbWndExtra值來做到。這樣就在內部視窗結構中保留了空間,並可以用SetWindowLong和GetWindowLong來存取該資料。

非模態對話方塊

在本章的開始,我曾經說過對話方塊分為「模態的」和「非模態的」兩種。現在我們已經研究過這兩種對話方塊中最常見的一種-模態對話方塊。模態對話方塊(不包括系統模態對話方塊)。允許使用者在對話方塊與其他程式之間進行切換。但是,使用者不能切換到同一程式的另一個視窗,直到模態對話方塊被清除為止。非模態對話方塊允許使用者在對話方塊與其他程式之間進行切換,又可以在對話方塊與建立對話方塊的視窗之間進行切換。因此,非模態對話方塊與使用者程式常見的普通彈出式視窗可能更為相似。

當使用者覺得讓對話方塊保留片刻會更加方便時,使用非模態對話方塊是合適的。例如,文書處理程式經常使用非模態對話方塊來進行「Find」和「Change」操作。如果「Find」對話方塊是模態的,那麼使用者必須從功能表中選擇「Find」,然後輸入要尋找的字串,結束對話方塊,傳回到檔案中,接著再重複整個程序來尋找同一字串的另一次出現。允許使用者在檔案與對話方塊之間進行切換則會方便得多。

您已經看到,模態對話方塊是用DialogBox來建立的。只有在清除對話方塊之後,函式才會傳回值。在對話方塊程序內使用EndDialog呼叫來終止對話方塊,DialogBox傳回的是該呼叫的第二個參數的值。非模態對話方塊是使用CreateDialog來建立的,該函式所使用的參數與DialogBox相同。

區別是CreateDialog函式立即傳回對話方塊的視窗代號,並通常將這個視窗代號存放到整體變數中。

儘管將DialogBox這一名字用於模態對話方塊而CreateDialog用於非模態對話方塊是隨意的,但是您可以通過非模態對話方塊與普通視窗類似這一點來記住這兩個函式的區別。CreateDialog可以令人想起CreateWindow函式來,而後者建立的是普通視窗。

模態對話方塊與非模態對話方塊的區別

使用非模態對話方塊與使用模態對話方塊相似,但是也有一些重要的區別:

首先,非模態對話方塊通常包含一個標題列和一個系統功能表按鈕。當您在Developer Studio中建立對話方塊時,這些是內定選項。用於非模態對話方塊的對話方塊模板中的STYLE敘述形如:

標題列和系統功能表允許使用者,使用滑鼠或者鍵盤將非模態對話方塊移動到另一個顯示區域。對於模態對話方塊,您通常無須提供標題列和系統功能表,因為使用者不能在其下面的視窗中做任何其他的事情。

第二項重要的區別是:注意,在我們的範例STYLE敘述中包含有WS_VISIBLE樣式。在 Developer Studio 中,從「 Dialog Properties 」對話方塊的「 More Styles 」頁面標籤中選擇此選項。如果省略了WS_VISIBLE,那麼您必須在CreateDialog呼叫之後呼叫ShowWindow:

如果您既沒有包含WS_VISIBLE樣式,又沒有呼叫ShowWindow,那麼非模態對話方塊將不會被顯示。如果忽略這個事實,那麼習慣於模態對話方塊的程式寫作者在第一次試圖建立非模態對話方塊時,經常會出現問題。

第三項區別:與模態對話方塊和訊息方塊的訊息不同,非模態對話方塊的訊息要經過程序式的訊息佇列。要將這些訊息傳送給對話方塊視窗訊息處理程式,則必須改變訊息佇列。方法如下:當您使用CreateDialog建立非模態對話方塊時,應該將從呼叫中傳回的對話方塊代號儲存在一個整體變數(如hDlgModeless)中,並將訊息迴圈改變為:

hDlgModeless = CreateDialog ( hInstance, szTemplate, hwndParent, DialogProc) ;

STYLE WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_VISIBLE

hDlgModeless = CreateDialog ( . . . ) ; ShowWindow (hDlgModeless, SW_SHOW) ;

圖11-3 ABOUT3建立的自訂按鍵

while (GetMessage (&msg, NULL, 0, 0)) { if (hDlgModeless == 0 || !IsDialogMessage (hDlgModeless, &msg)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } }

如果訊息是發送給非模態對話方塊的,那麼IsDialogMessage將它發送給對話方塊中視窗訊息處理程式,並傳回TRUE(非0);否則,它將傳回FALSE(0)。只有hDlgModeless為0或者訊息不是該對話方塊的訊息時,才必須呼叫TranslateMessage和DispatchMessage函式。如果您將鍵盤加速鍵用於您的程式視窗,那麼訊息迴圈將如下所示:

while (GetMessage (&msg, NULL, 0, 0)) { if (hDlgModeless == 0 || !IsDialogMessage (hDlgModeless, &msg)) { if (!TranslateAccelerator (hwnd, hAccel, &msg)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } } }

由於整體變數被初始化為0,所以hDlgModeless將為0,直到建立對話方塊為止,從而保證不會使用無效的視窗代號來呼叫IsDialogMessage。在清除非模態對話方塊時,您也必須注意這一點,正如最後一點所說明的。

hDlgModeless變數也可以由程式的其他部分使用,以便對非模態對話方塊是否存在加以驗證。例如,程式中的其他視窗可以在hDlgModeless不等於0時給對話方塊發送訊息。

最後一項重要的區別:使用DestroyWindow而不是EndDialog來結束非模態對話方塊。當您呼叫DestroyWindow後,將hDlgModeless整體變數設定為0。

使用者習慣於從系統功能表中選擇「Close」來結束非模態對話方塊。儘管啟用了「Close」選項,Windows內的對話方塊視窗訊息處理程式並不處理WM_CLOSE訊息。您必須自己在對話方塊程序中處理它:

case WM_CLOSE : DestroyWindow (hDlg) ; hDlgModeless = NULL ; break ;

注意這兩個視窗代號之間的區別:DestroyWindow的hDlg參數是傳遞給對話方塊程序的參數;hDlgModeless是從CreateDialog傳回的整體變數,程式在訊息迴圈內檢驗它。

您也可以允許使用者使用按鍵來關閉非模態對話方塊,處理方式與處理WM_CLOSE訊息一樣。對話方塊必須傳回給建立它的視窗之任何資料都可以儲存在整體變數中。如果不喜歡使用整體變數,那麼您也可以用CreateDialogParam來建立非模態對話方塊,並按前面介紹的方法讓它儲存一個結構指標。

新的COLORS程式

第九章中所描述的COLORS1程式 建立了九個子視窗,以便顯示三個捲動列和六個文字項。那時候,這個程式還是我們所寫過的程式中相當複雜的一個。如果將COLORS1轉換為使用非模態對話方塊則會使程式-特別是WndProc函式-變得令人難以置信的簡單,修正後的COLORS2程式如程式11-4所示。

程式11-4 COLORS2 COLORS2.C /*---------------------------------------------------------------------------- COLORS2.C -- Version using Modeless Dialog Box (c) Charles Petzold, 1998 ----------------------------------------------------------------------------*/ #include <windows.h> LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; BOOL CALLBACK ColorScrDlg (HWND, UINT, WPARAM, LPARAM) ; HWND hDlgModeless ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT ("Colors2") ; HWND hwnd ; MSG msg ; WNDCLASS wndclass ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = CreateSolidBrush (0L) ; wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = szAppName ; if (!RegisterClass (&wndclass)) { MessageBox ( NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; } hwnd = CreateWindow ( szAppName, TEXT ("Color Scroll"), WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; hDlgModeless = CreateDialog ( hInstance, TEXT ("ColorScrDlg"), hwnd, ColorScrDlg) ; while (GetMessage (&msg, NULL, 0, 0)) { if (hDlgModeless == 0 || !IsDialogMessage (hDlgModeless, &msg)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } } return msg.wParam ; } LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam) { switch (message) { case WM_DESTROY : DeleteObject ((HGDIOBJ) SetClassLong (hwnd, GCL_HBRBACKGROUND, (LONG) GetStockObject (WHITE_BRUSH))) ; PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; } BOOL CALLBACK ColorScrDlg ( HWND hDlg, UINT message,WPARAM wParam, LPARAM lParam) { static int iColor[3] ; HWND hwndParent, hCtrl ; int iCtrlID, iIndex ; switch (message) { case WM_INITDIALOG : for (iCtrlID = 10 ; iCtrlID < 13 ; iCtrlID++) { hCtrl = GetDlgItem (hDlg, iCtrlID) ; SetScrollRange (hCtrl, SB_CTL, 0, 255, FALSE) ; SetScrollPos (hCtrl, SB_CTL, 0, FALSE) ; } return TRUE ; case WM_VSCROLL : hCtrl = (HWND) lParam ; iCtrlID = GetWindowLong (hCtrl, GWL_ID) ; iIndex = iCtrlID - 10 ; hwndParent = GetParent (hDlg) ; switch (LOWORD (wParam)) { case SB_PAGEDOWN : iColor[iIndex] += 15 ; // fall through case SB_LINEDOWN : iColor[iIndex] = min (255, iColor[iIndex] + 1) ; break ; case SB_PAGEUP : iColor[iIndex] -= 15 ; // fall through case SB_LINEUP : iColor[iIndex] = max (0, iColor[iIndex] - 1) ; break ; case SB_TOP : iColor[iIndex] = 0 ; break ; case SB_BOTTOM : iColor[iIndex] = 255 ; break ; case SB_THUMBPOSITION : case SB_THUMBTRACK : iColor[iIndex] = HIWORD (wParam) ; break ; default : return FALSE ; } SetScrollPos (hCtrl, SB_CTL, iColor[iIndex], TRUE) ; SetDlgItemInt (hDlg, iCtrlID + 3, iColor[iIndex], FALSE) ; DeleteObject ((HGDIOBJ) SetClassLong (hwndParent, GCL_HBRBACKGROUND, (LONG) CreateSolidBrush ( RGB (iColor[0], iColor[1], iColor[2])))) ; InvalidateRect (hwndParent, NULL, TRUE) ; return TRUE ; } return FALSE ; }

COLORS2.RC (摘錄) //Microsoft Developer Studio generated resource script. #include "resource.h" #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// // Dialog COLORSCRDLG DIALOG DISCARDABLE 16, 16, 120, 141 STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION CAPTION "Color Scroll Scrollbars" FONT 8, "MS Sans Serif" BEGIN CTEXT "&Red",IDC_STATIC,8,8,24,8,NOT WS_GROUP SCROLLBAR 10,8,20,24,100,SBS_VERT | WS_TABSTOP CTEXT "0",13,8,124,24,8,NOT WS_GROUP CTEXT "&Green",IDC_STATIC,48,8,24,8,NOT WS_GROUP SCROLLBAR 11,48,20,24,100,SBS_VERT | WS_TABSTOP CTEXT "0",14,48,124,24,8,NOT WS_GROUP CTEXT "&Blue",IDC_STATIC,89,8,24,8,NOT WS_GROUP SCROLLBAR 12,89,20,24,100,SBS_VERT | WS_TABSTOP CTEXT "0",15,89,124,24,8,NOT WS_GROUP END

RESOURCE.H (摘錄) // Microsoft Developer Studio generated include file. // Used by Colors2.rc #define IDC_STATIC -1

原來的COLORS1程式所顯示的捲動列大小是依據視窗大小決定的,而新程式在非模態對話方塊內以固定的尺寸來顯示它們,如圖11-4所示。

當您建立對話方塊模板時,直接將三個捲動列的ID分別設為10、11和12,將顯示捲動列目前值的三個靜態文字欄位的ID分別設為13、14和15。將每個捲動列都設定為Tab Stop樣式,而從所有的六個靜態文字欄位中刪除Group樣式。

在COLORS2中,非模態對話方塊是在WinMain函式裏建立的,緊跟在程式主視窗的ShowWindow呼叫之後。注意,主視窗的視窗樣式包含WS_CLIPCHILDREN,這允許程式無須擦除對話方塊就能夠重畫主視窗。

如上所述,從CreateDialog傳回的對話方塊視窗代號存放在整體變數hDlgModeless中,並在訊息迴圈中被測試。不過,在這個程式中,不需要將代號存放在整體變數中,也不需要在呼叫IsDialogMessage之前測試這個值。訊息迴圈可以編寫如下:

圖11-4 COLORS2的螢幕顯示

while (GetMessage (&msg, NULL, 0, 0)) { if (!IsDialogMessage (hDlgModeless, &msg)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } }

由於對話方塊是在程式進入訊息迴圈前建立,並且直到程式結束時才會被清除,所以hDlgModeless的值將總是有效的。我加入了如下的處理方式,以便您可能會往對話方塊的視窗訊息處理程式中加入一段清除對話方塊的程式碼:

case WM_CLOSE : DestroyWindow (hDlg) ; hDlgModeless = NULL ; break ;

在原來的COLORS1程式中,SetWindowText在使用wsprintf將三個數值標籤轉換為文字之後才設定它們的值。敘述為:

i的值為目前處理的捲動列的ID,hwndValue是一個陣列,它包含顏色數值的三個靜態文字子視窗的視窗代號。

新版本使用SetDlgItemInt為每個子視窗的每個文字欄位設定一個號碼:

儘管SetDlgItemInt和與其對應的GetDlgItemInt在編輯控制項中用得最多,它們也可以用來設定其他控制項的文字欄位,如靜態文字控制項等。iCtrlID變數是捲動列的ID,給ID加上3使之變成對應數字標籤的ID。第三個參數是顏色值。通常,第四個參數表示第三個參數的值是解釋為有正負號的(第四個參數為TRUE)還是無正負號的(第四個參數為FALSE)。但是,對於這個程式,值的範圍是從0到256,所以這個參數沒有意義。

在將COLORS1轉換為COLORS2的程序中,我們把越來越多的工作交給了Windows。舊版本呼叫了CreateWindow 10次;而新版本只呼叫了CreateWindow和CreateDialog各一次。但是,如果您認為我們已經把呼叫CreateWindow的次數降到最少,那麼您就錯了,請看下一個程式。

HEXCALC:視窗還是對話方塊?

HEXCALC程式可能是寫程式偷懶的經典之作,如程式11-5所示。這個程式完全不呼叫CreateWindow,也不處理WM_PAINT訊息,不取得裝置內容,也不處理滑鼠訊息。但是它只用了不到150行的原始碼,就構成了一個具有完整鍵盤和滑鼠介面以及10種運算的十六進位計算機。計算機如圖11-5所示。

wsprintf (szBuffer, TEXT ("%i"), color[i]) ; SetWindowText (hwndValue[i], szBuffer) ;

SetDlgItemInt (hDlg, iCtrlID + 3, color [iCtrlID], FALSE) ;

程式11-5 HEXCALC HEXCALC.C /*------------------------------------------------------------------------ HEXCALC.C -- Hexadecimal Calculator (c) Charles Petzold, 1998 -------------------------------------------------------------------------*/ #include <windows.h> LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT ("HexCalc") ; HWND hwnd ; MSG msg ; WNDCLASS wndclass ; wndclass.style = CS_HREDRAW | CS_VREDRAW; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = DLGWINDOWEXTRA ; // Note! wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (hInstance, szAppName) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1) ; wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = szAppName ; if (!RegisterClass (&wndclass)) { MessageBox ( NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; } hwnd = CreateDialog (hInstance, szAppName, 0, NULL) ; ShowWindow (hwnd, iCmdShow) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } void ShowNumber (HWND hwnd, UINT iNumber) { TCHAR szBuffer[20] ; wsprintf (szBuffer, TEXT ("%X"), iNumber) ; SetDlgItemText (hwnd, VK_ESCAPE, szBuffer) ; } DWORD CalcIt (UINT iFirstNum, int iOperation, UINT iNum) { switch (iOperation) { case '=': return iNum ; case '+': return iFirstNum + iNum ; case '-': return iFirstNum - iNum ; case '*': return iFirstNum * iNum ; case '&': return iFirstNum & iNum ; case '|': return iFirstNum | iNum ; case '^': return iFirstNum ^ iNum ; case '<': return iFirstNum << iNum ; case '>': return iFirstNum >> iNum ; case '/': return iNum ? iFirstNum / iNum: MAXDWORD ; case '%': return iNum ? iFirstNum % iNum: MAXDWORD ; default : return 0 ; } } LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam) { static BOOL bNewNumber = TRUE ; static int iOperation = '=' ; static UINT iNumber, iFirstNum ; HWND hButton ; switch (message) { case WM_KEYDOWN: // left arrow --> backspace if (wParam != VK_LEFT) break ; wParam = VK_BACK ; // fall through case WM_CHAR: if ((wParam = (WPARAM) CharUpper ((TCHAR *) wParam)) == VK_RETURN) wParam = '=' ; if (hButton = GetDlgItem (hwnd, wParam)) { SendMessage (hButton, BM_SETSTATE, 1, 0) ; Sleep (100) ; SendMessage (hButton, BM_SETSTATE, 0, 0) ; } else { MessageBeep (0) ; break ; } // fall through case WM_COMMAND: SetFocus (hwnd) ; if (LOWORD (wParam) == VK_BACK) //backspace ShowNumber (hwnd, iNumber /= 16) ; else if (LOWORD (wParam) == VK_ESCAPE) // escape ShowNumber (hwnd, iNumber = 0) ; else if (isxdigit (LOWORD (wParam))) // hex digit { if (bNewNumber) { iFirstNum = iNumber ; iNumber = 0 ; } bNewNumber = FALSE ; if (iNumber <= MAXDWORD >> 4) ShowNumber (hwnd, iNumber = 16 * iNumber + wParam - (isdigit (wParam) ? '0': 'A' - 10)) ; else MessageBeep (0) ; } else // operation { if (!bNewNumber) ShowNumber (hwnd, iNumber = CalcIt (iFirstNum, iOperation, iNumber)) ; bNewNumber = TRUE ; iOperation = LOWORD (wParam) ; } return 0 ; case WM_DESTROY: PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }

HEXCALC.RC (摘錄) //Microsoft Developer Studio generated resource script. #include "resource.h" #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// // Icon HEXCALC ICON DISCARDABLE "HexCalc.ico" ///////////////////////////////////////////////////////////////////////////// #include "hexcalc.dlg"

HEXCALC.DLG /*-------------------------------- HEXCALC.DLG dialog script ----------------------------------*/ HexCalc DIALOG -1, -1, 102, 122 STYLE WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX CLASS "HexCalc" CAPTION "Hex Calculator" { PUSHBUTTON "D", 68, 8, 24, 14, 14 PUSHBUTTON "A", 65, 8, 40, 14, 14 PUSHBUTTON "7", 55, 8, 56, 14, 14 PUSHBUTTON "4", 52, 8, 72, 14, 14 PUSHBUTTON "1", 49, 8, 88, 14, 14 PUSHBUTTON "0", 48, 8, 104,14, 14 PUSHBUTTON "0", 27, 26, 4, 50, 14 PUSHBUTTON "E", 69, 26, 24, 14, 14 PUSHBUTTON "B", 66, 26, 40, 14, 14 PUSHBUTTON "8", 56, 26, 56, 14, 14 PUSHBUTTON "5", 53, 26, 72, 14, 14 PUSHBUTTON "2", 50, 26, 88, 14, 14 PUSHBUTTON "Back", 8, 26, 104,32, 14 PUSHBUTTON "C", 67, 44, 40, 14, 14 PUSHBUTTON "F", 70, 44, 24, 14, 14 PUSHBUTTON "9", 57, 44, 56, 14, 14 PUSHBUTTON "6", 54, 44, 72, 14, 14 PUSHBUTTON "3", 51, 44, 88, 14, 14 PUSHBUTTON "+", 43, 62, 24, 14, 14 PUSHBUTTON "-", 45, 62, 40, 14, 14 PUSHBUTTON "*", 42, 62, 56, 14, 14 PUSHBUTTON "/", 47, 62, 72, 14, 14 PUSHBUTTON "%", 37, 62, 88, 14, 14 PUSHBUTTON "Equals", 61, 62, 104,32, 14 PUSHBUTTON "&&",38, 80, 24, 14, 14 PUSHBUTTON "|", 124, 80, 40, 14, 14 PUSHBUTTON "^", 94, 80, 56, 14, 14 PUSHBUTTON "<", 60, 80, 72, 14, 14 PUSHBUTTON ">", 62, 80, 88, 14, 14 }

HEXCALC.ICO

圖11-5 HEXCALC的螢幕顯示

HEXCALC是一個普通的中序運算式計算機,使用C語言的符號表示方式進行計算。它對無正負號32位元整數作加、減、乘、除和取餘數運算,位元AND, OR, exclusive-OR運算,還有左右位移運算。被0除將導致結果被設定為FFFFFFFF。

在HEXCALC中既可以使用滑鼠又可以使用鍵盤。您從按鍵點入」或者輸入第一個數(最多8位元十六進位數位)開始,然後輸入運算子,然後是第二個數。接著,您可以透過單擊「Equals」按鈕或者按下等號鍵或Enter鍵便可以顯示運算結果。為了更正輸入,您可以使用「Back」按鈕、Backspace或者左箭頭鍵。單擊「display」方塊或者按下Esc鍵即可清除目前的輸入。

HEXCALC比較奇怪的一點是,螢幕上顯示的視窗似乎是普通的重疊式視窗與非模態對話方塊的混合體。一方面,HEXCALC的所有訊息都在函式的WndProc中處理,這個函式與通常的視窗訊息處理程式相似,該函式傳回一個長整數,它處理WM_DESTROY訊息,呼叫DefWindowProc,就像普通的視窗訊息處理程式一樣。另一方面,視窗是在WinMain中呼叫CreateDialog並使用HEXCALC.DLG中的對話方塊模板建立的。那麼,HEXCALC到底是一個普通的可重疊視窗,還是一個非模態對話方塊呢?

簡單的回答是,對話方塊就是視窗。通常,Windows使用它自己內部的視窗訊息處理程式處理對話方塊視窗的訊息,然後,Windows將這些訊息傳送給建立對話方塊的程式內的對話方塊程序。在HEXCALC中,我們讓Windows使用對話方塊模板建立一個視窗,但是自己寫程式處理這個視窗的訊息。

不幸的是,在Developer Studio的Dialog Editor中,對話方塊模板需要一些我們不能添加的東西。因此,對話方塊模板包含在HEXCALC.DLG檔案中,而且需要手工輸入。依照下面的方法,您可以將一個文字檔案添加到任何專案中:從「 File 」功能表選擇「 New 」,再選擇「 Files 」頁面標籤,然後從檔案型態列表中選擇「 Text File 」。像這樣的檔案-包含附加資源定義-需要包含在資源描述中。從「 View 」功能表選擇「 Resource Includes 」。這顯示一個對話方塊。在「Compile-time Directives」編輯欄輸入

這一行將插入到HEXCALC.RC資源描述中,像上面所顯示的一樣。

仔細看一下HEXCALC.DLG檔案中的對話方塊模板,您將發現HEXCALC如何為對話方塊使用它自己的視窗訊息處理程式。對話方塊模板的上方如下:

#include "hexcalc.dlg"

HexCalc DIALOG -1, -1, 102, 122 STYLE WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX CLASS "HexCalc" CAPTION "Hex Calculator"

注意諸如WS_OVERLAPPED和WS_MINIMIZEBOX等識別字,我們可以將它們用在CreateWindow呼叫中以建立普通的視窗。CLASS敘述是這個對話方塊與曾經建立過的對話方塊之間最重要的區別(而且它也是Developer Studio中的Dialog Editor不允許我們指定的)。當對話方塊模板省略了這個敘述時,Windows為對話方塊註冊一個視窗類別,並使用它自己的視窗訊息處理程式處理對話方塊訊息。這裏,包含CLASS敘述就告訴Windows將訊息發送到其他的地方-具體的說,就是發送到在HexCalc視窗類別中指定的視窗訊息處理程式。

HexCalc視窗類別是在HEXCALC的WinMain函式中註冊的,就像普通視窗的視窗類別一樣。但是,請注意有個十分重要的區別:WNDCLASS結構的cbWndExtra欄位設定為DLGWINDOWEXTRA。對於您自己註冊的對話方塊程序,這是必需的。

在註冊視窗類別之後,WinMain呼叫CreateDialog:

第二個參數(字串「HexCaEc」)是對話方塊模板的名字。第三個參數通常是父視窗的視窗代號,這裏設定為0,因為視窗沒有父視窗。最後一個參數,通常是對話方塊程序的位址,這裏不需要。因為Windows不會處理這些訊息,因而也不會將訊息發送給對話方塊程序。

這個CreateDialog呼叫與對話方塊模板一起,被Windows有效地轉換為一個CreateWindow呼叫。該CreateWindow呼叫的功能與下面的呼叫相同:

hwnd = CreateDialog (hInstance, szAppName, 0, NULL) ;

hwnd = CreateWindow (TEXT ("HexCalc"), TEXT ("Hex Calculator"), WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX, CW_USEDEFAULT, CW_USEDEFAULT, 102 * 4 / cxChar, 122 * 8 / cyChar, NULL, NULL, hInstance, NULL) ;

其中,cxChar和cyChar變數分別是系統字體字元的寬度和高度。

我們通過讓Windows來進行CreateWindow呼叫而收穫甚豐:Windows不會在建立彈出式視窗1後就停止,它還會為對話方塊模板中定義的其他29個子視窗按鍵控制項呼叫CreateWindow。所有這些控制項都給父視窗的視窗訊息處理程式發送WM_COMMAND訊息,該程序正是WndProc。對於建立一個包含許多子視窗的視窗來說,這是一個很好的技巧。

下面是使HEXCALC的程式碼量下降到最少的另一種方法:或許您會注意到HEXCALC沒有表頭檔案,表頭檔案中通常包含對話方塊模板中,需要為所有子視窗控制項定義的識別字。我們之所以可以不要這個檔案,是因為每個按鍵控制項的ID設定為出現在控制項上的文字的ASCII碼。這意味著,WndProc可以完全相同地對待WM_COMMAND訊息和WM_CHAR訊息。在每種情況下,wParam的低字組都是按鈕的ASCII碼。

當然,對鍵盤訊息進行一些處理是必要的。WndProc攔截WM_KEYDOWN訊息,將左箭頭鍵轉換為Backspace鍵。在處理WM_CHAR訊息時,WndProc將字元代碼轉換為大寫,Enter鍵轉換為等號鍵的ASCII碼。

WM_CHAR訊息的有效性是通過呼叫GetDlgItem來檢驗的。如果GetDlgItem函式傳回0,那麼鍵盤字元不是對話方塊模板中定義的ID之一。如果字元是ID之一,則通過給相應的按鈕發送一對BM_SETSTATE訊息,來使之閃爍:

if (hButton = GetDlgItem (hwnd, wParam)) { SendMessage (hButton, BM_SETSTATE, 1, 0) ; Sleep (100) ; SendMessage (hButton, BM_SETSTATE, 0, 0) ; }

這樣做,用最小的代價,卻為HEXCALC的鍵盤介面增色不少。Sleep函式將程式暫停100毫秒。這會防止按鈕被按得太快而讓人注意不到。

當WndProc處理WM_COMMAND訊息時,它總是將輸入焦點設定給父視窗:

否則,一旦使用滑鼠單擊某按鈕,輸入焦點就會切換到該按鈕上。

通用對話方塊

Windows的一個主要目的是推動標準的使用者介面。對許多常用的功能表項來說,這推行得很快,幾乎所有軟體廠商都採用Alt-File-Open選擇來打開一個檔案。然而,實際的檔案開啟對話方塊卻經常各不相同。

從Windows 3.1開始,對這個問題有了一個可行的解決方案,這是一種叫做「通用對話方塊程式庫」的增強。這個程式庫由幾個函式組成,這些函式啟動標準對話方塊來進行打開和儲存檔案、搜索和替換、選擇顏色、選擇字體(我將在本章討論以上的這些內容)以及列印(我將在 第十三章 討論)。

為了使用這些函式,您基本上都要初始化某一結構的各個欄位,並將該結構的指標傳送給通用對話方塊程式庫的某個函式,該函式會建立並顯示對話方塊。當使用者關閉對話方塊時,被呼叫的函式將控制權傳回給程式,您可以從傳送給它的結構中獲得資訊。

在使用通用對話方塊程式庫的任何C原始碼檔案時,您都需要含入COMMDLG.H表頭檔案。通用對話方塊的文件在/Platform SDK/User Interface Services/User Input/Common Dialog Box Library中。

增強POPPAD

當我們往 第十章的POPPAD 中增加功能表時,還有幾個標準功能表項沒有實作。現在我們已經準備好在POPPAD中加入打開檔案、讀入檔案以及在磁片上儲存編輯過檔案的功能。在處理中,我們還將在POPPAD中加入字體選擇和搜索替換功能。

實作POPPAD3程式的檔案如程式11-6所示。

case WM_COMMAND : SetFocus (hwnd) ;

程式11-6 POPPAD3 POPPAD.C /*------------------------------------------------------------------------ POPPAD.C -- Popup Editor (c) Charles Petzold, 1998 -------------------------------------------------------------------------*/ #include <windows.h> #include <commdlg.h> #include "resource.h" #define EDITID 1 #define UNTITLED TEXT ("(untitled)") LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; BOOL CALLBACK AboutDlgProc (HWND, UINT, WPARAM, LPARAM) ; // Functions in POPFILE.C void PopFileInitialize (HWND) ; BOOL PopFileOpenDlg (HWND, PTSTR, PTSTR) ; BOOL PopFileSaveDlg (HWND, PTSTR, PTSTR) ; BOOL PopFileRead (HWND, PTSTR) ; BOOL PopFileWrite (HWND, PTSTR) ; // Functions in POPFIND.C HWND PopFindFindDlg (HWND) ; HWND PopFindReplaceDlg (HWND) ; BOOL PopFindFindText (HWND, int *, LPFINDREPLACE) ; BOOL PopFindReplaceText (HWND, int *, LPFINDREPLACE) ; BOOL PopFindNextText (HWND, int *) ; BOOL PopFindValidFind (void) ; // Functions in POPFONT.C void PopFontInitialize (HWND) ; BOOL PopFontChooseFont (HWND) ; void PopFontSetFont (HWND) ; void PopFontDeinitialize (void) ; // Functions in POPPRNT.C BOOL PopPrntPrintFile (HINSTANCE, HWND, HWND, PTSTR) ; // Global variables static HWND hDlgModeless ; static TCHAR szAppName[] = TEXT ("PopPad") ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { MSG msg ; HWND hwnd ; HACCEL hAccel ; WNDCLASS wndclass ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (hInstance, szAppName) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; wndclass.lpszMenuName = szAppName ; wndclass.lpszClassName = szAppName ; if (!RegisterClass (&wndclass)) { MessageBox ( NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; } hwnd = CreateWindow ( szAppName, NULL, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, szCmdLine) ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; hAccel = LoadAccelerators (hInstance, szAppName) ; while (GetMessage (&msg, NULL, 0, 0)) { if (hDlgModeless == NULL || !IsDialogMessage (hDlgModeless, &msg)) { if (!TranslateAccelerator (hwnd, hAccel, &msg)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } } } return msg.wParam ; } void DoCaption (HWND hwnd, TCHAR * szTitleName) { TCHAR szCaption[64 + MAX_PATH] ; wsprintf (szCaption, TEXT ("%s - %s"), szAppName, szTitleName[0] ? szTitleName : UNTITLED) ; SetWindowText (hwnd, szCaption) ; } void OkMessage (HWND hwnd, TCHAR * szMessage, TCHAR * szTitleName) { TCHAR szBuffer[64 + MAX_PATH] ; wsprintf (szBuffer, szMessage, szTitleName[0] ? szTitleName : UNTITLED) ; MessageBox (hwnd, szBuffer, szAppName, MB_OK | MB_ICONEXCLAMATION) ; } short AskAboutSave (HWND hwnd, TCHAR * szTitleName) { TCHAR szBuffer[64 + MAX_PATH] ; int iReturn ; wsprintf (szBuffer, TEXT ("Save current changes in %s?"), szTitleName[0] ? szTitleName : UNTITLED) ; iReturn = MessageBox (hwnd, szBuffer, szAppName, MB_YESNOCANCEL | MB_ICONQUESTION) ; if (iReturn == IDYES) if (!SendMessage (hwnd, WM_COMMAND, IDM_FILE_SAVE, 0)) iReturn = IDCANCEL ; return iReturn ; } LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam) { static BOOL bNeedSave = FALSE ; static HINSTANCE hInst ; static HWND hwndEdit ; static int iOffset ; static TCHAR szFileName[MAX_PATH], szTitleName[MAX_PATH] ; static UINT messageFindReplace ; int iSelBeg, iSelEnd, iEnable ; LPFINDREPLACE pfr ; switch (message) { case WM_CREATE: hInst = ((LPCREATESTRUCT) lParam) -> hInstance ; // Create the edit control child window hwndEdit = CreateWindow (TEXT ("edit"), NULL, WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL | WS_BORDER | ES_LEFT | ES_MULTILINE | ES_NOHIDESEL | ES_AUTOHSCROLL | ES_AUTOVSCROLL, 0, 0, 0, 0, hwnd, (HMENU) EDITID, hInst, NULL) ; SendMessage (hwndEdit, EM_LIMITTEXT, 32000, 0L) ; // Initialize common dialog box stuff PopFileInitialize (hwnd) ; PopFontInitialize (hwndEdit) ; messageFindReplace = RegisterWindowMessage (FINDMSGSTRING) ; DoCaption (hwnd, szTitleName) ; return 0 ; case WM_SETFOCUS: SetFocus (hwndEdit) ; return 0 ; case WM_SIZE: MoveWindow (hwndEdit, 0, 0, LOWORD (lParam), HIWORD (lParam), TRUE) ; return 0 ; case WM_INITMENUPOPUP: switch (lParam) { case 1: // Edit menu // Enable Undo if edit control can do it EnableMenuItem ((HMENU) wParam, IDM_EDIT_UNDO, SendMessage (hwndEdit, EM_CANUNDO, 0, 0L) ? MF_ENABLED : MF_GRAYED) ; // Enable Paste if text is in the clipboard EnableMenuItem ((HMENU) wParam, IDM_EDIT_PASTE, IsClipboardFormatAvailable (CF_TEXT) ? MF_ENABLED : MF_GRAYED) ; // Enable Cut, Copy, and Del if text is selected SendMessage (hwndEdit, EM_GETSEL, (WPARAM) &iSelBeg, (LPARAM) &iSelEnd) ; iEnable = iSelBeg != iSelEnd ? MF_ENABLED : MF_GRAYED ; EnableMenuItem ((HMENU) wParam, IDM_EDIT_CUT, iEnable) ; EnableMenuItem ((HMENU) wParam, IDM_EDIT_COPY, iEnable) ; EnableMenuItem ((HMENU) wParam, IDM_EDIT_CLEAR, iEnable) ; break ; case 2: // Search menu // Enable Find, Next, and Replace if modeless // dialogs are not already active iEnable = hDlgModeless == NULL ? MF_ENABLED : MF_GRAYED ; EnableMenuItem ((HMENU) wParam, IDM_SEARCH_FIND, iEnable) ; EnableMenuItem ((HMENU) wParam, IDM_SEARCH_NEXT, iEnable) ; EnableMenuItem ((HMENU) wParam, IDM_SEARCH_REPLACE, iEnable) ; break ; } return 0 ; case WM_COMMAND: // Messages from edit control if (lParam && LOWORD (wParam) == EDITID) { switch (HIWORD (wParam)) { case EN_UPDATE : bNeedSave = TRUE ; return 0 ; case EN_ERRSPACE : case EN_MAXTEXT : MessageBox (hwnd, TEXT ("Edit control out of space."), szAppName, MB_OK | MB_ICONSTOP) ; return 0 ; } break ; } switch (LOWORD (wParam)) { // Messages from File menu case IDM_FILE_NEW: if (bNeedSave && IDCANCEL == AskAboutSave (hwnd, szTitleName)) return 0 ; SetWindowText (hwndEdit, TEXT ("\0")) ; szFileName[0] = '\0' ; szTitleName[0] = '\0' ; DoCaption (hwnd, szTitleName) ; bNeedSave = FALSE ; return 0 ; case IDM_FILE_OPEN: if (bNeedSave && IDCANCEL == AskAboutSave (hwnd, szTitleName)) return 0 ; if (PopFileOpenDlg (hwnd, szFileName, szTitleName)) { if (!PopFileRead (hwndEdit, szFileName)) { OkMessage (hwnd, TEXT ("Could not read file %s!"), szTitleName) ; szFileName[0] = '\0' ; szTitleName[0] = '\0' ; } } DoCaption (hwnd, szTitleName) ; bNeedSave = FALSE ; return 0 ; case IDM_FILE_SAVE: if (szFileName[0]) { if (PopFileWrite (hwndEdit, szFileName)) { bNeedSave = FALSE ; return 1 ; } else { OkMessage (hwnd, TEXT ("Could not write file %s"), szTitleName) ; return 0 ; } } //fall through case IDM_FILE_SAVE_AS: if (PopFileSaveDlg (hwnd, szFileName, szTitleName)) { DoCaption (hwnd, szTitleName) ; if (PopFileWrite (hwndEdit, szFileName)) { bNeedSave = FALSE ; return 1 ; } else { OkMessage (hwnd, TEXT ("Could not write file %s"), szTitleName) ; return 0 ; } } return 0 ; case IDM_FILE_PRINT: if (!PopPrntPrintFile (hInst, hwnd, hwndEdit, szTitleName)) OkMessage ( hwnd, TEXT ("Could not print file %s"), szTitleName) ; return 0 ; case IDM_APP_EXIT: SendMessage (hwnd, WM_CLOSE, 0, 0) ; return 0 ; // Messages from Edit menu case IDM_EDIT_UNDO: SendMessage (hwndEdit, WM_UNDO, 0, 0) ; return 0 ; case IDM_EDIT_CUT: SendMessage (hwndEdit, WM_CUT, 0, 0) ; return 0 ; case IDM_EDIT_COPY: SendMessage (hwndEdit, WM_COPY, 0, 0) ; return 0 ; case IDM_EDIT_PASTE: SendMessage (hwndEdit, WM_PASTE, 0, 0) ; return 0 ; case IDM_EDIT_CLEAR: SendMessage (hwndEdit, WM_CLEAR, 0, 0) ; return 0 ; case IDM_EDIT_SELECT_ALL: SendMessage (hwndEdit, EM_SETSEL, 0, -1) ; return 0 ; // Messages from Search menu case IDM_SEARCH_FIND: SendMessage (hwndEdit, EM_GETSEL, 0, (LPARAM) &iOffset) ; hDlgModeless = PopFindFindDlg (hwnd) ; return 0 ; case IDM_SEARCH_NEXT: SendMessage (hwndEdit, EM_GETSEL, 0, (LPARAM) &iOffset) ; if (PopFindValidFind ()) PopFindNextText (hwndEdit, &iOffset) ; else hDlgModeless = PopFindFindDlg (hwnd) ; return 0 ; case IDM_SEARCH_REPLACE: SendMessage (hwndEdit, EM_GETSEL, 0, (LPARAM) &iOffset) ; hDlgModeless = PopFindReplaceDlg (hwnd) ; return 0 ; case IDM_FORMAT_FONT: if (PopFontChooseFont (hwnd)) PopFontSetFont (hwndEdit) ; return 0 ; // Messages from Help menu case IDM_HELP: OkMessage (hwnd, TEXT ("Help not yet implemented!"), TEXT ("\0")) ; return 0 ; case IDM_APP_ABOUT: DialogBox (hInst, TEXT ("AboutBox"), hwnd, AboutDlgProc) ; return 0 ; } break ; case WM_CLOSE: if (!bNeedSave || IDCANCEL != AskAboutSave (hwnd, szTitleName)) DestroyWindow (hwnd) ; return 0 ; case WM_QUERYENDSESSION : if (!bNeedSave || IDCANCEL != AskAboutSave (hwnd, szTitleName)) return 1 ; return 0 ; case WM_DESTROY: PopFontDeinitialize () ; PostQuitMessage (0) ; return 0 ; default: // Process "Find-Replace" messages if (message == messageFindReplace) { pfr = (LPFINDREPLACE) lParam ; if (pfr->Flags & FR_DIALOGTERM) hDlgModeless = NULL ; if (pfr->Flags & FR_FINDNEXT) if (!PopFindFindText (hwndEdit, &iOffset, pfr)) OkMessage (hwnd, TEXT ("Text not found!"), TEXT ("\0")) ; if (pfr->Flags & FR_REPLACE || pfr->Flags & FR_REPLACEALL) if (!PopFindReplaceText (hwndEdit, &iOffset, pfr)) OkMessage (hwnd, TEXT ("Text not found!"), TEXT ("\0")) ; if (pfr->Flags & FR_REPLACEALL) while (PopFindReplaceText (hwndEdit, &iOffset, pfr)) ; return 0 ; } break ; } return DefWindowProc (hwnd, message, wParam, lParam) ; } BOOL CALLBACK AboutDlgProc (HWND hDlg, UINT message,WPARAM wParam, LPARAM lParam) { switch (message) { case WM_INITDIALOG: return TRUE ; case WM_COMMAND: switch (LOWORD (wParam)) { case IDOK: EndDialog (hDlg, 0) ; return TRUE ; } break ; } return FALSE ; }

POPFILE.C /*-------------------------------------------------------------------------- POPFILE.C -- Popup Editor File Functions ------------------------------------------------------------------------*/ #include <windows.h> #include <commdlg.h> static OPENFILENAME ofn ; void PopFileInitialize (HWND hwnd) { static TCHAR szFilter[] = TEXT ("Text Files (*.TXT)\0*.txt\0") \ TEXT ("ASCII Files (*.ASC)\0*.asc\0") \ TEXT ("All Files (*.*)\0*.*\0\0") ; ofn.lStructSize = sizeof (OPENFILENAME) ; ofn.hwndOwner = hwnd ; ofn.hInstance = NULL ; ofn.lpstrFilter = szFilter ; ofn.lpstrCustomFilter = NULL ; ofn.nMaxCustFilter = 0 ; ofn.nFilterIndex = 0 ; ofn.lpstrFile = NULL ; // Set in Open and Close functions ofn.nMaxFile = MAX_PATH ; ofn.lpstrFileTitle = NULL ; // Set in Open and Close functions ofn.nMaxFileTitle = MAX_PATH ; ofn.lpstrInitialDir = NULL ; ofn.lpstrTitle = NULL ; ofn.Flags = 0 ; // Set in Open and Close functions ofn.nFileOffset = 0 ; ofn.nFileExtension = 0 ; ofn.lpstrDefExt = TEXT ("txt") ; ofn.lCustData = 0L ; ofn.lpfnHook = NULL ; ofn.lpTemplateName = NULL ; } BOOL PopFileOpenDlg (HWND hwnd, PTSTR pstrFileName, PTSTR pstrTitleName) { ofn.hwndOwner = hwnd ; ofn.lpstrFile = pstrFileName ; ofn.lpstrFileTitle = pstrTitleName ; ofn.Flags = OFN_HIDEREADONLY | OFN_CREATEPROMPT ; return GetOpenFileName (&ofn) ; } BOOL PopFileSaveDlg (HWND hwnd, PTSTR pstrFileName, PTSTR pstrTitleName) { ofn.hwndOwner = hwnd ; ofn.lpstrFile = pstrFileName ; ofn.lpstrFileTitle = pstrTitleName ; ofn.Flags = OFN_OVERWRITEPROMPT ; return GetSaveFileName (&ofn) ; } BOOL PopFileRead (HWND hwndEdit, PTSTR pstrFileName) { BYTE bySwap ; DWORD dwBytesRead ; HANDLE hFile ; int i, iFileLength, iUniTest ; PBYTE pBuffer, pText, pConv ; // Open the file. if (INVALID_HANDLE_VALUE == (hFile = CreateFile (pstrFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL))) return FALSE ; // Get file size in bytes and allocate memory for read. // Add an extra two bytes for zero termination. iFileLength = GetFileSize (hFile, NULL) ; pBuffer = malloc (iFileLength + 2) ; // Read file and put terminating zeros at end. ReadFile (hFile, pBuffer, iFileLength, &dwBytesRead, NULL) ; CloseHandle (hFile) ; pBuffer[iFileLength] = '\0' ; pBuffer[iFileLength + 1] = '\0' ; // Test to see if the text is Unicode iUniTest = IS_TEXT_UNICODE_SIGNATURE | IS_TEXT_UNICODE_REVERSE_SIGNATURE ; if (IsTextUnicode (pBuffer, iFileLength, &iUniTest)) { pText = pBuffer + 2 ; iFileLength -= 2 ; if (iUniTest & IS_TEXT_UNICODE_REVERSE_SIGNATURE) { for (i = 0 ; i < iFileLength / 2 ; i++) { bySwap = ((BYTE *) pText) [2 * i] ; ((BYTE *) pText) [2 * i] = ((BYTE *) pText) [2 * i + 1] ; ((BYTE *) pText) [2 * i + 1] = bySwap ; } } // Allocate memory for possibly converted string pConv = malloc (iFileLength + 2) ; // If the edit control is not Unicode, convert Unicode text to // non-Unicode (i.e., in general, wide character). #ifndef UNICODE WideCharToMultiByte (CP_ACP, 0, (PWSTR) pText, -1, pConv, iFileLength + 2, NULL, NULL) ; // If the edit control is Unicode, just copy the string #else lstrcpy ((PTSTR) pConv, (PTSTR) pText) ; #endif } else // the file is not Unicode { pText = pBuffer ; // Allocate memory for possibly converted string. pConv = malloc (2 * iFileLength + 2) ; // If the edit control is Unicode, convert ASCII text. #ifdef UNICODE MultiByteToWideChar (CP_ACP, 0, pText, -1, (PTSTR) pConv, iFileLength + 1) ; // If not, just copy buffer #else lstrcpy ((PTSTR) pConv, (PTSTR) pText) ; #endif } SetWindowText (hwndEdit, (PTSTR) pConv) ; free (pBuffer) ; free (pConv) ; return TRUE ; } BOOL PopFileWrite (HWND hwndEdit, PTSTR pstrFileName) { DWORD dwBytesWritten ; HANDLE hFile ; int iLength ; PTSTR pstrBuffer ; WORD wByteOrderMark = 0xFEFF ; // Open the file, creating it if necessary if (INVALID_HANDLE_VALUE == (hFile = CreateFile (pstrFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL))) return FALSE ; // Get the number of characters in the edit control and allocate // memory for them. iLength = GetWindowTextLength (hwndEdit) ; pstrBuffer = (PTSTR) malloc ((iLength + 1) * sizeof (TCHAR)) ; if (!pstrBuffer) { CloseHandle (hFile) ; return FALSE ; } // If the edit control will return Unicode text, write the // byte order mark to the file. #ifdef UNICODE WriteFile (hFile, &wByteOrderMark, 2, &dwBytesWritten, NULL) ; #endif // Get the edit buffer and write that out to the file. GetWindowText (hwndEdit, pstrBuffer, iLength + 1) ; WriteFile (hFile, pstrBuffer, iLength * sizeof (TCHAR), &dwBytesWritten, NULL) ; if ((iLength * sizeof (TCHAR)) != (int) dwBytesWritten) { CloseHandle (hFile) ; free (pstrBuffer) ; return FALSE ; } CloseHandle (hFile) ; free (pstrBuffer) ; return TRUE ; }

POPFIND.C /*-------------------------------------------------------------------------- POPFIND.C -- Popup Editor Search and Replace Functions ------------------------------------------------------------------------*/ #include <windows.h> #include <commdlg.h> #include <tchar.h> // for _tcsstr (strstr for Unicode & non-Unicode) #define MAX_STRING_LEN 256 static TCHAR szFindText [MAX_STRING_LEN] ; static TCHAR szReplText [MAX_STRING_LEN] ; HWND PopFindFindDlg (HWND hwnd) { static FINDREPLACE fr ; // must be static for modeless dialog!!! fr.lStructSize = sizeof (FINDREPLACE) ; fr.hwndOwner = hwnd ; fr.hInstance = NULL ; fr.Flags = FR_HIDEUPDOWN | FR_HIDEMATCHCASE | FR_HIDEWHOLEWORD ; fr.lpstrFindWhat = szFindText ; fr.lpstrReplaceWith = NULL ; fr.wFindWhatLen = MAX_STRING_LEN ; fr.wReplaceWithLen = 0 ; fr.lCustData = 0 ; fr.lpfnHook = NULL ; fr.lpTemplateName = NULL ; return FindText (&fr) ; } HWND PopFindReplaceDlg (HWND hwnd) { static FINDREPLACE fr ; // must be static for modeless dialog!!! fr.lStructSize = sizeof (FINDREPLACE) ; fr.hwndOwner = hwnd ; fr.hInstance = NULL ; fr.Flags = FR_HIDEUPDOWN | FR_HIDEMATCHCASE | FR_HIDEWHOLEWORD ; fr.lpstrFindWhat = szFindText ; fr.lpstrReplaceWith = szReplText ; fr.wFindWhatLen = MAX_STRING_LEN ; fr.wReplaceWithLen = MAX_STRING_LEN ; fr.lCustData = 0 ; fr.lpfnHook = NULL ; fr.lpTemplateName = NULL ; return ReplaceText (&fr) ; } BOOL PopFindFindText (HWND hwndEdit, int * piSearchOffset, LPFINDREPLACE pfr) { int iLength, iPos ; PTSTR pstrDoc, pstrPos ; // Read in the edit document iLength = GetWindowTextLength (hwndEdit) ; if (NULL == (pstrDoc = (PTSTR) malloc ((iLength + 1) * sizeof (TCHAR)))) return FALSE ; GetWindowText (hwndEdit, pstrDoc, iLength + 1) ; // Search the document for the find string pstrPos = _tcsstr (pstrDoc + * piSearchOffset, pfr->lpstrFindWhat) ; free (pstrDoc) ; // Return an error code if the string cannot be found if (pstrPos == NULL) return FALSE ; // Find the position in the document and the new start offset iPos = pstrPos - pstrDoc ; * piSearchOffset = iPos + lstrlen (pfr->lpstrFindWhat) ; // Select the found text SendMessage (hwndEdit, EM_SETSEL, iPos, * piSearchOffset) ; SendMessage (hwndEdit, EM_SCROLLCARET, 0, 0) ; return TRUE ; } BOOL PopFindNextText (HWND hwndEdit, int * piSearchOffset) { FINDREPLACE fr ; fr.lpstrFindWhat = szFindText ; return PopFindFindText (hwndEdit, piSearchOffset, &fr) ; } BOOL PopFindReplaceText (HWND hwndEdit, int * piSearchOffset, LPFIND,REPLACE pfr) { // Find the text if (!PopFindFindText (hwndEdit, piSearchOffset, pfr)) return FALSE ; // Replace it SendMessage ( hwndEdit, EM_REPLACESEL, 0, (LPARAM) pfr-> lpstrReplaceWith) ; return TRUE ; } BOOL PopFindValidFind (void) { return * szFindText != '\0' ; }

POPFONT.C /*---------------------------------------------------- POPFONT.C -- Popup Editor Font Functions ------------------------------------------------------*/ #include <windows.h> #include <commdlg.h> static LOGFONT logfont ; static HFONT hFont ; BOOL PopFontChooseFont (HWND hwnd) { CHOOSEFONT cf ; cf.lStructSize = sizeof (CHOOSEFONT) ; cf.hwndOwner = hwnd ; cf.hDC = NULL ; cf.lpLogFont = &logfont ; cf.iPointSize = 0 ; cf.Flags = CF_INITTOLOGFONTSTRUCT | CF_SCREENFONTS | CF_EFFECTS ; cf.rgbColors = 0 ; cf.lCustData = 0 ; cf.lpfnHook = NULL ; cf.lpTemplateName = NULL ; cf.hInstance = NULL ; cf.lpszStyle = NULL ; cf.nFontType = 0 ; // Returned from ChooseFont cf.nSizeMin = 0 ; cf.nSizeMax = 0 ; return ChooseFont (&cf) ; } void PopFontInitialize (HWND hwndEdit) { GetObject (GetStockObject (SYSTEM_FONT), sizeof (LOGFONT), (PTSTR) &logfont) ; hFont = CreateFontIndirect (&logfont) ; SendMessage (hwndEdit, WM_SETFONT, (WPARAM) hFont, 0) ; } void PopFontSetFont (HWND hwndEdit) { HFONT hFontNew ; RECT rect ; hFontNew = CreateFontIndirect (&logfont) ; SendMessage (hwndEdit, WM_SETFONT, (WPARAM) hFontNew, 0) ; DeleteObject (hFont) ; hFont = hFontNew ; GetClientRect (hwndEdit, &rect) ; InvalidateRect (hwndEdit, &rect, TRUE) ; } void PopFontDeinitialize (void) { DeleteObject (hFont) ; }

POPPRNT0.C /*------------------------------------------------------------------------ POPPRNT0.C -- Popup Editor Printing Functions (dummy version) --------------------------------------------------------------------------*/ #include <windows.h> BOOL PopPrntPrintFile ( HINSTANCE hInst, HWND hwnd, HWND hwndEdit, PTSTR pstrTitleName) { return FALSE ; }

POPPAD.RC (摘錄) //Microsoft Developer Studio generated resource script. #include "resource.h" #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// // Dialog ABOUTBOX DIALOG DISCARDABLE 32, 32, 180, 100 STYLE DS_MODALFRAME | WS_POPUP FONT 8, "MS Sans Serif" BEGIN DEFPUSHBUTTON "OK",IDOK,66,80,50,14 ICON "POPPAD",IDC_STATIC,7,7,20,20 CTEXT "PopPad",IDC_STATIC,40,12,100,8 CTEXT "Popup Editor for Windows",IDC_STATIC,7,40,166,8 CTEXT "(c) Charles Petzold, 1998",IDC_STATIC,7,52,166,8 END PRINTDLGBOX DIALOG DISCARDABLE 32, 32, 186, 95 STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU CAPTION "PopPad" FONT 8, "MS Sans Serif" BEGIN PUSHBUTTON "Cancel",IDCANCEL,67,74,50,14 CTEXT "Sending",IDC_STATIC,8,8,172,8 CTEXT "",IDC_FILENAME,8,28,172,8 CTEXT "to print spooler.",IDC_STATIC,8,48,172,8 END ///////////////////////////////////////////////////////////////////////////// // Menu POPPAD MENU DISCARDABLE BEGIN POPUP "&File" BEGIN MENUITEM "&New\tCtrl+N", IDM_FILE_NEW MENUITEM "&Open...\tCtrl+O",IDM_FILE_OPEN MENUITEM "&Save\tCtrl+S", IDM_FILE_SAVE MENUITEM "Save &As...", IDM_FILE_SAVE_AS MENUITEM SEPARATOR MENUITEM "&Print\tCtrl+P", IDM_FILE_PRINT MENUITEM SEPARATOR MENUITEM "E&xit", IDM_APP_EXIT END POPUP "&Edit" BEGIN MENUITEM "&Undo\tCtrl+Z", IDM_EDIT_UNDO MENUITEM SEPARATOR MENUITEM "Cu&t\tCtrl+X", IDM_EDIT_CUT MENUITEM "&Copy\tCtrl+C", IDM_EDIT_COPY MENUITEM "&Paste\tCtrl+V", IDM_EDIT_PASTE MENUITEM "De&lete\tDel", IDM_EDIT_CLEAR MENUITEM SEPARATOR MENUITEM "&Select All", IDM_EDIT_SELECT_ALL END POPUP "&Search" BEGIN MENUITEM "&Find...\tCtrl+F",IDM_SEARCH_FIND MENUITEM "Find &Next\tF3", IDM_SEARCH_NEXT MENUITEM "&Replace...\tCtrl+R", IDM_SEARCH_REPLACE END POPUP "F&ormat" BEGIN MENUITEM "&Font...", END POPUP "&Help" BEGIN MENUITEM "&Help", IDM_HELP MENUITEM "&About PopPad...", IDM_APP_ABOUT END END ///////////////////////////////////////////////////////////////////////////// // Accelerator POPPAD ACCELERATORS DISCARDABLE BEGIN VK_BACK, IDM_EDIT_UNDO, VIRTKEY, ALT, NOINVERT VK_DELETE, IDM_EDIT_CLEAR, VIRTKEY, NOINVERT VK_DELETE, IDM_EDIT_CUT, VIRTKEY, SHIFT, NOINVERT VK_F1, IDM_HELP, VIRTKEY, NOINVERT VK_F3, IDM_SEARCH_NEXT, VIRTKEY, NOINVERT VK_INSERT, IDM_EDIT_COPY, VIRTKEY, CONTROL, NOINVERT VK_INSERT, IDM_EDIT_PASTE, VIRTKEY, SHIFT, NOINVERT "^C", IDM_EDIT_COPY, ASCII, NOINVERT "^F", IDM_SEARCH_FIND, ASCII, NOINVERT "^N", IDM_FILE_NEW, ASCII, NOINVERT "^O", IDM_FILE_OPEN, ASCII, NOINVERT "^P", IDM_FILE_PRINT, ASCII, NOINVERT "^R", IDM_SEARCH_REPLACE, ASCII, NOINVERT "^S", IDM_FILE_SAVE, ASCII, NOINVERT "^V", IDM_EDIT_PASTE, ASCII, NOINVERT "^X", IDM_EDIT_CUT, ASCII, NOINVERT "^Z", IDM_EDIT_UNDO, ASCII, NOINVERT END ///////////////////////////////////////////////////////////////////////////// // Icon POPPAD ICON DISCARDABLE "poppad.ico"

RESOURCE.H (摘錄) // Microsoft Developer Studio generated include file. // Used by poppad.rc #define IDC_FILENAME 1000 #define IDM_FILE_NEW 40001 #define IDM_FILE_OPEN 40002 #define IDM_FILE_SAVE 40003 #define IDM_FILE_SAVE_AS 40004 #define IDM_FILE_PRINT 40005 #define IDM_APP_EXIT 40006 #define IDM_EDIT_UNDO 40007 #define IDM_EDIT_CUT 40008 #define IDM_EDIT_COPY 40009 #define IDM_EDIT_PASTE 40010 #define IDM_EDIT_CLEAR 40011 #define IDM_EDIT_SELECT_ALL 40012 #define IDM_SEARCH_FIND 40013 #define IDM_SEARCH_NEXT 40014 #define IDM_SEARCH_REPLACE 40015 #define IDM_FORMAT_FONT 40016 #define IDM_HELP 40017 #define IDM_APP_ABOUT 40018

POPPAD.ICO

為了避免在 第十三章 中重複原始碼,我在POPPAD.RC的功能表中加入了列印專案和一些其他的支援。

POPPAD.C包含了程式中所有的基本原始碼。POPFILE.C具有啟動File Open和File Save對話方塊的程式碼,它還包含檔案I/O常式。POPFIND.C中包含了搜尋和替換文字功能。POPFONT.C包含了字體選擇功能。POPPRNT0.C不完成什麼工作:在 第十三章 中將使用POPPRNT.C替換POPPRNT0.C以建立最終的POPPAD程式。

讓我們先來看一看POPPAD.C。POPPAD.C含有兩個檔案名字串:第一個,儲存在WndProc,名稱為szFileName,含有詳細的驅動器名稱、路徑名稱和檔案名稱;第二個,儲存為szTitleName,是程式本身的檔案名稱。它用在POPPAD3的DoCaption函式中,以便將檔案名稱顯示在視窗的標題列上;也用在OKMessage函式和AskAboutSave函式中,以便向使用者顯示訊息方塊。

POPFILE.C包含了幾個顯示「File Open」和「File Save」對話方塊以及實際執行檔案I/O的函式。對話方塊是使用函式GetOpenFileName和GetSaveFileName來顯示的。這兩個函式都使用一個型態為OPENFILENAME的結構,這個結構在COMMDLG.H中定義。在POPFILE.C中,使用了一個該結構型態的整體變數,取名為ofn。ofn的大多數欄位在PopFileInitialize函式中被初始化,POPPAD.C在WndProc中處理WM_CREATE訊息時呼叫該函式。

將ofn作為靜態整體結構變數會比較方便,因為GetOpenFileName和GetSaveFileName給該結構傳回的一些資訊,並將在以後呼叫這些函式時用到。

儘管通用對話方塊具有許多選項-包括設定自己的對話方塊模板,以及為對話方塊程序增加「掛勾(hook)」-POPFILE.C中使用的「File Open」和「File Save」對話方塊是最基本的。OPENFILENAME結構中被設定的欄位只有lStructSize(結構的長度)、hwndOwner(對話方塊擁有者)、lpstrFilter(下面將簡要討論)、lpstrFile和nMaxFile(指向接收完整檔案名稱的緩衝區指標和該緩衝區的大小)、lpstrFileTitle和nMaxFileTitle(檔案名稱緩衝區及其大小)、Flags(設定對話方塊的選項)和lpstrDefExt(如果使用者在對話方塊中輸入檔案名時不指定檔案副檔名,那麼它就是內定的檔案副檔名)。

當使用者在「File」功能表中選擇「Open」時,POPPAD3呼叫POPFILE的PopFileOpenDlg函式,將視窗代號、一個指向檔案名稱緩衝區的指標和一個指向檔案標題緩衝區的指標傳給它。PopFileOpenDlg恰當地設定OPENFILENAME結構的hwndOwner、lpstrFile和lpstrFileTitle欄位,將Flags設定為OFN_ CREATEPROMPT,然後呼叫GetOpenFileName,顯示如圖11-6所示的普通對話方塊。

當使用者結束這個對話方塊時,GetOpenFileName函式傳回。OFN_CREATEPROMPT旗標指示GetOpenFileName顯示一個訊息方塊,詢問使用者如果所選檔案不存在,是否要建立該檔案。

左下角的下拉式清單方塊列出了將要顯示在檔案列表中的檔案型態,此清單方塊被稱為「篩選清單」。使用者可以通過從下拉式清單方塊列表中選擇另一種檔案型態,來改變篩選條件。在POPFILE.C的PopFileInitialize函式中,我在變數szFilter(一個字串陣列)中為三種型態的檔案定義了一個篩檢清單:帶有.TXT副檔名的文字檔案、帶有.ASC副檔名的ASCII檔案和所有檔案。OPENFILENAME結構的lpstrFilter欄位儲存指向此陣列第一個字串的指標。

如果使用者在對話方塊處於活動狀態時改變了篩選條件,那麼OPENFILENAME的nFilterIndex欄位反映出使用者的選擇。由於該結構是靜態變數,下次啟動對話方塊時,篩選條件將被設定為選中的檔案型態。

POPFILE.C中的PopFileSaveDlg函式與此類似,它將Flags參數設定為OFN_OVERWRITEPROMPT,並呼叫GetSaveFileName啟動「File Save」對話方塊。OFN_OVERWRITEPROMPT旗標導致顯示一個訊息方塊,如果被選檔案已經存在,那麼將詢問使用者是否覆蓋該檔案。

Unicode檔案I/O

對於本書中的大多數程式,您都不必注意Unicode和非Unicode版的區別。例如,在POPPAD3的Unicode中,編輯控制項將保留Unicode文字和使用Unicode字串的所有通用對話方塊。例如,當程式需要搜索和替換時,所有的操作都會處理Unicode字串,而不需要轉換。

不過,POPPAD3得處理檔案I/O,也就是說,程式不能閉門造車。如果Unicode版的POPPAD3獲得了編輯緩衝區的內容並將其寫入磁片,檔案將是使用Unicode存放的。如果非Unicode版的POPPAD3讀取了該檔案,並將其寫入編輯緩衝區,其結果將是一堆垃圾。Unicode版讀取由非Unicode版儲存的檔案時也會這樣。

解決的辦法在於辨別和轉換。首先,在POPFILE.C的PopFileWrite函式中,您將看到Unicode版的程式將在檔案的開始位置寫入0xFEFF。這定義為位元組順序標記,以表示文字檔案含有Unicode文字。

其次,在PopFileRead函式中,程式用IsTextUnicode函式來決定檔案是否含有位元組順序標記。此函式甚至檢測位元組順序標記是否反向了,亦即Unicode文字檔案在Macintosh或者其他使用與Intel處理器相反的位元組順序的機器上建立的。這時,位元組的順序都經過翻轉。如果檔案是Unicode版,但是被非Unicode版的POPPAD3讀取,這時,文字將被WideCharToMultiChar轉換。WideCharToMultiChar實際上是一個寬字元ANSI函式(除非您執行遠東版的Windows)。只有這時文字才能放入編輯緩衝區。

同樣地,如果檔案是非Unicode文字檔案,而執行的是Unicode版的程式,那麼文字必須用MultiCharToWideChar轉換。

改變字體

我們將在 第十七章 `詳細討論字體,但那些都不能代替通用對話方塊函式來選擇字體。

在WM_CREATE訊息處理期間,POPFONT.C中的POPPAD呼叫PopFontInitialize。這個函式取得一個依據系統字體建立的LOGFONT結構,由此建立一種字體,並向編輯控制項發送一個WM_SETFONT訊息來設定一種新的字體(內定編輯控制項字體是系統字體,而PopFontInitialize為編輯控制項建立一種新的字體,因為最終該字體將被刪除,而刪除現有系統字體是不明智的)。

當POPPAD收到來自程式的字體選項的WM_COMMAND訊息時,它呼叫PopFontChooseFont。這個函式初始化一個CHOOSEFONT結構,然後呼叫ChooseFont顯示字體選擇對話方塊。如果使用者按下「OK」按鈕,那麼ChooseFont將傳回TRUE。隨後,POPPAD呼叫PopFontSetFont來設定編輯控制項中的新字體,舊字體將被刪除。

最後,在WM_DESTROY訊息處理期間,POPPAD呼叫PopFontDeinitialize來刪除最近一次由PopFontSetFont建立的字體。

搜尋與替換

通用對話方塊程式庫也提供兩個用於文字搜尋和替換函式的對話方塊,這兩個函式(FindText和ReplaceText)使用一個型態為FINDREPLACE的結構。圖10-11中所示的POPFIND.C檔案有兩個常式(PopFindFindDlg和PopFindReplaceDlg)呼叫這些函式,還有兩個函式在編輯控制項中搜尋和替換文字。

使用搜尋和替換函式有一些考慮。首先,它們啟動的對話方塊是非模態對話方塊,這意味著必須改寫訊息迴圈,以便在對話方塊活動時呼叫IsDialogMessage。第二,傳送給FindText和ReplaceText的FINDREPLACE結構必須是一個靜態變數,因為對話方塊是模態的,函式在對話方塊顯示之後傳回,而不是在對話方塊結束之後傳回;而對話方塊程序必須仍然能夠存取該結構。

第三,在顯示FindText和ReplaceText對話方塊時,它們通過一條特殊訊息與擁有者視窗聯絡,訊息編號可以通過以FINDMSGSTRING為參數呼叫RegisterWindowMessage函式來獲得。這是在WndProc中處理WM_CREATE訊息時完成的,訊息號存放在靜態變數中。

在處理內定訊息時,WndProc將訊息變數與RegisterWindowMessage傳回的值相比較。lParam訊息參數是一個指向FINDREPLACE結構的指標,Flags欄位指示使用者使用對話方塊是為了搜尋文字還是替換文字,以及是否要終止對話方塊。POPPAD3是呼叫POPFIND.C中的PopFindFindText和PopFindReplaceText函式來執行搜尋和替換功能的。

只呼叫一個函式的Windows程式

到現在為止,我們已經說明了兩個程式,讓您瀏覽選擇顏色,這兩個程式分別是第九章中的 COLORS1 和本章中的 COLORS2 。現在是講解COLORS3的時候了,這個程式只有一個Windows函式呼叫。COLORS3的原始碼如程式11-7所示。

COLORS3所呼叫的唯一Windows函式是ChooseColor,這也是通用對話方塊程式庫中的函式,它顯示如圖11-7所示的對話方塊。顏色選擇類似於COLORS1和COLORS2,但是它與使用者交談互動能力更強。

圖11-6 「File Open」對話方塊

程式11-7 COLORS3 COLORS3.C /*------------------------------------------------------------------------- COLORS3.C -- Version using Common Dialog Box (c) Charles Petzold, 1998 --------------------------------------------------------------------------*/ #include <windows.h> #include <commdlg.h> int WINAPI WinMain ( HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static CHOOSECOLOR cc ; static COLORREF crCustColors[16] ; cc.lStructSize = sizeof (CHOOSECOLOR) ; cc.hwndOwner = NULL ; cc.hInstance = NULL ; cc.rgbResult = RGB (0x80, 0x80, 0x80) ; cc.lpCustColors = crCustColors ; cc.Flags = CC_RGBINIT | CC_FULLOPEN ; cc.lCustData = 0 ; cc.lpfnHook = NULL ; cc.lpTemplateName = NULL ; return ChooseColor (&cc) ; }

圖11-7 COLORS3的螢幕顯示

ChooseColor函式使用一個CHOOSECOLOR型態的結構和含有16個DWORD的陣列來存放常用顏色,使用者將從對話方塊中選擇這些顏色之一。rgbResult欄位可以初始化為一個顏色值,如果Flags欄位的CC_RGBINIT旗標被設立,則顯示該顏色。通常在使用這個函式時,rgbResult將被設定為使用者選擇的顏色。

請注意,Color對話方塊的hwndOwner欄位被設定為NULL。在ChooseColor函式呼叫DialogBox以顯示對話方塊時,DialogBox的第三個參數也被設定為NULL。這是完全合法的,其含義是對話方塊不為另一個視窗所擁有。對話方塊的標題將顯示在工作列中,而對話方塊就像一個普通的視窗那樣執行。

您也可以在自己程式的對話方塊中使用這種技巧。使Windows程式只建立對話方塊,其他事情都在對話方塊程序中完成,這是可能的。