對話框

對話框

壹佰軟件開發小組  整理編譯  

如果有很多輸入超出了菜單可以處理的程度,那麼我們可以使用對話框來取得輸入信息。程序寫作者可以通過在某選項後面加上省略號(…)來表示該菜單項將啓動一個對話框。

對話框的一般形式是包含多種子窗口控件的彈出式窗口,這些控件的大小和位置在程序資源描述文件的「對話框模板」中指定。雖然程序寫作者能夠「手工」定義對話框模板,但是現在通常是在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所示的對話框。


 

圖11-1 程序ABOUT1的對話框

對話框及其模板

要把一個對話框添加到Visual C++ Developer Studio會有的應用程序上,可以先從Insert菜單中選擇 Resource,然後選擇Dialog Box。現在一個對話框出現在您的眼前,該對話框帶有標題列、標題(Dialog)以及 OKCancel按鈕。Controls工具列允許您在對話框中插入不同的控件。

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

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

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

如果您要讓程序的圖標出現在對話框中,可以這樣做:先在浮動的Controls工具列中按下「 Pictures」按鈕。將鼠標移動到對話框的表面,按下左鍵,然後拉出一個矩形。這就是圖標將出現的位置。然後在次矩形上按下鼠標右鍵,從菜單中選擇 Properties。保持IDIDC_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建立的模板。我所設計的對話框模板如下:

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(文字居中)。這些敘述的格式爲:

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

其中,後面的iStyle項是可選的,它使用Windows表頭文件中定義的標識符來指定其它窗口樣式。

DEFPUSHBUTTON、ICON和CTEXT等標識符只可以在對話框中使用,它們是某種特定窗口類別和窗口樣式的縮寫。例如,CTEXT指示這個子窗口控件類別是「靜態的」,其樣式爲:

WS_CHILD | SS_CENTER | WS_VISIBLE | WS_GROUP
        

雖然前面沒有出現過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的對話框程序:

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取得程序的執行實體句柄並將它放在靜態變量中:

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

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

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

