精华内容
下载资源
问答
  • 对话框

    2010-09-04 14:09:00
    程序写作者可以通过在某选项后面加上省略号(…)来表示该菜单项将启动一个对话框对话框的一般形式是包含多种子窗口控件的弹出式窗口,这些控件的大小和位置在程序资源描述文件的「对话框模板」中指定。虽然...

    对话框

    壹佰软件开发小组  整理编译  

    如果有很多输入超出了菜单可以处理的程度,那么我们可以使用对话框来取得输入信息。程序写作者可以通过在某选项后面加上省略号(…)来表示该菜单项将启动一个对话框。

    对话框的一般形式是包含多种子窗口控件的弹出式窗口,这些控件的大小和位置在程序资源描述文件的「对话框模板」中指定。虽然程序写作者能够「手工」定义对话框模板,但是现在通常是在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程序只建立对话框,其它事情都在对话框程序中完成,这是可能的。

    展开全文
  • 第12章 对话框

    2018-08-22 20:22:38
    程序写作者可以通过在某选项后面加上省略号(…)来表示该菜单项将启动一个对话框对话框的一般形式是包含多种子窗口控件的弹出式窗口,这些控件的大小和位置在程序资源描述文件的「对话框模板」中指定。虽然程序...

    如果有很多输入超出了菜单可以处理的程度,那么我们可以使用对话框来取得输入信息。程序写作者可以通过在某选项后面加上省略号(…)来表示该菜单项将启动一个对话框。

    对话框的一般形式是包含多种子窗口控件的弹出式窗口,这些控件的大小和位置在程序资源描述文件的「对话框模板」中指定。虽然程序写作者能够「手工」定义对话框模板,但是现在通常是在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程序只建立对话框,其它事情都在对话框程序中完成,这是可能的。

    展开全文
  • 11.1 模态对话框

    2015-11-16 17:04:53
     对话框分“模态”和“非模态”两种,其中模态对话框最为常见。当程序显示一个模态对话框时,用户不能在对话框和该程序的其他窗口之间进行切换。用户必须先明确地终止该对话框。这通常由单击 OK 或 Cancel 按钮来...

    摘录于《Windows程序(第5版,珍藏版).CHarles.Petzold 著》P385

            对话框分“模态”和“非模态”两种,其中模态对话框最为常见。当程序显示一个模态对话框时,用户不能在对话框和该程序的其他窗口之间进行切换。用户必须先明确地终止该对话框。这通常由单击 OK 或 Cancel 按钮来实现。但是当对话框正在显示时,用户可以切换到其他的程序。有些对话正在显示时,用户可以切换到其他的程序。有些对话框 (所谓“系统模态”)则连这种切换都不允许。在 Windows 中,用户必须先结束系统模态对话框才可以进行其他操作。

    11.1.1  创建一个 About 对话框

            即使一个 Windows 程序不需要任何用户输入,它也经常含有一个可以由菜单中的 About 选项激活的对话框。这个对话框一般显示程序的名称和图标、版权声明、标有 OK 的按钮以及一些其他信息(比如一个提供技术支持的电话号码)。我们将要看到的第一个程序就只显示一个 About 对话框。ABOUT1 程序在图 11-1 中显示。

    /*--------------------------------------------------------
    	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");
    	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(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 Visual C++ generated resource script.
    //
    #include "resource.h"
    
    /
    //
    // Dialog
    //
    
    ABOUTBOX DIALOGEX 32, 32, 180, 102
    STYLE DS_MODALFRAME | WS_POPUP
    FONT 8, "MS Sans Serif"
    BEGIN
        DEFPUSHBUTTON   "OK",IDOK,66,81,50,14
        ICON            "ABOUT1",IDC_STATIC,0,0,21,23
        CTEXT           "About1",IDC_STATIC,40,12,100,8
        CTEXT           "About Box Demo Program",IDC_STATIC,0,40,166,8
        CTEXT           "(c) Charles Petzold, 1998",IDC_STATIC,0,52,166,8
    END
    
    /
    //
    // Menu
    //
    
    ABOUT1 MENU
    BEGIN
        POPUP "&Help"
        BEGIN
            MENUITEM "&About About1...",            IDM_APP_ABOUT
        END
    END
    
    /
    //
    // Icon
    //
    
    ABOUT1                  ICON                    "About1.ico"
    
    RESOURCE.H (节选)
    // Microsoft Visual C++ 生成的包含文件。
    // 供 About1.rc 使用
    //
    #define IDM_APP_ABOUT                   40001
    #define IDC_STATIC			-1

    ABOUT1.ICO

            创建图标和菜单的方法和第 10 章所描述的相同。图标和菜单的文本 ID 名称都是“About”。菜单含有一个菜单项,它用来生成 ID 是 IDM_APP_ABOUT 的 WM_COMMAND 消息。这个消息将导致程序显示如图11-2 所示的对话框。

    图 11-2  ABOUT1 程序的对话框

    11.1.2  对话框及其模板

            在 Visual C++ Developer Studio 中,给程序添加对话框要先选择 Insert 菜单中的 Resource,再选择 Dialog Box。然后会显示出一个具有标题栏、OK 和 Cancel 按钮的对话框,标题栏的标题是“Dialog”。同时显示的 Controls 工具栏可用来在对话框中插入各种控件。

            Developer Studio 会给予对话框一个标准的 ID 名:IDD_DIALOG1。你可以用右键单击这个名称(或对话框本身),并弹出菜单的 Properties 选项。对于这个程序,ID 可以被改写成"AboutBox"(包括引号)。为了与已创建的对话框保持一致,请把字段 X pos 和 Y pos 改写成 32.这些字段是用来指定对话框以客户区左上角为坐标原点的相对位置。

            现在,在 Properties 对话框中,请选择 Style 选项卡。因为这个对话框没有标题栏,所以请取消选择 Title Bar 复选框。最后请单击 Properties 对话框的关闭按钮。

            现在我们来正式设计对话框。我们不需要 Cancel 按钮,所以请单击这个按钮并按键盘上的 Del 键。请单击 OK 按钮,并把它移到对话框的底部。在 Developer Studio 窗口底部的工具栏上有一个小的位图,它可以使控件在窗口的水平方向上居中对齐。请单击这个按钮。

            我们程序要求在对话框中也显示图标。为了实现这个功能,请按下浮动空间工具栏的 Pictures 按钮。然后把鼠标移动到对话框上,按住左键,并拖动成一个方形。图标将被显示在这个方形中。在这个方形中按下鼠标右键,并选择弹出菜单的 Properties 选项。保持 ID 值 IDC_STATIC 不变。在 RESOURCE.H 文件中,这个标识符将被定义为-1。在 C 程序中所有没被引用到的 ID 的值都是-1。将 Type 改成 Icon。在 Image 字段中敲入程序图标的名称。或者,如果已经创建了图标,也可以在组合框中选择图标名称("About1")。

            请在控件工具栏中选择 Static Text,并把对话框中的三个静态文本字符串置放在对话框窗口中。然后用右键单击控件,并选择弹出菜单的 Properties 选项。在 Properties 对话框的 Caption 字段中,请敲入要显示的文本。最后请在 Style 选项卡的 Align Text 字段中选择 Center 选项。

            在添加这些文本的时候,对话框可能需要增大。请选择对话框并拖动其边框。你还可以选择各种控件并改变其大小。一般情况下,用键盘的光标移动键比较容易完成此项任务。箭头键本身可用来移动控件;同时按下 Shift 键时,箭头键可用来改变控件的大小。被选中的空间的坐标和大小在 Developer Studio 窗口的右下角显示。

            如果先编译此应用程序,然后打开资源脚本文件 ABOUT1.RC,应该可以看到 Developer Studio 生成的对话框模板。下面是我设计的对话框的模板:

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

            文件的第一行指定了对话框的名称(本例中是 ABOUTBOX)。至于其他资源,则可由数字来取值。在名称的后面紧跟着的是关键词 DIALOG 和 DISCARDABLE,以及四个数值。前两个数值是对话框左上角的 x 和 y 坐标值,这个坐标是相对于对话框被程序激活时的父窗口的客户区的。后两个数值是对话框的宽度和高度。

            这些坐标值和大小并不是以像素为单位的。它们采用的是对话框模板的一种专门的坐标系。它们的数值基于对话框所用字体的大小(在本例中是 8 磅的 MS Sans Serif 字体):x 坐标值和宽度值以字符的平均宽度的 1/4 为单位;y 坐标值和高度值以字符的高度的 1/8为单位。在本例中,对话框的左上角距离主窗口的坐标有 8 个字符,距离主窗口的上边有  4 个字符。对话框本身有 45 个字符宽,12 又 1/2 个字符高。

            通过这种坐标系,使用同样的坐标值和大小值可以使对话框在使用各种显示器和字体的情况下,保持同样的尺寸和外观。由于一般字体的字符高度是宽度的两倍,所以 x 轴和 y 轴的尺寸基本相同

            模板中的 STYLE 语句与 CreateWindow 调用中的样式字段相类似。模态对话框一般使用 WS_POPUP 和 DS_MODALFRAME。之后我们将探讨其他一些样式。

            在 BEGIN 和 END 语句之间(或在左右方括弧之间,如果你倾向于手工设计对话框模板的话),可以定义对话框的子窗口控件。这个对话框使用了三种子窗口控件:DEFPUSHBUTTON(默认按钮)、ICON(图标)和 CTEXT(居中文本)。这些定义语句的格式如下:

    控件类型 "文本" id, xPos, xWidth, yHeight, iStyle
    语句末尾的 iStyle 值是可选的,它用来指定窗口的附加样式。它取值于 Windows 头文件中定义的标识符。

            DEFPUSHBUTTON、ICON 和 CTEXT 标识符只被用于对话框。它们是一些特殊窗口类和窗口样式的简写。例如,CTEXT 指定子窗口控件的类是“静态的”,同时其样式为下

    WS_CHILD | SS_CENTER | WS_VISIBLE | WS_GROUP
    
    在第 9 章的 COLORS1 程序中我们使用过 WS_CHILD、SS_CENTER 和 WS_BISIBLE 来创建静态子窗口文本控件。这里我们第一次使用了 WS_GROUP 标识符。

            对于图标来说,文本字段指定了程序的图标资源的名称。这个名称是在 ABOUT1 资源脚本中定义的。对于按钮来说,文本字段指定了按钮中显示的文本。这个文本的设定等价于用来在程序中创建子窗口控件的 CreateWindow 调用的第二个参数所指定的文本。

            当子窗口向父窗口发送消息(一般是 WM_COMMAND 消息)时,id 字段是子窗口用来标示自己的标识符。这些子窗口控件的父窗口是对话框本身。它会把这些收到的消息发送给某个 WIndows 的窗口过程。这个窗口过程也会把这些消息发送给用户程序中定义的对话框过程。这里的 ID 值与第 9 章中用 CreateWindows 函数创建子窗口时所用的子窗口 ID 相同。由于文本和图标控件并不向父窗口发送消息,所以它们的 ID 值被设为 IDC_STATIC。IDC_STATIC 在 RESOURCE.H 中被定义被-1。按钮的 ID 值是 IDOK。IDOK 在 WINUSER.H 中被定义为 1。

            接下来的四个数字用来设定子窗口控件的位置(相对于对话框客户区的左上角)和大小。位置和大小是以字体字符的平均宽度的 1/4 和 平均高度的 1/8 为单位的。ICON 语句中宽度和高度值被忽略了。

            在对话框模板中的 DEFPUSHBUTTON 语句中包含了 WS_GROUP 窗口样式。它是独立于 DEFPUSHBUTTON 本身隐含的窗口样式之外的。当讨论这个程序的第二个版本 ABOUT2 时,我会对 WS_GROUP 做更多的描述。

    11.1.3  对话框过程

            发送给对话框的消息是由用户程序中的对话框过程来处理的。这个过程与真正的窗口过程看起来很像,但其实是不一样的。对话框的窗口过程属于 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 函数。尽管我使用了hDlg 作为对话框窗口的句柄,但其实你也可以使用 hwnd。让我们来先看一下这个对话框过程与窗口过程的区别。

    • 窗口过程的返回值是 LRESULT;而对话框过程的返回值是 BOOL。BOOL 在 Windows 头文件中被定义成 int。
    • 当窗口过程不处理一条消息时,它会调用 DefWindowProc;当对话框过程处理一条消息时,它会返回 TRUE(非零),而当它不处理一条消息时,返回 FALSE(零)。
    • 对话框过程不需要处理 WM_PAINT 和 WM_DESTORY 消息。它也不会收到 WM_CREATE 消息。然而它会处理一条专门的 WM_INITDIALOG 消息时进行初始化。

            WM_INITDIALOG 是对话框过程接收到的第一条消息。这条消息只发送给对话框过程。当对话框过程返回 TRUE 时,Windows 会把输入焦点设置到对话框的第一个含有 WS_TABSTOP 样式的子窗口控件(我们会在 ABOUT2 中对 WS_TABSTOP 进行解释)。在本程序的对话框中,第一个含有 WS_TABSTOP 的子窗口控件是按钮。此外,在处理 WM_INITDIALOG 时,对话框过程也可以调用 SetFocus 函数来设置焦点所在的子窗口控件,同时返回 FALSE。

            除上述消息以外,对话框过程只处理 WM_COMMAND。当鼠标单击按钮控件或当焦点在按钮控件时按下空格键,按钮控件就会向父窗口发送 WM_COMMAND 消息。该消息的 wParam 参数的低位字是控件的 ID(在对话框模板中被设定为 IDOK)。对这条消息,对话框过程调用了 EndDialog 函数,此函数会通知 Windows 销毁该对话框。对于任何其他消息,对话框过程会通过返回 FALSE 来通知 Windows 的对话框窗口过程该消息未获处理。

            由于模态对话框的消息并不通过程序的消息循环,所以你不用担心这种对话框内的键盘加速键会有什么问题。

    11.1.4  激活对话框

            当 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 按钮,按下空格键,或者按下回车键来结束此对话框。当回车键或空格键被按下时,Windows 会向对话框发送 WM_COMMAND 消息,消息的 wParam 参数的低位字是默认按钮的 ID。本例中该 ID 是 IDOK。你还可以通过按下 Esc 键来结束对话框。此时,Windows 发送的 WM_COMMAND 消息中的 ID 等于 IDCANCEL。

            直到对话框结束以后,用来显示对话框的 DialogBox 函数才将控制返回给 WndProc。DialogBox 的返回值等于对话框过程中调用的 EndDialog 函数的第二个参数。(这个值在 ABOUT1 中没有使用,而在 ABOUT2 中使用了。)之后 WndProc 可以将控制权返回给 Windows。

            当对话框正在显示时,WndProc 依旧可以处理消息。事实上,你可以从对话框过程中向 WndProc 发送消息。ABOUT1 的主窗口是对话框弹出窗口的父窗口,故此 AboutDlgProc 中对 SendMessage 的调用可如下开始:

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

    11.1.5  主题变换

            尽管 Visual C++ Developer Studio 中的对话框编辑器和其他资源编辑器使你没有必要阅读资源脚本,但是学习资源脚本的语法还是有所益处的。特别是对于对话框模板来说,理解语法会使你更好地感受到对话框的使用范围和局限性。有时当没有其他方法时,你甚至需要手动创建对话框模板(例如本章后面的 HEXCALC 程序)。在/Platform SDK/Windows Programming Guidelines/Platform SDK Tools/Compiling/Using the Resource Compiler 中有关于资源编译器和资源脚本语法的文档。

            Developer Studio 中的 Properties 对话框可以用来设定对话框的窗口样式,该样式定义会转换为对话框模板中的 STYLE 语句。对于 ABOUT1,我们使用了最常用的模态对话框的样式:

    WS_POPUP | DS_MODALFRAME
    
    你也可以尝试一下其他的样式。有些对话框含有一个标示对话框用途的标题栏,并允许用户用鼠标在显示器上移动对话框。这种样式是 WS_CAPTION。当使用 WS_CAPTION 时,DIALOG 语句中定义的 x 和 y 坐标值是对话框客户区相对于父窗口客户区左上角的坐标。标题栏会显示在 y 坐标的上面。

            对于标题栏,你可以通过在对话框模板中的 STYLE 语句后使用 CAPTION 语句来定义标题文本:

    CAPTION "Dialog Box Caption"
    
    或者当对话框过程处理 WM_INITDIALOG 消息时,你可以使用

    SetWindowText (hDlg, TEXT("Dialog Box Caption"));
    
    使用 WS_CAPTION 样式时,还可以用 WS_SYSMENU 样式来添加一个系统菜单框。这一样是允许用户通过系统菜单来选择 Move 或 Close。

            尽管下面的用法不太常见,但在 Properties 对话框的 Border 列表框中选择 Resizing(等价于 WS_THICKFRAME 样式)可允许用户改变对话框的大小。如果想别出心裁,你可以尝试在对话框中添加最大化框。

            你甚至还可以给对话框添加菜单。这样对话框模板中会含有语句:

    MENU 菜单名
    
    该语句的参数是资源脚本中菜单的名称或数字。在对话框中使用菜单是非常罕见的。如果你在对话框中使用菜单,请务必使所有菜单和对话框控件的 ID 值保持唯一性。如果 ID 有所重复,重复的 ID 应使用同样的命令。

            程序可以使用 FONT 语句来将对话框文本设置为系统字体以外的字体。这曾经在对话框中比较罕见,但现在已经很平常了。事实上,Developer Studio 在默认情况下为所有程序创建的对话框选择的是 8 磅的 MS Sans Serif 字体。Windows 程序可以通过随同程序发布一个专用于某个对话框或某些文本的特殊字体来实现一些独特的显示效果。

            尽管对话框窗口过程通常属于 Windows,但你也可以使用自己的某个窗口过程来处理对话框消息。要做到这一点,请在对话框模板中指定一个窗口类名:

    CLASS "类名"
    
    这里还牵扯到其他一些因素,我会再本章稍后的 HEXCALC 程序中示范。

            当程序调用 DialogBox,并指定对话框模板的名称后,Windows 几乎拥有所有用来调用 CreateWindow 函数以创建一个弹出式窗口的信息。Windows 从对话框模板中获取窗口的位置和大小、窗口样式、标题和菜单。Windows 从 DialogBox 的参数中获取程序实例句柄和父窗口句柄。Windows 所需的唯一其他信息是一个窗口类(假设对话框模板没有指定它)。Windows 为对话框注册一个特殊的窗口类。这个窗口类的窗口过程拥有对你的对话框过程的访问地址(这是在程序调用 Dialog 时提供的),因此该弹出式窗口可以向程序传递其收到的消息。当然,程序可以通过自己创建弹出式窗口来创建并维护自己的对话框。使用 DialogBox 只是一种更为方便的办法

            你也许想获得使用 Windows 对话框管理器的好处,但你可能不想(或不能)在资源脚本中定义对话框模板。也许你希望程序在运行时动态创建一个对话框。这就要用到 DialogBoxIndirect 函数,该函数使用数据结构来定义模板。

            在 ABOUT1.RC 中的对话框模板中,三种简写符号 CTEXT、ICON 和 DEFPUSHBUTTON 被用来定义对话框所需的三种子窗口控件。还有其他一些类型可供使用,每种类型意味着一种预定义的窗口类和窗口样式。下表列出了一些常用控件类型的相应窗口类和窗口样式。

           

    控件类型窗 口 类窗口样式
     PUSHBUTTON button BS_PUSHBUTTON | WS_TABSTOP
     DEFPUSHBUTTON button BS_DEFPUSHBUTTON | WS_TABSTOP
     CHECKBOX button BS_CHECKBOX | WS_TABSTOP
     RADIOBUTTON button BS_RADIOBUTTON | WS_TABSTOP
     GROUPBOX button BS_GROUPBOX
     LTEXT static SS_LEFT | WS_GROUP
     CTEXT static SS_CENTER | WS_GROUP
     RTEXT static SS_RIGHT | WS_GROUP
     ICON static SS_ICON
     EDITTEXT edit ES_LEFT | WS_BORDER | WS_TABSTOP
     SCROLLBAR scrollbar SBS_HORZ
     LISTBOX listbox LBS_NOTIFY | WS_BORDER
     COMBOBOX combobox CBS_SIMPLE | WS_TABSTOP
    资源编译器是理解这些缩写符号 的唯一程序。除了上面所列的窗口样式之外,每个控件还有如下样式

    WS_CHILD | WS_VISIBLE
    

            除了 EDITTEXT、SCROLLBAR、LISTBOX 和 COMBOBOX 之外,所有这些控件的控件语句都有如下格式:

    控件类型 "文本", id, xPos, yPos, xWidth, yHeight, iStyle
    
    EDITTEXT、SCROLLBAR、LISTBOX 和 COMBOBOX 的格式是

    控件类型 id, xPos, yPos, xWidth, yHeight, iStyle
    该格式没有文本字段。两种语句中,iStyle 参数都是可选的。

            在第 9 章中,我讨论了确定预定义子窗口控件的宽度和高度的规则。你也许要回顾一下该章中的规则,请注意对话框模板中指定的大小永远是以平均字符宽度的 1/4 和字符高度的 1/8 为单位的。

            控件语句中的“样式”字段是可选的。它使程序可以包含其他窗口样式标识符。例如,如果程序要创建一个复选框,并在它方框左边有一些文本,则可以用

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

            请注意,控件类型 EDITTEXT 自动含有边框。如果程序要创建一个没有边框 的子窗口控件,可以用

    EDITTEXT id, xPos, yPos, xWidth, yHeight, BS_LEFTTEXT

            资源编辑器还承认像下面这样的一般化控件语句

    CONTROL "文本", id, "类", 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 中,可以用控件工具栏中的自定义控件选项来生成如上语句。在 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 函数调用获得的。

    11.1.6  更复杂的对话框

            ABOUT1 中的简单对话框示范了一些对话框启动和运行的基本行为;现在让我们来尝试一些更复杂的。ABOUT2 程序示范了如何在对话框过程中管理控件(在本例中是单选按钮),以及如何在对话框的客户区绘图。

    /*--------------------------------------------------------
    	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");
    	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(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_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 Visual C++ generated resource script.
    //
    #include "resource.h"
    
    /
    //
    // Dialog
    //
    
    ABOUTBOX DIALOGEX 32, 32, 200, 234
    STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION
    FONT 8, "MS Sans Serif", 0, 0, 0x1
    BEGIN
        ICON            "ABOUT2",IDC_STATIC,7,7,20,20
        CTEXT           "About2",IDC_STATIC,57,12,86,8
        CTEXT           "About Box Demo Programe",IDC_STATIC,7,40,186,8
        LTEXT           "",IDC_PAINT,114,67,72,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,WS_GROUP | WS_TABSTOP
        DEFPUSHBUTTON   "OK",IDOK,35,212,50,14,WS_GROUP
        PUSHBUTTON      "Cancel",IDCANCEL,113,212,50,14,WS_GROUP
    END
    
    /
    //
    // Icon
    //
    
    ABOUT2                  ICON                    "About2.ico"
    
    /
    //
    // Menu
    //
    
    ABOUT2 MENU
    BEGIN
        POPUP "&Help"
        BEGIN
            MENUITEM "&About",                      IDM_APP_ABOUT
        END
    END
    
    RESOURCE.H (节选)
    
    // Microsoft Visual C++ 生成的包含文件。
    // 供 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

    图 11-3  ABOUT2 程序

            ABOUT2 中的 About 框有两组单选按钮。其中一组用来选择颜色,另一组用来选择矩形或椭圆形。对话框中会显示内部用所选颜色着色的矩形或椭圆形。如果用户按下 OK 按钮,对话框就结束,而窗口过程则在自己的客户区绘出所选的图形。如果用户按下 Cancel,主窗口的客户区保持不变。该对话框如图 11-4 所示。尽管 ABOUT2 对话框的两个按钮使用了预定义标识符 IDOK 和 IDCANCEL,但是每个单选按钮都有自己的以 IDC(表示 ID for a control)打头的标识符。这些标识符是在 RESOURCE.H 中定义的。

            创建 ABOUT2 对话框中的单选按钮时,请按它们的显示顺序来创建。这将确保 Developer Studio 定义标识符是顺序取值的,应用程序也是这么认为的。此外,请取消选中每个单选按钮的 Auto 选项。自动单选按钮需要较少的代码,但开始时会比较玄奥。请给它们使用如 ABOUT2.RC 中所示的标识符。

    图 11-4  ABOUT2 程序的对话框

            请在 OK 和 Cancel 按钮、Figure 选项组框以及每组的第一个单选按钮(Block 和 Rectangle)的 Properties 对话框中选择 Group 选项。请对这两个单选按钮选中 Tab Stop。

            当对话框中的所有控件都有大概位置和大小之后,请选择 Layout 菜单中的 Tab Order 选项。请按 ABOUT2.RC 资源脚本中的顺序来单击每个控件。

    11.1.7  对话框控件的应用

            在第 9 章中,大多数子窗口控件都向父窗口发送 WM_COMMAND 消息。(唯一的例外是滚动条控件。)你还会看到父窗口可以通过向控件发送消息来改变子窗口控件(例如,选中或取消选中单选按钮或复选框)。同样,你可以在对话框过程中改变控件。例如,你的程序有一些单选按钮,程序可以通过向它们发送消息来选择或取消选中它们。然而,Windows 还提供了几种应用对话框控件的捷径。下面让我们来看看对话框过程和子窗口控件是怎样交互的。

            ABOUT2 的对话框模板显示在图 11-3 中所示的 ABOUT2.RC 资源脚本中。GROUPBOX 控件只是一个有标题(Color 或 Figure)的框架,它们分别围绕着两组单选按钮。第一组中的 8 个单选按钮时相互排斥的,第二组中的两个按钮同样也是。

            当其中一个单选按钮被鼠标点中时(或当单选按钮拥有输入焦点时按下空格键时),子窗口向其父窗口发送 WM_COMMAND 消息。该消息的 wParam 参数的低位字是控件的 ID,高位字是通知码。lParam 参数是控件的窗口句柄。对于单选按钮来说,该通知码是 BN_CLICKED,它等于 0。然后 Windows 的对话框窗口过程把这个 WM_COMMAND 消息传递给 ABOUT2.C 中的对话框过程。当对话框过程收到关于一个单选按钮的 WM_COMMAND 消息后,它选中该按钮,并取消选中同组所有其他按钮。

            你可能还记得,在第 9 章中选中或取消选中按钮需要向子窗口控件发送一条 BM_SETCHECK 消息。要选中按钮,可使用以下代码:

    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-3 所示的 ABOUT2.H 头文件中,8 种颜色的 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_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;   <pre name="code" class="cpp">        [其他程序行]

     
    

            这以方式运作良好。程序将新的颜色值存在 iColor 中,并设立了一个通过所有 8 种颜色的 ID 值的循环。程序还获取了这 8 个单选按钮控件的窗口句柄,并用 SendMessage 向每一个句柄发送了一条 BM_SETCHECK 消息。只有对向对话框窗口过程发送 WM_COMMAND 消息的按钮,消息的 wParam 值才设为 1。

            第一种捷径是特殊的对话框过程 SendDlgItemMessage:

    <pre name="code" class="cpp">SendDlgItemMessage (hDlg, id, iMsg, wParam, lParam);
    

     它等价于 
    

    <pre name="code" class="cpp">SendMessage (GetDlgItem(hDlg, id), iMsg, wParam, lParam);

     现在,程序循环如下所示: 
    

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

            这只是稍有改进。而真正的突破是当你发现 CheckRadioButton 函数时:

    CheckRadioButton (hDlg, idFirst, idLast, idCheck);
    
    这个函数只选中 ID 值是 idCheck 的单选按钮,其他从 ID 值是 idFirst 到 idLast 的单选按钮都被取消选中。这要求 ID 值必须是顺序排列的。现在我们可以彻底摆脱顺序并使用:

    CheckRadioButton (hDlg, IDC_BLACK, IDC_WHITE, LOWORD(wParam));
    ABOUT2 中的对话框过程就是这样做的。

            使用复选框时也有一个类似的快捷函数。如果创建一个 CHECKBOX 对话框窗口控件,那么你可以通过使用如下函数来选中或取消选中选项标记:

    CheckDlgButton (hDlg, idCheckbox, iCheck);
    
    如果 iCheck 值为 1,按钮则被选中:如果 iCheck 值为 0,按钮被取消选中。对话框中的复选框的状态值可以通过如下方式获取:

    iCheck = IsDlgButtonChecked (hDlg, idCheckbox);
    
    你既可以将选中标记的当前状态保存在对话框过程的静态变量中,也可以通过如下方式在 WM_COMMAND 消息中切换按钮状态:

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

            如果是一个 BS_AUTOCHECKBOX 控件,程序根本不需要处理 WM_COMMAND 消息。程序可以在对话框终止之前通过 IsDlgButtonChecked 来获取按钮的当前状态。但是,如果使用 BS_AUTORADIOBUTTON 样式,那 IsDlgButtonChecked 函数就并不十分令人满意。因为程序必须对每一个单选按钮来调用该函数,直到函数返回 TRUE。所以,还不如通过捕获 WM_COMMAND 消息来跟踪哪个按钮被按下。

    11.1.8  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,WS_GROUP
    PUSHBUTTON      "Cancel",IDCANCEL,113,212,50,14,WS_GROUP

            这种安排对于对话框的 OK(确定)和 Cancel(取消)按钮是很常见的;把 OK 按钮作为默认按钮对键盘接口有所帮助。原因如下:在正常情况下,你通过用鼠标按一下其中的一个按钮,或在所希望的按钮有输入焦点时按下空格键来结束对话框。然而,不论是哪个控件用于输入焦点,当用户按回车键时,对话框窗口过程都会产生一个 WM_COMMAND 消息。该消息的 wParam 参数的 LOWORD 会设置为默认按钮的 ID 值,除非另外一个按键按钮具有输入焦点。在那种情况下,wParam 参数的 LOWORD 会被设置为具有焦点的按键按钮的 ID 值。如果对话框中没有按键按钮时默认按钮,Windows 会向对话框过程发送一条 wParam 参数的 LOWORD 等于 IDOK 的 WM_COMMAND 消息如果用户按下 ESC 键或 Ctrl-Break 键,Windows 会向对话框过程发送一条 wParam 参数的 LOWORD 等于 IDCANCEL 的 WM_COMMAND 消息。因为通常用于终止一个对话框的按键都被 Windows 翻译为这两个按钮的 WM_COMMAND 消息,所以你也就不必在对话框过程中添加单独的键盘逻辑了。

            AboutDlgProc 函数是通过调用 EndDialog 来处理这两个 WM_COMMAND 消息的:

    switch (LOWORD(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_APP_ABOUT:
         if (DialogBox(hInstance, TEXT("AboutBox"), hwnd, AboutDlgProc))
             InvalidateRect(hwnd, NULL, TRUE);
         return 0;
    

            如果 DialogBox 返回 TRUE(非零),则意味着 OK 按钮被按下,从而 WndProc 的客户区需要被更新为新的图像和颜色。在 AboutDlgProc 处理 WM_COMMAND 消息时,如果 wParam 参数的低位字为 IDOK,这些值就会被存放在全局变量 iCurrentColor 和 iCurrentFigure 中。如果 DialogBox 返回 FALSE,则主窗口继续使用 iCurrentColor 和 iCurrentFigure 的原来的设置值。

            通常情况下,EndDialog 函数 通过 TRUE 或 FALSE 来通知主窗口过程用户是用 OK 还是 Cancel 结束对话框的。然而,EndDialog 的参数类型是 int,而 DialogBox 正好也返回 int,所以该参数还可以被用来传递 TRUE 或 FLASE 之外的额外信息。

    11.1.9  避免全局变量

            在 ABOUT2 中使用全局变量也许会使你有些不安。 一些程序员(包括我自己)更愿意将全局变量的使用降到最低限度。看上去 ABOUT2 中的变量 iCurrentColor 和 iCurrentFigure 的使用可以说是符合对全局变量的定义的,因为它们必须既要在窗口过程要要在对话框过程中使用。然而,当一个程序有许多对话框时,每个对话框都有可能改变这些变量的值,这很容易就会造成混乱不堪的全局变量扩散。

            你可能更喜欢程序的每个对话框都有一个数据结构与之关联,该结构包括所有该对话框能够改变的变量。这样的结构可以用 typedef 语句来定义。例如,在 ABOUT2 中,你可以定义如下一个结构来与 About 框关联:

    typedef strutct
    {
        int iColor, iFigure;
    }
    ABOUTBOX_DATA; 
    
    在 WndProc 中,定义并初始化一个基于这个结构的静态变量:

    static ABOUTBOX_DATA ad = { IDC_BLACK, IDC_RECT };
    
    此外,在 WndProc 中,将所有出现的 iCurrentColor 和 iCurrentFigure 替换为 ad.iCorlor 和 ad.iFigure。当调用对话框时,请使用 DialogBoxParam 而不是 DialogBox。这个函数的第五个变量可以是任何一个 32 位值。一般来说,它被设置为一个结构的指针,在本例中这个结构是 WndProc 中的 ABOUTBOX_DATA。

    case IDM_APP_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 中结构的逐字段复制操作

            现在,在整个 AboutDlgProc 中,除去处理用户按下 OK 按钮的代码,用 ad.iColor 和 ad.iFigure 来替换其他所有的 iFigure 和 iColor。对于 OK 按钮,需要把局部结构的内容拷贝回 WndProc 的结构中:

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

    11.1.10  Tab 停靠和选项组

            在第 9 章中,我们用窗口子类给 COLORS1 添加了一个功能,它使我们可以通过按下 Tab 键来从一个滚动条移到另一个滚动条。在对话框中,没有必要使用窗口子类:Windows 提供了所有的程序逻辑来实现把输入焦点从一个控件移到另一个控件的功能。然而,程序本身也要在对话框模板的窗口样式中引用 WS_TABSTOP 和 WS_GROUP。对于所有需要用 Tab 键访问的控件,请在窗口样式中加入 WS_TABSTOP。

            回顾 11.1.5 中的窗口类和窗口样式表,你可以注意到许多控件默认包含有 WS_TABSTOP,而其他一些控件却没有。一般来说,不含有 WS_TABSTOP 样式的控件(特别是静态控件)不应该获取输入焦点,因为它们要了也没有用。除非程序在处理 WM_INITDIALOG 消息时将输入焦点设置为某个控件并返回 FALSE,否则 Windows 会把输入焦点设置为对话框中第一个含有 WS_TABSTOP 样式的控件。

            Windows 为对话框添加的第二个键盘接口涉及光标移动键。这一接口对单选按钮来说尤为重要。当用户用 Tab 键将焦点移到选项组中当前选择的单选按钮后,用户可以用光标移动键来使输入焦点从一个单选按钮移到同组中其他单选按钮上去。这一逻辑可以通过使用 WS_GROUP 窗口样式来实现。对于对话框模板中定义的一组控件来说,Windows 会使用光标移动键把输入焦点从第一个含有 WS_GROUP 样式的空间移到下一个含有该样式的控件。如有必要,Windows 还会从对话框的最后一个控件循环到第一个控件,直到找到选项组的末尾。

            默认情况下,LTEXT、CTEXT、RTEXT 和 ICON 控件含有 WS_GROUP 样式,这很方便地标示了选项组的结束。程序经常要为其他控件添加 WS_GROUP 样式。

            看看 ABOUT2.RC 中的对话框模板。四个含有 WS_TABSTOP 样式的控件分别是每个选项组的第一个单选按钮(它们明确包含了该样式)和两个按键按钮(在默认设置下包含了)。当用户首次激活对话框时,用户可以用 Tab 键来在这四个控件间移动。

            在每组单选按钮中,用户可以用光标移动键来改变输入焦点和选中标记。例如,Color 选项组中的第一个单选按钮和 Figure 选项组含有 WS_GROUP 样式。这意味着,用户可以用光标移动键把焦点从 Black 单选按钮移动到 Figure 选项组之前。类似的,Figure 选项组的第一个按钮(Rectangle)和 DEFPUSHBUTTON 都含有 WS_GROUP 样式,从而用户可以用光标键在选项组的两个单选按钮(Rectangle 和 Ellipse)之间移动。两个按键按钮全都有 WS_GROUP 样式,因此光标移动键在按钮拥有输入焦点时不起作用。

            在使用 ABOUT2 时,Windows 的对话框管理器对两组单选按钮做了魔术般的操作。正如预期的那样,在同组中使用光标移动键不但会移动输入焦点,还会向对话框过程发送一条 WM_COMMAND 消息。但是当用户改变组内选中的单选按钮时,Windows 还会赋予新选中的单选按钮 WS_TABSTOP 样式。这样在下次用户通过按 Tab 定位到这个选项组时,Windows 就能将输入焦点设置为该单选按钮

            文本字段中的符号“&”将使其后面的第一个字母带有下划线,并同时添加了另一个键盘接口。用户可以用个按下带有下划线字母来将输入焦点移到任何单选按钮。通过按下 C(Color 选项组)或 F(Figure 选项组),用户可以将输入焦点移到该选项组中当前被选中的单选按钮上。

            尽管程序员在一般情况下回让对话框管理器处理所有这些情况,但 Windows 还是提供了下面两个函数来查找下一个或前一个 Tab 停靠位或选项组项:

    hwndCtrl = GetNextDlgTabItem (hDlg, hwndCtrl, bPrevious);
    hwndCtrl = GetNextDlgGroupItem (hDlg, hwndCtrl, bPrevious);
    
    如果 bPrevious 是 TRUE,函数返回前一个 Tab 停靠位或选项组;如果是 FALSE,则返回下一个 Tab 停靠位或选项组。

    11.1.11  在对话框上绘图

            ABOUT2 在有些方面非比寻常:它在对话框上绘图。让我们看一下这是如何实现的。在 ABOUT2.RC 的对话框模板中定义了一个空白文本控件,它指定了我们要绘图的区域的位置和大小:

    LTEXT           "",IDC_PAINT,114,67,72,72
    
    这个区域有 18 个字符宽,9 个字符高。以为这个控件没有文本,所以在子窗口控件必须要被重绘时,窗口过程只是把该“静态”类的背景删干净。

            如果当前选中的颜色或图形有变化,或对话框收到一条 WM_PAINT 消息,对话框过程会调用在 ABOUT2.C 中定义的 PaintTheBlock 函数:

    PaintTheBlock (hCtrlBlock, iColor, iFigure);
    
    在 AboutDlgPrc 中,窗口句柄 hCtrlBlock 在处理 WM_INITDIALOG 消息时被设置:

    hCtrlBlock = GetDlgItem(hDlg, IDC_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 消息,子窗口控件就被设为无效并被更新,从而使其认为自己的客户区又有效了。然后我们在其上面绘图。

    11.1.12  关于对话框的其他函数

            大多数可以对子窗口使用的函数都可以对对话框中的控件使用。例如,你可以用 MoveWindow 来使控件在对话框中移动,从而使用户不得不用鼠标四处捕捉它。

            有时,程序需要根据其他控件的设置来动态地启用或禁用某些控件。下面的调用:

    EnableWindow (hwndCtrl, bEnable);
    
    当 bEnable 为 TRUE(非零)时启用控件,当 bEnable 为 FALSE(0)时禁用之。 当控件被禁用时,它不会收到任何键盘或鼠标的输入。请不要禁用含有输入焦点的控件

    11.1.13  定义程序自己的控件

            尽管 Windows 承担了大部分维护对话框和子窗口控件的责任,但还是有各种方法使程序可以将自己的代码融入这一过程。我们已经看到了一种可以使程序在对话框表明绘图的方法。程序还可以用窗口子类(在第 9 章中讨论过)来改变子窗口控件的运行。

            程序还可以定义自己的子窗口控件并运用于对话框中。例如,假设你对一般的方形按钮不感兴趣,而希望创建椭圆形按钮。你可以注册一个窗口类,并用自己定义的窗口过程来处理自定义子窗口的消息。然后你可以在 Developer Studio 的关于自定义控件的 Properties 对话框中指定该窗口类。这一过程会再对话框模板中产生一条 CONTROL 语句。ABOUT3 程序正式这样做的。

    /*--------------------------------------------------------
    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);
    LRESULT CALLBACK EllipPushWndProc(HWND, UINT, WPARAM, LPARAM);
    
    int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    	PSTR szCmdLine, int iCmdShow)
    {
    	static TCHAR szAppName[] = TEXT("About3");
    	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(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 Visual C++ generated resource script.
    //
    #include "resource.h"
    
    /
    //
    // Dialog
    //
    
    ABOUTBOX DIALOGEX 32, 32, 180, 100
    STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP
    FONT 8, "MS Sans Serif", 0, 0, 0x1
    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
    BEGIN
        POPUP "&Help"
        BEGIN
            MENUITEM "&About About3...",            IDM_APP_ABOUT
        END
    END
    
    /
    //
    // Icon
    //
    
    ABOUT3                  ICON                    "About3.ICO"
    
    RESOURCE.H (节选)
    
    // Microsoft Visual C++ 生成的包含文件。
    // 供 About3.rc 使用
    //
    #define IDM_APP_ABOUT                   40001
    #define IDC_STATIC                        -1 
    

    ABOUT3.ICO

            我们将要注册的窗口类称为 EllipPush(“椭圆按钮”)。在 Developer Studio 的对话框编辑器中,删除 Cancel 和 OK 按钮。请在控件工具栏中选择 Custom Control 来添加基于这个窗口类的控件。在这个控件的 Properties 对话框中,请在 Class 字段中输入 EllipPush。在对话框模板中不会再有 DEFPPUSHBUTTON 语句,取而代之的是一条 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_BTNFACE + 1);
    wndclass.lpszMenuName = NULL;
    wndclass.lpszClassName = TEXT ("EllipPush");
    
    RegisterClass(&wndclass);
    该窗口类指定的窗口函数是 ABOUT3.C 中的 EllipPushWndProc。

            窗口过程 EllipPushWndProc 只处理 3 种消息:WM_PAINT、WM_KEYUP 和 WM_LBUTTON。在处理 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 函数来获取父窗口(对话框)的句柄,并发送一条 wParam 等于控件 ID 的 WM_COMMAND 消息。控件 ID 是通过 GetWindowLong 获取的。然后对话框窗口过程将此消息传递给 ABOUT3 中的对话框过程。其结果就是如图 11-6 所示的自定义按钮。你可以用相同的方法来创建对话框中的其他自定义控件。

            难道就这样简单吗?当然不是。EllipPushWndProc 只是涉及维护子窗口控件的程序逻辑的最基本版本。例如,该按钮不会像正常按钮一样山东。要反转按钮内部的颜色,窗口过程需要处理 WM_KEYDOWN(来自空格键)和 WM_LBUTTONDOWN 消息。窗口过程还需要在处理 WM_LBUTTONDOWN 消息时捕获鼠标,以便当鼠标移出子窗口工作区时释放鼠标(使按钮的内部颜色返回正常)。只有当按钮被释放同时也捕获到鼠标时,子窗口才应该向它的父窗口发回一条 WM_COMMAND 消息。

    图 11-6  ABOUT3 程序创建的自定义按钮

            EllipPushWndProc 也不处理 WM_ENABLE 消息。如上所述,对话框过程可以通过使用 EnableWindow 函数来禁用一个窗口。这样子窗口的文字会显示成灰色而不是黑色,这表明它已被禁用而无法接受任何消息。

            如果子窗口的窗口过程需要为每个被创建的窗口存储一些数据,那么它可以通过为窗口类结构的 cbWndExtra 设置一个正整数来实现。这样可以在窗口内部结构中预留一些存储空间,该空间可以通过 SetWindowLong 和 GetWindowLong 访问。

    展开全文
  • 对话框(api)

    2014-08-18 17:24:00
    程序写作者可以通过在某选项后面加上省略号(…)来表示该菜单项将启动一个对话框对话框的一般形式是包含多种子窗口控件的弹出式窗口,这些控件的大小和位置在程序资源描述文件的「对话框模板」中指定。虽然程序...

    对话框

    壹佰软件开发小组  整理编译  

    如果有很多输入超出了菜单可以处理的程度,那么我们可以使用对话框来取得输入信息。程序写作者可以通过在某选项后面加上省略号(…)来表示该菜单项将启动一个对话框。

    对话框的一般形式是包含多种子窗口控件的弹出式窗口,这些控件的大小和位置在程序资源描述文件的「对话框模板」中指定。虽然程序写作者能够「手工」定义对话框模板,但是现在通常是在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程序只建立对话框,其它事情都在对话框程序中完成,这是可能的。

    转载于:https://www.cnblogs.com/caiyonghai/p/lianghemei.html

    展开全文
  • MFC之对话框

    2012-09-13 13:22:38
    1、新建对话框  对话框可以分为模式对话框和非模式对话框两种。模式对话框是指用户只能在当前的窗体中进行操作,在该窗体没有关闭之前不能切换到其他的窗体。非模式对话框是指当前的所操作的窗体可以切换。 ...
  • 文章目录一、简单绘图二、设置对话框2.1 设置线宽2.2 设置线型 新建一个单文档类型的MFC工程,取名:Graphic。此程序将实现简单的绘图功能。 一、简单绘图 实现简单的绘图功能,包括点、直线和椭圆的绘制。为了实现...
  • 当我们点击按钮的时候,会弹出一个对话框: 这个对话框是使用SPFx提供的SP-Dialog实现的,SP-Dialog是微软提供的一个对话框组件,完全与SharePoint Online集成,在SPFx解决方案中(webpart和extension...
  • BCGControlBar MFC对话框换肤 续
  • PPT文本框中文字方向的设置1 启动PowerPoint2013,插入一个图形或者文本框,右击文本框,编辑文字,输入好文字之后,文字是自左向右排列的。我们要改变样式,右击文本框边缘,从右键菜单中选择设置形状格式。2...
  • Vista 风格文件选择对话框笔记

    千次阅读 2014-08-24 16:12:04
    windows从 vista 开始后面操作系统提供新文件选择打开...Vista之前使用 GetOpenFileName,GetSaveFileName函数来启动打开保存文件对话框并支持hook来扩展文件选择对话框的功能,比如修改打开(或保存)按钮文字,获得文
  • 对话框和常用控件

    千次阅读 2011-04-20 09:22:00
    对话框是Windows应用程序中最重要的用户界面元素之一,是与用户交互的重要手段,在程 序运行过程中,对话框可用于扑捉用户的输入信息或数据。对话框是一个特殊类型的窗口,任何对窗口进行的操作(如:移动、...
  • CAD对话框不见后要如何调出

    千次阅读 2020-04-18 19:11:48
    方法,输入:filedia 回车 1 回车。 filedia Enter new value for FILEDIA <0>: 1 0 不显示对话框。用户仍然可以通过在响应命令提示时输入波浪号 (~) 来请求显示文件对话框。这种方法同样适用于 AutoLISP ...
  • android之对话框通知栏:

    千次阅读 2017-01-16 15:46:49
    警告对话框 AlertDialog: 一个可以有0到3个按钮, 一个单选框或复选框的列表的对话框. 警 告对话框可以创建大多数的交互界面, 是推荐的类型. 进度对话框 ProgressDialog: 显示一个进度环或者一个进度条. 由于它...
  • 程序写作者可以通过在某选项后面加上省略号(…)来表示该菜单项将启动一个对话框对话框的一般形式是包含多种子窗口控件的弹出式窗口,这些控件的大小和位置在程序资源描述文件的「对话框模板」中指定。虽然程序...
  • Qt之对话框消失动画

    2017-08-05 09:49:00
    背景色:主要针对窗口背景色进行了定制,就像groupbox中按钮文字那样,是红色和绿色的背景提示框,其中红色提示框使用了最小化关闭效果,绿色提示框使用了淡出特效 飞出:这4个按钮弹出的对话框都使用了飞出特效,...
  • 使用这个类的最简单的方法是调用静态方法构造自己需要的一切,并返回一个新的Toast对象。 1.使用静态方法创建 // 静态方法可以创建一个简单的Toast public static Toast makeText(Context context,CharSequence ...
  • 很多监控软件要求软件能够在系统重新启动后不用用户去点击图标启动项目,而是直接能够启动运行,方法是写注册表Software\\Microsoft\\Windows\\CurrentVersion\\Run。 参考程序可以见下:(查找程序目录
  • 按照参考样式"word参考样式.gif"完成设置和制作。具体要求如下:(1)设置页边距为上下左右各2.7厘米,装订线在左侧;设置文字水印页面背景,文字为"中国互联网信息中心",水印版式为斜式。(2)设置第一段落文字"中国...
  • jQuery UI之对话框(dialog)的使用

    千次阅读 2016-05-20 10:53:01
    本文介绍了jQuery UI,以dialog为例,介绍了jQuery UI中小部件的基本使用方法
  • 对话框的OnPaint函数的两种写法的区别
  • 向导(Wizard,在macOS上也称为助手)是一种特殊的输入对话框,由一系列页面组成。向导的目的是引导用户逐步进行某个操作过程。QWizard继承了QDialog并表示一个向导,QWizard中的每一个页面都是有一个QWizardPage(一...
  • Android面试宝典总结 –第四章:对话框、信息提示和菜单 本文对Android面试宝典的面试题目做一点总结和记录,希望对大家有帮助。
  • 一、实验环境 1.Windows7x64_SP1 2.anaconda2.5.0 +python2.7(anaconda集成,不需单独安装) ... “新建”,弹出“新建窗口对话框”,选择第一个选项 2.3删除底部的OKCancel按钮 2.4 “Widge...
  • 第十讲用户资源&通知、对话框和警告 ... 样式styte和主题theme资源3. 程序国际化 I18N4. 通过Toast显示消息提示框5. 使用AlertDialog实现对话框6. 使用AlarmManager设置警告(闹钟)7. 题目记录 0....
  • 对话框Component———————————————————————————AlertDialog: 警告框AlertDialog : show() : 显示警告框 没有公开的构造方法, 只能通过其内部类Builder来创建AlertDialog.Builder: ...
  • 下拉刷新模糊效果AutoLayout富文本图表表相关与Tabbar隐藏与显示HUD与Toast对话框其他UI 具体内容 下拉刷新 EGOTableViewPullRefresh - 最早的下拉刷新控件。SVPullToRefresh - 下拉刷新控件。MJRefresh -...
  • word不能启动解决方法

    2009-12-23 09:34:10
    此时,请试试笔者以下所述方法,或许能够挽回你的全部或部分损失。一、自动恢复尚未保存的修改 (WWW.SQ120.COM电脑知识网推荐)Word提供了“自动恢复”功能,可以帮助用户找回程序遇到问题并停止响应时尚未保存的信息...
  • 如之前使用的是“Windows经典样式”,可以先设置为“Windows XP样式”,再换回“Windows经典样式”,这样字体大小就会恢复正常。 Q:念青老师,我在使用Word编辑文字时,将一段带有下划线的文字中的某个字符设置为...
  • 自定义添加文字的电脑桌面RocketDock is an application launcher for Windows modeled after the Mac OS X launch toolbar. It’s a dock that sits along an edge of your screen and contains a collection of ...
  • 16、 对话框样式:标题栏上动态显示当前时间;主界面可用鼠标动态调整大小;当主界面大小改变时,其中按钮相对窗口边缘的位置不变,只有1-5大小发生变化。 提示: 1、 定时器的使用:启动定时器采用Cwnd::SetTimer...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 12,151
精华内容 4,860
关键字:

启动文字样式对话框的方法