該函數需要執行實體句柄(在處理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呼叫可以使用如下敘述來開始:

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

不同的主題

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

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

STYLE WS_POPUP | DS_MODALFRAME
        

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

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

CAPTION "Dialog Box Caption"
        

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

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

如果您使用WS_CAPTION樣式,也可以添加一個WS_SYSMENU樣式的系統菜單按鈕。此樣式允許使用者從系統菜單中選擇 MoveClose

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

您甚至可以給對話框添加一個菜單。這時對話框模板將包括下面的敘述:

MENU menu-name
        

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

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

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

CLASS "class-name"
        

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

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

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

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

表 11-1

 

控件型態

窗口類別

窗口樣式

PUSHBUTTON

按鈕

BS_PUSHBUTTON | WS_TABSTOP

DEFPUSHBUTTON

按鈕

BS_DEFPUSHBUTTON | WS_TABSTOP

CHECKBOX

按鈕

BS_CHECKBOX | WS_TABSTOP

RADIOBUTTON

按鈕

BS_RADIOBUTTON | WS_TABSTOP

GROUPBOX

按鈕

BS_GROUPBOX | WS_TABSTOP

LTEXT

靜態文字

SS_LEFT | WS_GROUP

CTEXT

靜態文字

SS_CENTER | WS_GROUP

RTEXT

靜態文字

SS_RIGHT | WS_GROUP

ICON

靜態圖標

SS_ICON

EDITTEXT

編輯

ES_LEFT | WS_BORDER | WS_TABSTOP

SCROLLBAR

滾動條

SBS_HORZ

LISTBOX

清單方塊

LBS_NOTIFY | WS_BORDER | WS_VSCROLL

COMBOBOX

下拉式清單方塊

CBS_SIMPLE | WS_TABSTOP

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

WS_CHILD | WS_VISIBLE
        

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

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

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

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

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

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

控件敘述的style字段是可選的。它允許您包含其它窗口樣式標識符。例如,如果您想建立在正方形框左邊包含文字的複選框,那麼可以使用:

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

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

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
        

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

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

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中定義。


 

圖11-2 ABOUT2程序的對話框

當您在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消息。要設定一個按鈕選中標記,您可以使用:

SendMessage (hwndCtrl, BM_SETCHECK, 1, 0) ;
        

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

SendMessage (hwndCtrl, BM_SETCHECK, 0, 0) ;
        

其中hwndCtrl參數是子窗口按鈕控件的窗口句柄。

但是在對話框程序中使用這種方法是時有點問題的,因爲您不知道所有單選按鈕的窗口句柄,只是從您獲得的消息中知道其中一個句柄。幸運的是,Windows爲您提供了一個函數,可以用對話框句柄和控件ID來取得一個對話框控件的窗口句柄:

hwndCtrl = GetDlgItem (hDlg, id) ;
        

(您也可以使用如下函數,從窗口句柄中取得控件的ID值:

id = GetWindowLong (hwndCtrl, GWL_ID) ;
        

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

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

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:

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函數時纔會出現:

CheckRadioButton (hDlg, idFirst, idLast, idCheck) ;
        

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

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

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

在使用複選框時,也提供了類似的簡化函數。如果您建立了一個「CHECKBOX」對話框窗口控件,那麼可以使用如下的函數來設定和清除選中標記:

CheckDlgButton (hDlg, idCheckbox, iCheck) ;
        

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

iCheck = IsDlgButtonChecked (hDlg, idCheckbox) ;
        

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

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

如果您定義了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」按鈕是內定的:

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

在對話框中,通常都這樣安排「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消息:

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中,您可以依據此結構來定義並初始化一個靜態變量:

static ABOUTBOX_DATA ad = { IDC_BLACK, IDC_RECT } ;
        

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

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

這是關鍵:DialogBoxParam的最後一個參數是作爲WM_INITDIALOG消息中的lParam傳遞給對話框程序的。

對話框程序有兩個ABOUTBOX_DATA結構型態的靜態變量(一個結構和一個指向結構的指針):

static ABOUTBOX_DATA ad, * pad ;
        

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

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

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

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

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樣式,所以您可以使用光標移動鍵在組內兩個單選按鈕- RectangleEllipse之間移動。兩個按鍵都有WS_GROUP樣式,以阻止光標移動鍵在按鍵具有輸入焦點時起作用。

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

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

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

hwndCtrl = GetNextDlgTabItem (hDlg, hwndCtrl, bPrevious) ;
        

hwndCtrl = GetNextDlgGroupItem (hDlg, hwndCtrl, bPrevious) ;
        

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

在對話框上畫圖

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

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

這個區域爲18個字符寬和9個字符高。由於這個控件沒有文字,所以窗口消息處理程序爲「靜態」類別所做的工作,只是在必須重繪這個子窗口控件時清除其背景。

在目前顏色或圖形選擇發生改變,或者對話框自身獲得一個WM_PAINT消息時,對話框過程調用PaintTheBlock,這個函數在ABOUT2.C中:

PaintTheBlock (hCtrlBlock, iColor, iFigure) ;
        

在AboutDlgProc中,窗口句柄hCtrlBlock已經在處理WM_INITDIALOG消息時被設定:

hCtrlBlock = GetDlgItem (hDlg, IDD_PAINT) ;
        

下面是PaintTheBlock函數:

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在對話框內移動控件,強迫使用者用鼠標來追蹤它們。

有時,您需要根據其它控件的設定,動態地啓用或者禁用某些控件,這需要呼叫:

EnableWindow (hwndCtrl, bEnable) ;
        

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

定義自己的控件

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

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

程序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敘述來指定此窗口類別:

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

當在對話框中建立子窗口控件時,對話框管理器把這個窗口類別用於CreateWindow呼叫中。

ABOUT3.C程序在WinMain中註冊了EllipPush窗口類別:

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所示。您可以用同樣的方法來建立其它自訂對話框控件。


 

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

這就是全部要做的嗎?其實不然。通常,對於維護子窗口控件所需要的處理而言,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相同。

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

區別是CreateDialog函數立即傳回對話框的窗口句柄,並通常將這個窗口句柄存放到整體變量中。

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

模態對話框與非模態對話框的區別

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

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

STYLE WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_VISIBLE
        

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

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

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

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

第三項區別:與模態對話框和消息框的消息不同,非模態對話框的消息要經過程序式的消息隊列。要將這些消息傳送給對話框窗口消息處理程序,則必須改變消息隊列。方法如下:當您使用CreateDialog建立非模態對話框時,應該將從呼叫中傳回的對話框句柄儲存在一個整體變量(如hDlgModeless)中,並將消息循環改變爲:

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樣式。


 

圖11-4 COLORS2的屏幕顯示

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

如上所述,從CreateDialog傳回的對話框窗口句柄存放在整體變量hDlgModeless中,並在消息循環中被測試。不過,在這個程序中,不需要將句柄存放在整體變量中,也不需要在呼叫IsDialogMessage之前測試這個值。消息循環可以編寫如下:

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將三個數值卷標轉換爲文字之後才設定它們的值。敘述爲:

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

i的值爲目前處理的滾動條的ID,hwndValue是一個數組,它包含顏色數值的三個靜態文字子窗口的窗口句柄。

新版本使用SetDlgItemInt爲每個子窗口的每個文字字段設定一個號碼:

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

儘管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所示。

程序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」編輯欄輸入

#include "hexcalc.dlg"
        

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

仔細看一下HEXCALC.DLG文件中的對話框模板,您將發現HEXCALC如何爲對話框使用它自己的窗口消息處理程序。對話框模板的上方如下:

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:

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

第二個參數(字符串「HexCaEc」)是對話框模板的名字。第三個參數通常是父窗口的窗口句柄,這裏設定爲0,因爲窗口沒有父窗口。最後一個參數,通常是對話框程序的地址,這裏不需要。因爲Windows不會處理這些消息,因而也不會將消息發送給對話框程序。

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

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消息時,它總是將輸入焦點設定給父窗口:

case        WM_COMMAND :
        
           SetFocus (hwnd) ;
        

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

通用對話框

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所示。

程序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所示的普通對話框。


 

圖11-6 「File Open」對話框

當使用者結束這個對話框時,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-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程序只建立對話框,其它事情都在對話框程序中完成,這是可能的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章