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

    千次阅读 2011-05-03 15:51:00
    对话框是一种用户界面,它的主要功能是输出信息和接收用户的输入。对话框与控件是密不可分的,在每个对话框内一般都有一些控件,对话框依靠这些控件与用户进行交互.一个典型的对话框例子是选择了File-Open命令后弹...

     对话框是一种用户界面,它的主要功能是输出信息和接收用户的输入。对话框与控件是密不可分的,在每个对话框内一般都有一些控件,对话框依靠这些控件与用户进行交互.一个典型的对话框例子是选择了File-Open命令后弹出的文件对话框.

      对话框是一种复杂的用户界面,本章的讨论将围绕对话框和基本控件进行。

    5.1对话框和控件的基本概念

    5.1.1对话框的基本概念

    对话框(Dialog)实际上是一个窗口.在MFC中,对话框的功能被封装在了CDialog类中,CDialog类是CWnd类的派生类.

    对话框分为模态对话框和非模态对话框两种.大部分读者都会有这样的经历,当你通过File-Open命令打开一个文件对话框后,再用鼠标去选择菜单将只会发出嘟嘟声,这是因为文件对话框是一个模态对话框.模态对话框垄断了用户的输入,当一个模态对话框打开时,用户只能与该对话框进行交互,而其它用户界面对象收不到输入信息.我们平时所遇到的大部分对话框都是模态对话框。非模态对话框的典型例子是Windows95提供的写字板程序中的搜索对话框,搜索对话框不垄断用户的输入,打开搜索对话框后,仍可与其它用户界面对象进行交互,用户可以一边搜索,一边修改文章,这样就大大方便了使用.

    本节主要介绍模态对话框,在第四节将介绍非模态对话框.

    从MFC编程的角度来看,一个对话框由两部分组成:

    对话框模板资源.对话框模板用于指定对话框的控件及其分布,Windows根据对话框模板来创建并显示对话框.

    对话框类.对话框类用来实现对话框的功能,由于对话框行使的功能各不相同,因此一般需要从CDialog类派生一个新类,以完成特定的功能.

    相应地,对话框的设计包括对话框模板的设计和对话框类的设计两个主要方面.

         与对话框有关的消息主要包括WM_INITDIALOG消息和控件通知消息。在对话框创建时,会收到WM_INITDIALOG消息,对话框对该消息的处理函数是OnInitDialog 。

         OnInitDialog的主要用处是初始化对话框。对话框的控件会向对话框发送控件通知消息,以表明控件的状态发生了变化。

    5.1.2控件的基本概念



    图5.1对话框中的控件

    控件(Control)是独立的小部件,在对话框与用户的交互过程中,控件担任着主要角色.控件的种类较多,图5.1显示了对话框中的一些基本的控件.MFC的控件类封装了控件的功能,表5.1介绍了一些常用的控件及其对应的控件类.

    表5.1

    控件 功能 对应控件类 
    静态正文(Static Text) 显示正文,一般不能接受输入信息。 CStatic 
    图片(Picture) 显式位图、图标、方框和图元文件,一般不能接受输入信息. CStatic 
    编辑框(Edit Box) 输入并编辑正文,支持单行和多行编辑. CEdit 
    命令按钮(Pushbutton) 响应用户的输入,触发相应的事件. CButton 
    检查框(Check Box) 用作选择标记,可以有选中、不选中和不确定三种状态。 CButton 
    单选按钮(Radio Button) 用来从两个或多个选项中选中一项. CButton 
    组框(Group Box) 显示正文和方框,主要用来将相关的一些控件聚成一组. CButton 
    列表框(List Box) 显示一个列表,用户可以从该列表中选择一项或多项. CListBox 
    组合框(Combo Box) 是一个编辑框和一个列表框的组合.分为简易式、下拉式和下拉列表式. CComboBox 
    滚动条(Scroll Bar) 主要用来从一个预定义范围值中迅速而有效地选取一个整数值. CScrollBar 

    控件实际上都是窗口,所有的控件类都是CWnd类的派生类.控件通常是作为对话框的子窗口而创建的,控件也可以出现在视窗口,工具条和状态条中.
    5.2对话框模板的设计

    利用Developer Studio提供的可视化设计工具,用户可以方便地设计对话框模板. 

    请读者按前面章节介绍的方法利用AppWizard建立一个名为Register的MFC应用程序,并在进入MFC AppWizard对话框后按下面几步操作:  

    在第1步中选中Single document以建立一个单文档应用程序.  
    在第4步中使Docking toolbar项不选中,这样AppWizard就不会创建工具条.  
    在第6步中先选择CRegisterView,然后在Base class栏中选择CEditView,这样CRegisterView将是CEditView的继承类,从而使视图具有了编辑功能.  
    编译并运行Register,读者会发现Register居然是个编辑器,它可以打开、编辑和保存文本文件. 当然,Register的目的不仅仅是个编辑器。假设要对某一地区的就业情况进行调查,我们希望Register程序能够登录就业情况数据并将数据存储起来. 

    要登录数据,用对话框是再合适不过了。一个典型的就业情况登录对话框如图5.1所示,本节的任务就是设计如图5.1的中文对话框模板. 

    切换至资源视图,选择Insert-Resource命令,并在Insert Resource对话框中双击Dialog项。完成后在资源视图中会出现一个名为IDD_DIALOG1的新的对话框模板资源。双击IDD_DIALOG1,则会打开该对话框模板的编辑窗口,如图5.2所示。缺省的对话框模板有OK和Cancel两个按钮,在窗口的旁边有一个控件面板,在控件面板上用鼠标选择一个控件,然后在对话框中点击,则相应的控件就被放置到了对话框模板中。图5.3显示了控件面板上的按钮所代表的控件。读者不用记忆图5.3的内容,如果不能确定控件的类型,可将鼠标在某个控件按钮上停留片刻,则会显示一个工具提示,指出该按钮所代表控件的名称。 



    图5.2 缺省的对话框模板 



    图5.3 控件面板 

    提示:若读者看不到控件面板,请在Developer Studio的工具条的空白处单击鼠标右键,并在随之弹出的菜单中选中Controls。  

             

    读者可以在对话框模板中随意加几个控件试试看。当用鼠标选择对话框或控件时,会出现一个围绕它的虚框,拖动虚框的边界可以改变对话框或控件的大小,在Developer Studio的状态条中会显示出所选对象的坐标和尺寸。控件可以被拖动,也可以按箭头键来移动选中的控件。在拖动控件时若按住Ctrl键,则控件会被复制。 

    用户可以一次选择多个控件,选择的方法有两个:1。 在对话框的空白处拖动鼠标,则拖动出来的虚线框内的控件将被选中。2。在选择控件时按住Ctrl键,则可以多重选择。 

    选中控件或对话框后按回车键,则会弹出一个属性对话框,属性对话框用来设置控件或对话框的各种属性。属性对话框是标签式对话框,第一页是常规属性(General)。一个典型的控件属性对话框如图5.4所示.如果对属性对话框中的选项的意思不明白,可以按F1键获得帮助. 



    图5.4 控件属性对话框 

    在控件属性对话框的常规属性中,有一些控件共同的属性: 

    ID属性。用于指定控件的标识符,Windows依靠ID来区分不同的控件。 

    Caption(标题)属性。静态正文、组框、按钮、检查框、单选按钮等控件可以显示标题,用来对控件进行文字说明。控件标题中的字符&使紧跟其后的字符有下划线,按Alt+下划线将启动该控件。若控件是一个单选按钮,则Alt+下划线字符将选择该按钮;若是检查框,则相当于对该检查框按空格键;若是按钮,则将激活按钮命令;若控件是一个静态正文,则将激活按tab顺序紧随其后的下一个控件。 

    Visible属性。用来指定控件是否是可见的。 

    Disable属性。使控件允许或禁止,一个禁止的控件呈灰色显示,不能接收任何输入。  
    Tabstop属性。用户可以按Tab键移动到具有Tabstop属性的控件上。Tab移动的顺序可以由用户指定。按Ctrl+D则Tab顺序会显示出来,如图5.5,用户可以用鼠标来重新指定Tab顺序。缺省的Tab顺序是控件的创建次序。  
    Group属性。用来指定一组控件,用户可以用箭头键在该组控件内移动。在同一组内的单选按钮具有互斥的特性,即在这些单选按钮中只能有一个是选中的。如果一个控件具有Group属性,则这个控件以及按Tab顺序紧随其后的所有控件都属于一组的,直到遇到另一个有Group属性的控件为止。  
    现在就开始进行对话框模板的设计。首先,用鼠标选中对话框,按回车键,在弹出的属性对话框中将ID改为IDD_REGISTER并指定对话框的标题为“登录数据”。需要注意的是,由于要在对话框中显示汉字,因此必须设定正确的语种和字体。请读者在工作区资源视图的Dialog类型中单击鼠标选中IDD_REGISTER项,然后按Alt+Enter键,并在弹出的属性对话框中的Language栏中选择Chinese(P.R.C.)。接着,打开模板的属性对话框,单击Font...按钮,并选择“宋体”。 

    接着,请将对话框模板上的所有控件删除,删除的办法是选择控件后按Del键。为了容纳所有需要的控件,需将对话框的尺寸扩大到280×180。然后,请读者按图5.1和表5.2来设计对话框模板。 

    提示:对话框的尺寸单位不是象素,而是与字体的大小有关。X方向上一个单位等于字符平均宽度的1/4,Y方向上一个单位等于字符平均高度的1/8。这样,随着字体的改变,对话框单位也会改变,对话框本身的总体比例保持不变。  



    表5.2 

    控件类型   ID   标题(Caption)   其它属性  
    组框(个人情况)   缺省   个人情况   缺省  
    组框(单位情况)   缺省   单位情况   缺省  
    静态正文(姓名)   缺省   姓名   缺省  
    编辑框(姓名)   IDC_NAME       缺省  
    检查框(婚否)   IDC_MARRIED   婚否   缺省  
    静态正文(年龄)   缺省   年龄   缺省  
    编辑框(年龄)   IDC_AGE       缺省  
    组框(性别)   缺省   性别   缺省  
    单选按钮(男)   IDC_SEX   男   Group、Tabstop  
    单选按钮(女)   缺省   女   缺省  
    组框(就业状况)   缺省   就业状况   缺省  
    单选按钮(在职)   IDC_WORK   在职   Group、Tabstop  
    单选按钮(下岗)   IDC_WORK1   下岗   缺省  
    静态正文(工作单位)   缺省   工作单位   缺省  
    编辑框(工作单位)   IDC_UNIT       缺省  
    静态正文(单位性质)   缺省   单位性质   缺省  
    组合框(单位性质)   IDC_KIND       Drop List、不排序(不选中Sort风格)、初始化列表项(见下文说明)  
    静态正文(工资收入)   缺省   工资收入   缺省  
    列表框(工资收入)   IDC_INCOME       不排序(不选中Sort)  
    按钮(确定)   IDOK   确定(&Y)   缺省  
    按钮(取消)   IDCANCEL   取消(&C)   缺省  

    请注意组合框IDC_KIND的Drop List属性,Drop List属性是在属性对话框的Styles(风格)页的Type栏中选择的,这使得IDC_KIND成为一个下拉列表式组合框。组合框有简易式(Simple)、下拉式(Dropdown)和下拉列表式(Drop List)三种。简易式组合框包含一个编辑框和一个总是显示的列表框。下拉式组合框同简易式组合框的区别在于仅当单击下滚箭头时才出现列表框。下拉列表式组合框也有一个下拉的列表框,但它的编辑框是只读的,不能输入字符。组合框IDC_KIND不要自动排序,因此需在Styles页中使Sort项不被选中。 

    组合框的列表项可以在设计模板时初始化,而列表框的初始化只能在程序中进行。请读者在组合框IDC_KIND的属性对话框的General页中输入以下几个列表项,以作为单位性质的选项。输入时要注意,换行时不要按回车键,而应按Ctrl+回车键。 

    国有企事业 

    集体企业 

    私有企业 

    中外合资 

    外商独资 



    组合框控件的一个与众不同之处是它有两个尺寸,一个是下拉前的尺寸,一个是下拉后的尺寸。当用鼠标点击组合框上的箭头后,可设定下拉后的尺寸。 

    控件最好都放在对话框模板的蓝色虚框内,控件之间的距离不要太近,否则有可能造成不正确的显示。 

    安置好控件之后,下一步的任务是指定Tab顺序。按Ctrl+D键后,会显示当前的Tab顺序,通过用鼠标点击控件可以设定新的Tab顺序,如果想放弃本次修改,在对话框的空白处点击一下即可。请读者按图5.5安排Tab顺序。 



    图5.5 对话框的Tab顺序 

    最后,需要测试一下对话框。按Ctrl+T,则会弹出一个当前模板的测试对话框,这个对话框的外观和基本行为与程序中将要弹出的对话框一样。这样,读者不用编译运行程序,通过测试对话框就可以评估对话框是否合乎要求。如果发现了错误或不满意的地方,可按ESC键退出测试对话框并重新修改对话框模板。 

    至此,对话框模板的设计就完成了。 

    5.3 对话框类的设计

    完成对话框模板的设计后,就需要设计一个对话框类以实现对话框的功能。设计对话框类主要包括下面几步: 

    创建对话框类。该类应从CDialog类派生。 

    为对话框类加入与控件相对应的成员变量。 

    为对话框进行初始化工作。 

    增加对控件通知消息的处理 



    5.3.1对话框类的创建 

    利用ClassWizard,程序员可以十分方便的创建MFC窗口类的派生类,对话框类也不例外。请读者按以下几步操作: 

    打开IDD_REGISTER对话框模板,然后按Ctrl+W进入ClassWizard。 

    进入ClassWizard后,ClassWizard发现IDD_REGISTER是一个新的对话框模板,于是它会询问是否要为IDD_REGISTER创建一个对话框类。按OK键确认。 

    如图5.6在Create New Class对话框中,在Name栏中输入CRegisterDialog,在Base class栏中选择CDialog,在Dialog ID栏中选择IDD_REGISTER。按Create按钮后,对话框类CRegisterDialog即被创建。 



    图5.6 Create New Class对话框 

    ClassWizard自动使类CRegesterDialog与IDD_REGISTER模板联系起来。 

    提示:只要想创建的类是某一MFC窗口类的派生类,一般都可以利用ClassWizard来自动完成创建。创建的一般方法是:打开ClassWizard,选择Add Class->New,然后在Create New Class对话框中输入新类的类名,选择其MFC基类,如果是对话框类,则还要选择对话框的ID。  

    5.3.2为对话框类加入成员变量 



              对话框的主要功能是输出和输入数据,例子中的登录数据对话框的任务就是输入数据。对话框需要有一组成员变量来存储数据。在对话框中,控件用来表示或输入数据,因此,存储数据的成员变量应该与控件相对应。 

    与控件对应的成员变量即可以是一个数据,也可以是一个控件对象,这将由具体需要来确定。例如,可以为一个编辑框控件指定一个数据变量,这样就可以很方便地取得或设置编辑框控件所代表的数据,如果想对编辑框控件进行控制,则应该为编辑框指定一个CEdit对象,通过CEdit对象,程序员可以控制控件的行为。需要指出的是,不同类的控件对应的数据变量的类型往往是不一样的,而且一个控件对应的数据变量的类型也可能有多种。表5.3说明了控件的数据变量的类型。 

    表5.3 

    控件   数据变量的类型  
    编辑框   CString, int, UINT, long, DWORD, float, double, short, BOOL, COleDateTime, COleCurrency  
    普通检查框   BOOL(真表示被选中,假表示未选中)  
    三态检查框   int(0表示未选中,1表示选中,2表示不确定状态)  
    单选按钮(组中的第一个按钮)   int(0表示选择了组中第一个单选按钮,1表示选择了第二个...,-1表示没有一个被选中)  
    不排序的列表框   CString(为空则表示没有一个列表项被选中),   
    int(0表示选择了第一项,1表示选了第二项,-1表示没有一项被选中)  

    下拉式组合框   CString, int(含义同上)  
    其它列表框和组合框   CString(含义同上)  

    利用ClassWizard可以很方便地为对话框类CRegisterDialog加入成员变量。请读者按下列步骤操作。 

    按Ctrl+W进入ClassWizard。 

    选择ClassWizard上部的Member Variables标签,然后在Class name栏中选择CRegisterDialog。这时,在下面的变量列表中会出现对话框控件的ID,如图5.7所示。 



    图5.7 ClassWizard对话框 

    双击列表中的ID_AGE会弹出Add Member Variable对话框,如图5.8所示。在Member variable name栏中输入m_nAge,在Category栏中选择Value,在Variable type栏中选择UINT。按OK按钮后,数据变量m_nAge就会被加入到变量列表中。 



    图5.8 Add Member Variable对话框 

    仿照第3步和表5.4,为各个控件加入相应的成员变量。 

    将m_nAge的值限制在16到65之间。方法是先选择m_nAge,然后在ClassWizard对话框的左下角输入最大和最小值。m_nAge代表年龄,这里规定被调查的人的年龄应在16岁以上,64岁以下。有了这个限制后,对话框会对输入的年龄值进行有效性检查,若输入的值不在限制范围内,则对话框会提示用户输入有效的值。 

    表5.4 

    控件ID  
    变量类型  
    变量名  

    IDC_AGE  
    UINT  
    m_nAge  

    IDC_INCOME  
    CString  
    m_strIncome  

    IDC_INCOME  
    CListBox  
    m_ctrlIncome  

    IDC_KIND  
    CString  
    m_strKind  

    IDC_MARRIED  
    BOOL  
    m_bMarried  

    IDC_NAME  
    CString  
    m_strName  

    IDC_SEX  
    int  
    m_nSex  

    IDC_UNIT  
    CString  
    m_strUnit  

    IDC_WORK  
    int  
    m_nWork  


    读者会注意到控件IDC_INCOME居然有两个变量,一个是CString型的,一个是CListBox型的,这是完全合法的,不会引起任何冲突。之所以要加入CListBox型的变量,是因为列表框的初始化要通过CListBox对象进行。 

    提示:在ClassWizard中可分别为一个控件指定一个数据变量和一个控件对象,这样做的好处是即能方便地获得数据,又能方便地控制控件。  

    5.3.3对话框的初始化 

    对话框的初始化工作一般在构造函数和OnInitDialog函数中完成。在构造函数中的初始化主要是针对对话框的数据成员。读者可以找到CRegisterDialog的构造函数,如清单5.1所示。 



    清单5.1 CRegisterDialog的构造函数 

    CRegisterDialog::CRegisterDialog(CWnd* pParent /*=NULL*/) 

    : CDialog(CRegisterDialog::IDD, pParent) 

    { 

    //{{AFX_DATA_INIT(CRegisterDialog) 

    m_nAge = 0; 

    m_strIncome = _T(""); 

    m_strKind = _T(""); 

    m_bMarried = FALSE; 

    m_strName = _T(""); 

    m_nSex = -1; 

    m_strUnit = _T(""); 

    m_nWork = -1; 

    //}}AFX_DATA_INIT 

    } 





    可以看出,对数据成员的初始化是由ClassWizard自动完成的。若读者对初值的含义还不太清楚,请参看表5.3。 

    在对话框创建时,会收到WM_INITDIALOG消息,对话框对该消息的处理函数是OnInitDialog。调用OnInitDialog时,对话框已初步创建,对话框的窗口句柄也已有效,但对话框还未被显示出来。因此,可以在OnInitDialog中做一些影响对话框外观的初始化工作。OnInitDialog对对话框的作用与OnCreate对CMainFrame的作用类似。 

    提示:MFC窗口的初始化工作一般在OnCreate成员函数中进行,但对话框的初始化工作最好在OnInitDialog中进行。  

    OnInitDialog是WM_INITDIALOG消息的处理函数,所以要用ClassWizard为RegisteritDialog类增加一个WM_INITDIALOG消息的处理函数,增加的方法是进入ClassWizard后,先选中MessageMaps标签,然后在Class name中选择CRegisterDialog,在Object IDs栏中选择CRegisterDialog,在Messages栏中找到WM_INITDIALOG并双击之,最后按OK按钮退出ClassWizard。 

    请读者按清单5.2修改OnInitDialog函数。 



    清单5.2 OnInitDialog函数 

    BOOL CRegisterDialog::OnInitDialog()  

    { 

    CDialog::OnInitDialog(); 



    // TODO: Add extra initialization here 



    m_ctrlIncome.AddString("500元以下"); 

    m_ctrlIncome.AddString("500-1000元"); 

    m_ctrlIncome.AddString("1000-2000元"); 

    m_ctrlIncome.AddString("2000元以上"); 



    return TRUE; // return TRUE unless you set the focus to a control 

    // EXCEPTION: OCX Property Pages should return FALSE 

    } 

    CRegisterDialog::OnInitDialog()的主要任务是对工资收入列表框的列表项进行初始化。调用CListBox::AddString可将指定的字符串加入到列表框中。由于该列表是不自动排序的,因此AddString将表项加在列表框的末尾。 

    5.3.4对话框的数据交换机制 

    对话框的数据成员变量存储了与控件相对应的数据。数据变量需要和控件交换数据,以完成输入或输出功能。例如,一个编辑框即可以用来输入,也可以用来输出:用作输入时,用户在其中输入了字符后,对应的数据成员应该更新;用作输出时,应及时刷新编辑框的内容以反映相应数据成员的变化。对话框需要一种机制来实现这种数据交换功能,这对对话框来说是至关重要的。 

    MFC提供了类CDataExchange来实现对话框类与控件之间的数据交换(DDX),该类还提供了数据有效机制(DDV)。数据交换和数据有效机制适用于编辑框、检查框、单选按钮、列表框和组合框。 

    数据交换的工作由CDialog::DoDataExchange来完成。读者可以找到CRegisterDialog::DoDataExchange函数,如清单5.3所示。 

    清单5.3 DoDataExchange函数 

    void CRegisterDialog::DoDataExchange(CDataExchange* pDX) 

    { 

    CDialog::DoDataExchange(pDX); 

    //{{AFX_DATA_MAP(CRegisterDialog) 

    DDX_Control(pDX, IDC_INCOME, m_ctrlIncome); 

    DDX_LBString(pDX, IDC_INCOME, m_strIncome); 

    DDX_CBString(pDX, IDC_KIND, m_strKind); 

    DDX_Check(pDX, IDC_MARRIED, m_bMarried); 

    DDX_Text(pDX, IDC_NAME, m_strName); 

    DDX_Radio(pDX, IDC_SEX, m_nSex); 

    DDX_Text(pDX, IDC_UNIT, m_strUnit); 

    DDX_Radio(pDX, IDC_WORK, m_nWork); 

    DDX_Text(pDX, IDC_AGE, m_nAge); 

    DDV_MinMaxUInt(pDX, m_nAge, 16, 65); 

    //}}AFX_DATA_MAP 

    } 

    读者可以看出,该函数中的代码是由ClassWizard自动加入的。DoDataExchange只有一个参数,即一个CDataExchange对象的指针pDX。在该函数中调用了DDX函数来完成数据交换,调用DDV函数来进行数据有效检查。 

    当程序需要交换数据时,不要直接调用DoDataExchange函数,而应该调用CWnd::UpdateData。UpdataData函数内部调用了DoDataExchange。该函数只有一个布尔型参数,它决定了数据传送的方向。调用UpdateData(TRUE)将数据从对话框的控件中传送到对应的数据成员中,调用UpdateData(FALSE)则将数据从数据成员中传送给对应的控件。 

    在缺省的CDialog::OnInitDialog中调用了UpdateData(FALSE),这样,在对话框创建时,数据成员的初值就会反映到相应的控件上。若用户是按了OK(确定)按钮退出对话框,则对话框认为输入有效,就会调用UpdataData(TRUE)将控件中的数据传给数据成员。图5.9描绘了对话框的这种数据交换机制。 



    图5.9 对话框的数据交换 

    5.3.5对话框的运行机制 

    在程序中运行模态对话框有两个步骤: 

    在堆栈上以变量的形式构建一个对话框对象。 

    调用CDialog::DoModal ( )。 



    DoModal负责对模态话框的创建和撤销。在创建对话框时,DoModal的任务包括载入对话框模板资源、调用OnInitDialog初始化对话框和将对话框显示在屏幕上。完成对话框的创建后,DoModal启动一个消息循环,以响应用户的输入。由于该消息循环截获了几乎所有的输入消息,使主消息循环收不到对对话框的输入,致使用户只能与模态对话框进行交互,而其它用户界面对象收不到输入信息。 

    若用户在对话框内点击了ID为IDOK的按钮(通常该按钮的标题是“确定”或“OK”),或按了回车键,则CDialog::OnOK将被调用。OnOK首先调用UpdateData(TRUE)将数据从控件传给对话框成员变量,然后调用CDialog::EndDialog关闭对话框。关闭对话框后,DoModal会返回值IDOK。 

    若用户点击了ID为IDCANCEL的按钮(通常其标题为“取消”或“Cancel”),或按了ESC键,则会导致CDialog::OnCancel的调用。该函数只调用CDialog::EndDialog关闭对话框。关闭对话框后,DoModal会返回值IDCANCEL。 

    程序根据DoModal的返回值是IDOK还是IDCANCEL就可以判断出用户是确定还是取消了对对话框的操作。 

    在弄清了对话框的运行机制后,下面让我们来就可以实现Register程序登录数据的功能。 

    首先,将Register工程的工作区切换至资源视图。打开IDR_MAINFRAME菜单资源,在Edit菜单的底端加入一个名为“登录数据”的新菜单项,并令其ID为ID_EDIT_REGISTER(最好在该项之前加一条分隔线,以便和前面的菜单项分开)。注意不要忘了把菜单资源的语种设置成中文,否则菜单中将显示不出中文来。设置的方法是先在工作区资源视图中选择IDR_MAINFRAME菜单资源,然后按Alt+Enter键,并在弹出的属性对话框中的Language栏中选择Chinese(P.R.C.)。 

    接着,用ClassWizard为该菜单命令创建命令处理函数CRegisterView::OnEditRegister。注意,OnEditRegister是类CRegisterView的成员函数,这是因为CRegisterView要负责打开和关闭登录数据对话框,并将从对话框中输入的数据在视图中输出。 

    然后,请读者在RegisterView.cpp文件的开头加入下面一行 

    #include "RegisterDialog.h" 

    最后,按清单5.4修改程序。 

    清单5.4 OnEditRegister函数 

    void CRegisterView::OnEditRegister()  

    { 

    // TODO: Add your command handler code here 

    CRegisterDialog dlg; 

    if(dlg.DoModal()==IDOK) 

    { 

    CString str; 

    //获取编辑正文 

    GetWindowText(str); 

    //换行 

    str+="/r/n"; 

    str+="姓名:"; 

    str+=dlg.m_strName; 

    str+="/r/n"; 

    str+="性别:"; 

    str+=dlg.m_nSex?"女":"男"; 

    str+="/r/n"; 

    str+="年龄:"; 

    CString str1; 

    //将数据格式输出到字符串对象中 

    str1.Format("%d",dlg.m_nAge); 

    str+=str1; 

    str+="/r/n"; 

    str+="婚否:"; 

    str+=dlg.m_bMarried?"已婚":"未婚"; 

    str+="/r/n"; 

    str+="就业状况:"; 

    str+=dlg.m_nWork?"下岗":"在职"; 

    str+="/r/n"; 

    str+="工作单位:"; 

    str+=dlg.m_strUnit; 

    str+="/r/n"; 

    str+="单位性质:"; 

    str+=dlg.m_strKind; 

    str+="/r/n"; 

    str+="工资收入:"; 

    str+=dlg.m_strIncome; 

    str+="/r/n"; 

    //更新编辑视图中的正文 

    SetWindowText(str); 

    } 

    } 

    在OnEditRegister函数中,首先构建了一个CRegisterDialog对象,然后调用CDialog::DoModal来实现模态对话框。如果DoModal返回IDOK,则说明用户确认了登录数据的操作,程序需要将录入的数据在编辑视图中输出。程序用一个CString对象来作为编辑正文的缓冲区,CString是一个功能强大的字符串类,它的最大特点在于可以存储动态改变大小的字符串,这样,用户不必担心字符串的长度超过缓冲区的大小, 使用十分方便。 

    在输出数据时,程序首先调用CWnd::GetWindowText获得编辑正文,这是一个多行的编辑正文。CWnd::GetWindowText用来获取窗口的标题,若该窗口是一个控件,则获取的是控件内的正文。CRegisterView是CEditView的继承类,而CEditView实际上包含了一个编辑控件,因此在CRegisterView中调用GetWindowText获得的是编辑正文。 

    然后,程序在该编辑正文的末尾加入新的数据。在程序中大量使用了CString类的重载操作符“+=”,该操作符的功能是将操作符右侧字符串添加到操作符左侧的字符串的末尾。注意在多行编辑控件中每行末尾都有一对回车和换行符。在程序中还调用了CString::Format来将数据格式化输出到字符串中,Format的功能与sprintf类似。最后,调用CWnd::SetWindowText来更新编辑视图中的正文。 

    编译并运行Register,打开登录数据对话框,输入一些数据试试。现在,Register已经是一个简易的数据库应用程序了,它可以将与就业情况有关的数据输出到一个编辑视图中。用户可以编辑视图中的正文,并将结果保存在文本文件中。 

    5.3.6处理控件通知消息 

    虽然Register已经可以登录数据了,但读者会很快会发现该程序还有一些不完善的地方: 

    登录完一个人的数据后,对话框就关闭了,若用户有很多人的数据要输入,则必须频繁地打开对话框,很不方便。在登录数据时,应该使对话框一直处于打开状态。 

    登录数据对话框分个人情况和单位情况两组,若被调查人是下岗职工,则不必输入单位情况。程序应该能够对用户的输入及时地作出反应,即当用户选择了“下岗”单选按钮时,应使单位情况组中的控件禁止。一个禁止的控件呈灰色示,并且不能接收用户的输入。 

    要解决上述问题,就必须对控件通知消息进行处理。当控件的状态因为输入等原因而发生变化时,控件会向其父窗口发出控件通知消息。例如,如果用户在登录数据对话框中的某一按钮(包括普通按钮、检查框和单选按钮)上单击鼠标,则该按钮会向对话框发送BN_CLICKED消息。对话框根据按钮的ID激活相应的BN_CLICKED消息处理函数,以对单击按钮这一事件作出反应。通过对按钮的BN_CLICKED消息的处理,我们可以使登录数据对话框的功能达到上述要求。 

    首先,让我们来解决第一个问题。我们的设想是修改原来的“确定(Y)”按钮,使得当用户点击该按钮后,将数据输出到视图中,并且对话框不关闭,以便用户输入下一个数据。请读者按下面几步进行修改。 

    修改登录数据对话框的“确定(Y)”按钮,使该按钮的标题变为“添加(&A)”,ID变为IDC_ADD。这样,当用户点击该按钮后,对话框会收到BN_CLICKED消息。由于这个BN_CLICKED消息对应的按钮ID不是IDOK,不会触发OnOK消息处理函数,因此不会关闭对话框。 

    为按钮IDC_ADD的BN_CLICKED消息创建消息处理函数。创建的方法是进入ClassWizard后,选Message Maps页并在Class name栏中选择CRegisterDialog,然后在Object IDs栏中选择IDC_ADD,在Messages栏中双击BN_CLICKED。在确认使用缺省的消息处理函数名OnAdd后,按回车键退出ClassWizard。 

    OnAdd要向编辑视图输出正文,就必须获得一个指向CRegisterView对象的指针以访问该对象。为此,请在CRegisterDialog类的说明中加入下面一行
    Cwnd* m_pParent;
    注意不要加在AFX注释对中。 

    为实现IDC_ADD按钮的功能,请按清单5.5和清单5.6修改程序。主要的改动是把原来由CRegiserView::OnEditRegister完成的在视图中输出数据的任务交给CRegisterDialog::OnAdd来完成。 

    清单5.5 CRegisterView::OnEditRegister函数 

    void CRegisterView::OnEditRegister()  

    { 

    // TODO: Add your command handler code here 

    CRegisterDialog dlg(this); 

    dlg.DoModal(); 

    } 

    清单5.6 CRegisterDialog类的部分源代码 

    CRegisterDialog::CRegisterDialog(CWnd* pParent /*=NULL*/) 

    : CDialog(CRegisterDialog::IDD, pParent) 

    { 

    //{{AFX_DATA_INIT(CRegisterDialog) 

    . . . . . . 

    //}}AFX_DATA_INIT 

    m_pParent=pParent; 

    } 

    void CRegisterDialog::OnAdd()  

    { 

    // TODO: Add your control notification handler code here 

    //更新数据 

    UpdateData(TRUE); 

    //检查数据是否有效 

    if(m_strName=="" || m_nSex<0 || m_nWork<0 || m_strUnit==""  

    || m_strKind=="" || m_strIncome=="") 

    { 

    AfxMessageBox("请输入有效数据"); 

    return; 

    } 

    CString str; 

    //获取编辑正文 

    m_pParent->GetWindowText(str); 

    //换行 

    str+="/r/n"; 

    str+="姓名:"; 

    str+=m_strName; 

    str+="/r/n"; 

    str+="性别:"; 

    str+=m_nSex?"女":"男"; 

    str+="/r/n"; 

    str+="年龄:"; 

    CString str1; 

    //将数据格式输出到字符串对象中 

    str1.Format("%d",m_nAge); 

    str+=str1; 

    str+="/r/n"; 

    str+="婚否:"; 

    str+=m_bMarried?"已婚":"未婚"; 

    str+="/r/n"; 

    str+="就业状况:"; 

    str+=m_nWork?"下岗":"在职"; 

    str+="/r/n"; 

    str+="工作单位:"; 

    str+=m_strUnit; 

    str+="/r/n"; 

    str+="单位性质:"; 

    str+=m_strKind; 

    str+="/r/n"; 

    str+="工资收入:"; 

    str+=m_strIncome; 

    str+="/r/n"; 

    //更新编辑视图中的正文 

    m_pParent->SetWindowText(str); 

    } 

    CRegisterDialog的构造函数有一个参数pParent,该参数是一个指向CWnd对象的指针,用于指定对话框的父窗口或拥有者窗口。在CRegisterView:: OnEditRegister函数中,在构建CRegisterDialog对象时指定了this参数,this指针指向CRegisterView对象本身。这样在调用CRegisterDialog的构造函数时,this指针值被赋给了CRegisterDialog的成员m_pParent。OnAdd函数可利用m_pParent来访问对话框的拥有者即CRegisterView对象。 

    提示:术语父窗口(Parent)是相对于子窗口而言。若某一个窗口拥有一个子窗口(Child),则该窗口就被称为子窗口的父窗口。子窗口就是具有WS_CHILD风格的窗口,子窗口依赖于父窗口且完全被限制在父窗口内部。拥有者窗口(owner)相对于被拥有者窗口而言。若某一个窗口拥有一个非子窗口,则该窗口被称为拥有者窗口。被拥有窗口(owned)不具有WS_CHILD风格,可在屏幕上任意移动。  

    当用户用鼠标点击IDC_ADD按钮时,该按钮的BN_CLICKED消息处理函数CRegisterDialog::OnAdd将被调用。在OnAdd中,首先调用了UpdateData(TRUE)以把数据从控件传给对话框的数据成员变量。然后,程序要对数据的有效性进行检查,如果输入的数据不完全有效,则会显示一个消息对话框,提示用户输入有效的数据。接下来进行的工作是在视图中输出数据,这部分代码与清单5.4类似,读者应该比较熟悉了。 

    完成上述工作后,登录数据对话框就变得较为实用了。打开对话框后,用户可以方便地输入多人的数据,只有按了取消按钮后,对话框才会关闭。 

    接下来让我们来解决第二个问题。解决该问题的关键在于当用户点击“在职”或“下岗”单选按钮时,程序要对收到的BN_CLICKED消息作出响应。有些读者可能会想到为两个单选按钮分别创建BN_CLICKED消息处理函数,这在只有两个单选按钮的情况下是可以的,但如果一组内有多个单选按钮,则分别创建消息处理函数就比较麻烦了。利用MFC提供的消息映射宏ON_CONTROL_RANGE可以避免这种麻烦,该映射宏把多个ID连续的控件发出的消息映射到同一个处理函数上。这样,我们只要编写一个消息处理函数,就可以对“在职”和“下岗”两个单选按钮的BN_CLICKED消息作出响应。ClassWizard不支持ON_CONTROL_RANGE宏,所以我们必须手工创建单选按钮的消息映射和消息处理函数。 

    首先,在CRegisterDialog类的头文件中加入消息处理函数的声明,该函数名为OnWorkClicked,如清单5.7所示。 

    清单5.7 BN_CLICKED消息处理函数OnWorkClicked的声明 

    . . . . . .  

    protected: 

    void OnWorkClicked(UINT nCmdID); 

    // Generated message map functions 

    //{{AFX_MSG(CRegisterDialog) 

    virtual BOOL OnInitDialog(); 

    afx_msg void OnAdd(); 

    //}}AFX_MSG 

    . . . . . . 

    然后,在CRegisterDialog类的消息映射中加入ON_CONTROL_RANGE映射,如清单5.8所示。ON_CONTROL_RANGE映射的形式是ON_CONTROL_RANGE 

    清单5.8 在CRegisterDialog类的消息映射中加入ON_CONTROL_RANGE映射 

    BEGIN_MESSAGE_MAP(CRegisterDialog, CDialog) 

    //{{AFX_MSG_MAP(CRegisterDialog) 

    ON_BN_CLICKED(IDC_ADD, OnAdd) 

    //}}AFX_MSG_MAP 

    ON_CONTROL_RANGE(BN_CLICKED, IDC_WORK, IDC_WORK1, OnWorkClicked) 

    END_MESSAGE_MAP() 

    ON_CONTROL_RANGE消息映射宏的第一个参数是控件消息码,第二和第三个参数分别指明了一组连续的控件ID中的头一个和最后一个ID,最后一个参数是消息处理函数名。如果读者是按表5.2的顺序放置控件的则IDC_WORK和IDC_WORK1应该是连续的。这样,无论用户是在IDC_WORK还是在IDC_WORK1单选按钮上单击,都会调用OnWorkClicked消息处理函数。 

    提示:如果不能确定两个ID是否是连续的,请用File->Open命令打开resource.h文件,在该文件中有对控件ID值的定义。如果发现两个ID是不连续的,读者可以改变对ID的定义值使之连续,但要注意改动后的值不要与别的ID值发生冲突。 

    最后,在CRegisterDialog类所在CPP文件的最后插入消息处理函数CRegisterDialog::OnWorkClicked,如清单5.9所示。 

    清单5.9 CRegisterDialog::OnWorkClicked消息处理函数 

    void CRegisterDialog::OnWorkClicked(UINT nCmdID)  

    { 

    //判断“在职”单选按钮是否被选中 

    if(IsDlgButtonChecked(IDC_WORK)) 

    { 

    //使控件允许 

    GetDlgItem(IDC_UNIT)->EnableWindow(TRUE); 

    GetDlgItem(IDC_KIND)->EnableWindow(TRUE); 

    GetDlgItem(IDC_INCOME)->EnableWindow(TRUE); 

    } 

    else 

    { 

    //清除编辑框的内容并使之禁止 

    GetDlgItem(IDC_UNIT)->SetWindowText(""); 

    GetDlgItem(IDC_UNIT)->EnableWindow(FALSE); 

    //使组合框处于未选择状态并使之禁止 

    CComboBox *pComboBox=(CComboBox *)GetDlgItem(IDC_KIND); 

    pComboBox->SetCurSel(-1); 

    pComboBox->EnableWindow(FALSE); 

    //使列表框处于未选择状态并使之禁止 

    m_ctrlIncome.SetCurSel(-1); 

    m_ctrlIncome.EnableWindow(FALSE); 

    } 

    } 

    OnWorkClicked函数判断“在职”单选按钮是否被选中。若该按钮被选中,则使单位情况组中的控件允许,若该按钮未被选中,则说明“下岗”按钮被选中,这时应使控件禁止,清除编辑框中的正文, 并且使组合框和列表框处于未选中状态。 

    在OnWorkClicked函数中主要调用了下列函数: 

    CWnd::IsDlgButtonChecked函数,用来判断单选按钮或检查框是否被选择,该函数的声明为
    UINT IsDlgButtonChecked(int nIDButton) const;
    参数nIDButton为按钮的ID。若按钮被选择,则函数返回1,否则返回0,若按钮处于不确定状态,则返回值为2。 

    CWnd::GetDlgItem函数,用来获得指向某一控件的指针,该函数的声明为
    CWnd* GetDlgItem(int nID) const;
    参数nID为控件的ID。该函数返回一个指定控件的CWnd对象指针,通过该指针,程序可以对控件进行控制。 

    CWnd::EnableWindow函数,该函数使窗口允许或禁止,禁止的窗口呈灰色显示,不能接收键盘和鼠标的输入。该函数的声明是
    BOOL EnableWindow( BOOL bEnable = TRUE );
    若参数bEnable的值为TRUE,则窗口被允许,若bEnable的值为FALSE,则窗口被禁止。 

    CListBox::SetCurSel和CComboBox::SetCurSel函数功能类似,用来使列表中的某一项被选中,选中的项呈高亮度显示。函数的声明是
    int SetCurSel(int nSelect);
    参数nSelect指定了新选项的索引,第一项的索引值为0,若nSelect的值为-1,那么函数将清除以前的选择,使列表处于未选择状态。 

    有时,需要将GetDlgItem返回的CWnd指针强制转换成控件对象的指针,以便调用控件对象专有的成员函数对控件进行控制。例如,在程序中GetDlgItem(IDC_KIND)返回的指针被强制转换成CComboBox类型,只有这样,才能调用CComboBox::SetCurSel成员函数。 

    为了对控件进行查询和控制,在程序中采用了两种访问控件的方法。一种方法是直接利用ClassWizard提供的控件对象,例如m_ctrlIncome列表框对象。另一种方法是利用CWnd类提供的一组管理对话框控件的成员函数,例如程序中用到的GetDlgItem和IsDlgButtonChecked。这两种方法是在对话框内访问控件的常用方法,读者都应该掌握。表5.5列出了管理对话框控件的Cwnd成员函数。 



    表5.5 用来管理对话框控件的CWnd成员函数 

    函数名  
    功能  

    CheckDlgButton  
    选中或不选中按钮控件。  

    CheckRadioButton  
    选择一个指定的单选按钮并使同组内的其它单选按钮不被选择。  

    DlgDirList  
    往一个列表框中添加文件、目录或驱动器的列表。  

    DlgDirListComboBox  
    往一个组合框中的列表框内添加文件、目录或驱动器的列表。  

    DlgDirSelect  
    从一个列表框中获得当前选择的文件、目录或驱动器。  

    DlgDirSelectBomboBox  
    从一个组合框中获得当前选择的文件、目录或驱动器。  

    GetCheckedRadioButton  
    返回指定的单选按钮组中被选择的单选按钮的ID。  

    GetDlgItem  
    返回一个指向一给定的控件的临时对象的指针。  

    GetDlgItemInt  
    返回在一个指定的控件中由正文表示的数字值。  

    GetDlgItemText  
    获得在一个控件内显示的正文。  

    GetNextDlgGroupItem  
    返回一个指向一组控件内的下一个或上一个控件的临时对象的指针。  

    GetNextDlgTabItem  
    返回下一个tab顺序的控件的临时对象的指针。  

    IsDlgButtonChecked  
    返回一个按钮控件的状态。  

    SendDlgItemMessage  
    把一个消息传送给一个控件。  

    SetDlgItemInt  
    将一个整数转换为正文,并将此正文赋给控件。  

    SetDlgItemText  
    设置一个控件显示的正文。  


    编译并运行Register看看,现在的登录数据对话框已经比较令人满意了。 

    5.4 非模态对话框

    5.4.1 非模态对话框的特点 

    与模态对话框不同,非模态对话框不垄断用户的输入,用户打开非模态对话框后,仍然可以与其它界面进行交互。 

    非模态对话框的设计与模态对话框基本类似,也包括设计对话框模板和设计CDialog类的派生类两部分。但是,在对话框的创建和删除过程中,非模态对话框与模态对话框相比有下列不同之处: 

    非模态对话框的模板必须具有Visible风格,否则对话框将不可见,而模态对话框则无需设置该项风格。更保险的办法是调用CWnd::ShowWindow(SW_SHOW)来显示对话框,而不管对话框是否具有Visible风格。 

    非模态对话框对象是用new操作符在堆中动态创建的,而不是以成员变量的形式嵌入到别的对象中或以局部变量的形式构建在堆栈上。通常应在对话框的拥有者窗口类内声明一个指向对话框类的指针成员变量,通过该指针可访问对话框对象。 

    通过调用CDialog::Create函数来启动对话框,而不是CDialog::DoModal,这是模态对话框的关键所在。由于Create函数不会启动新的消息循环,对话框与应用程序共用同一个消息循环,这样对话框就不会垄断用户的输入。Create在显示了对话框后就立即返回,而DoModal是在对话框被关闭后才返回的。众所周知,在MFC程序中,窗口对象的生存期应长于对应的窗口,也就是说,不能在未关闭屏幕上窗口的情况下先把对应的窗口对象删除掉。由于在Create返回后,不能确定对话框是否已关闭,这样也就无法确定对话框对象的生存期,因此只好在堆中构建对话框对象,而不能以局部变量的形式来构建之。 

    必须调用CWnd::DestroyWindow而不是CDialog::EndDialog来关闭非模态对话框。调用CWnd::DestroyWindow是直接删除窗口的一般方法。由于缺省的CDialog::OnOK和CDialog::OnCancel函数均调用EndDialog,故程序员必须编写自己的OnOK和OnCancel函数并且在函数中调用DestroyWindow来关闭对话框。 

    因为是用new操作符构建非模态对话框对象,因此必须在对话框关闭后,用delete操作符删除对话框对象。在屏幕上一个窗口被删除后,框架会调用CWnd::PostNcDestroy,这是一个虚拟函数,程序可以在该函数中完成删除窗口对象的工作,具体代码如下
    void CModelessDialog::PostNcDestroy
    {
    delete this; //删除对象本身
    }
    这样,在删除屏幕上的对话框后,对话框对象将被自动删除。拥有者对象就不必显式的调用delete来删除对话框对象了。 

    必须有一个标志表明非模态对话框是否是打开的。这样做的原因是用户有可能在打开一个模态对话框的情况下,又一次选择打开命令。程序根据标志来决定是打开一个新的对话框,还是仅仅把原来打开的对话框激活。通常可以用拥有者窗口中的指向对话框对象的指针作为这种标志,当对话框关闭时,给该指针赋NULL值,以表明对话框对象已不存在了。 

    提示:在C++编程中,判断一个位于堆中的对象是否存在的常用方法是判断指向该对象的指针是否为空。这种机制要求程序员将指向该对象的指针初始化为NULL值,在创建对象时将返回的地址赋给该指针,而在删除对象时将该指针置成NULL值。  

    根据上面的分析,我们很容易把Register程序中的登录数据对话框改成非模态对话框。这样做的好处在于如果用户在输入数据时发现编辑视图中有错误的数据,那么不必关闭对话框,就可以在编辑视图中进行修改。 

    请读者按下面几步操作: 

    在登录数据对话框模板的属性对话框的More Styles页中选择Visible项。 

    在RegisterView.h头文件的CRegisterView类的定义中加入
    public:
    CRegisterDialog* m_pRegisterDlg; 

    在RegisterView.h头文件的头部加入对CRegisterDialog类的声明
    class CRegisterDialog;
    加入该行的原因是在CRegisterView类中有一个CRegisterDialog类型的指针,因此必须保证CRegisterDialog类的声明出现在CRegisterView之前,否则编译时将会出错。解决这个问题有两种办法,一种办法是保证在#include “RegisterView.h”语句之前有#include “RegisterDialog.h”语句,这种办法造成了一种依赖关系,增加了编译负担,不是很好;另一种办法是在CRegisterView类的声明之前加上一个对CRegisterDialog的声明来暂时“蒙蔽”编译器,这样在有#include “RegisterView.h”语句的模块中,除非要用到CRegisterDialog类,否则不用加入#include “RegisterDialog.h”语句。 

    在RegisterDialog.cpp文件的头部的#include语句区的末尾添加下面两行
    #include "RegisterDoc.h"
    #include "RegisterView.h" 

    利用ClassWizard为CRegisterDialog类加入OnCancel和PostNcDestroy成员函数。加入的方法是进入ClassWizard后选择Message Maps页,并在Class name栏中选择CRegisterDialog。然后,在Object IDs栏中选择IDCANCEL后,在Messages栏中双击BN_CLICKED,这就创建了OnCancel。要创建PostNcDestroy,先在Object IDs栏中选择CRegisterDialog,再在Messages栏中双击PostNcDestroy即可。 

    分别按清单5.10和5.11,对CRegisterView类和CRegisterDialog类进行修改。 

    清单5.10 CRegisterView类的部分代码 

    CRegisterView::CRegisterView() 

    { 

    // TODO: add construction code here 



    m_pRegisterDlg=NULL; //指针初始化为NULL 

    } 



    void CRegisterView::OnEditRegister()  

    { 

    // TODO: Add your command handler code here 





    if(m_pRegisterDlg) 

    m_pRegisterDlg->SetActiveWindow(); //激活对话框 

    else 

    { 

    //创建非模态对话框 

    m_pRegisterDlg=new CRegisterDialog(this); 

    m_pRegisterDlg->Create(IDD_REGISTER,this); 

    } 

    }
    清单5.11 CRegisterDialog的部分代码 

    void CRegisterDialog::PostNcDestroy()  

    { 

    // TODO: Add your specialized code here and/or call the base class 

    delete this; //删除对话框对象 

    } 

    void CRegisterDialog::OnCancel()  

    { 

    // TODO: Add extra cleanup here 

    ((CRegisterView*)m_pParent)->m_pRegisterDlg=NULL; 

    DestroyWindow(); //删除对话框  

    } 

    CRegisterView::OnEditRegister函数判断登录数据对话框是否已打开,若是,就激活对话框,否则,就创建该对话框。该函数中主要调用了下列函数: 

    调用CWnd::SetActiveWindow激活对话框,该函数的声明为
    CWnd* SetActiveWindow( );
    该函数使本窗口成为活动窗口,并返回原来活动的窗口。 

    调用CDialog::Create来显示对话框,该函数的声明为
    BOOL Create( UINT nIDTemplate, CWnd* pParentWnd = NULL );
    参数nIDTemplate是对话框模板的ID。pParentWnd指定了对话框的父窗口或拥有者。 

    当用户在登录数据对话框中点击“取消”按钮后,CRegisterDialog::OnCancel将被调用,在该函数中调用CWnd::DestroyWindow来关闭对话框,并且将CRegisterView的成员m_pRegisterDlg置为NULL以表明对话框被关闭了。调用DestroyWindow导致了对CRegisterDialog::PostNcDestroy的调用,在该函数中用delete操作符删除了CRegisterDialog对象本身。 

    编译并运行Register,现在登录数据对话框已经变成一个非模态对话框了。 

    5.4.2 窗口对象的自动清除 

    一个MFC窗口对象包括两方面的内容:一是窗口对象封装的窗口,即存放在m_hWnd成员中的HWND(窗口句柄),二是窗口对象本身是一个C++对象。要删除一个MFC窗口对象,应该先删除窗口对象封装的窗口,然后删除窗口对象本身。 

    删除窗口最直接方法是调用CWnd::DestroyWindow或::DestroyWindow,前者封装了后者的功能。前者不仅会调用后者,而且会使成员m_hWnd保存的HWND无效(NULL)。如果DestroyWindow删除的是一个父窗口或拥有者窗口,则该函数会先自动删除所有的子窗口或被拥有者,然后再删除父窗口或拥有者。在一般情况下,在程序中不必直接调用DestroyWindow来删除窗口,因为MFC会自动调用DestroyWindow来删除窗口。例如,当用户退出应用程序时,会产生WM_CLOSE消息,该消息会导致MFC自动调用CWnd::DestroyWindow来删除主框架窗口,当用户在对话框内按了OK或Cancel按钮时,MFC会自动调用CWnd::DestroyWindow来删除对话框及其控件。 

    窗口对象本身的删除则根据对象创建方式的不同,分为两种情况。在MFC编程中,会使用大量的窗口对象,有些窗口对象以变量的形式嵌入在别的对象内或以局部变量的形式创建在堆栈上,有些则用new操作符创建在堆中。对于一个以变量形式创建的窗口对象,程序员不必关心它的删除问题,因为该对象的生命期总是有限的,若该对象是某个对象的成员变量,它会随着父对象的消失而消失,若该对象是一个局部变量,那么它会在函数返回时被清除。 

    对于一个在堆中动态创建的窗口对象,其生命期却是任意长的。初学者在学习C++编程时,对new操作符的使用往往不太踏实,因为用new在堆中创建对象,就不能忘记用delete删除对象。读者在学习MFC的例程时,可能会产生这样的疑问,为什么有些程序用new创建了一个窗口对象,却未显式的用delete来删除它呢?问题的答案就是有些MFC窗口对象具有自动清除的功能。 

    如前面讲述非模态对话框时所提到的,当调用CWnd::DestroyWindow或::DestroyWindow删除一个窗口时,被删除窗口的PostNcDestroy成员函数会被调用。缺省的PostNcDestroy什么也不干,但有些MFC窗口类会覆盖该函数并在新版本的PostNcDestroy中调用delete this来删除对象,从而具有了自动清除的功能。此类窗口对象通常是用new操作符创建在堆中的,但程序员不必操心用delete操作符去删除它们,因为一旦调用DestroyWindow删除窗口,对应的窗口对象也会紧接着被删除。 

    不具有自动清除功能的窗口类如下所示。这些窗口对象通常是以变量的形式创建的,无需自动清除功能。 

    所有标准的Windows控件类。 

    从CWnd类直接派生出来的子窗口对象(如用户定制的控件)。 

    切分窗口类CSplitterWnd。 

    缺省的控制条类(包括工具条、状态条和对话条)。 

    模态对话框类。 

    具有自动清除功能的窗口类如下所示,这些窗口对象通常是在堆中创建的。 

    主框架窗口类(直接或间接从CFrameWnd类派生)。 

    视图类(直接或间接从CView类派生)。 



    读者在设计自己的派生窗口类时,可根据窗口对象的创建方法来决定是否将窗口类设计成可以自动清除的。例如,对于一个非模态对话框来说,其对象是创建在堆中的,因此应该具有自动清除功能。 

    综上所述,对于MFC窗口类及其派生类来说,在程序中一般不必显式删除窗口对象。也就是说,既不必调用DestroyWindow来删除窗口对象封装的窗口,也不必显式地用delete操作符来删除窗口对象本身。只要保证非自动清除的窗口对象是以变量的形式创建的,自动清除的窗口对象是在堆中创建的,MFC的运行机制就可以保证窗口对象的彻底删除。 

    如果需要手工删除窗口对象,则应该先调用相应的函数(如CWnd::DestroyWindow)删除窗口,然后再删除窗口对象.对于以变量形式创建的窗口对象,窗口对象的删除是框架自动完成的.对于在堆中动态创建了的非自动清除的窗口对象,必须在窗口被删除后,显式地调用delete来删除对象(一般在拥有者或父窗口的析构函数中进行).对于具有自动清除功能的窗口对象,只需调用CWnd::DestroyWindow即可删除窗口和窗口对象。注意,对于在堆中创建的窗口对象,不要在窗口还未关闭的情况下就用delete操作符来删除窗口对象. 

    提示:在非模态对话框的OnCancel函数中可以不调用CWnd::DestroyWindow,取而代之的是调用CWnd::ShowWindow(SW_HIDE)来隐藏对话框.在下次打开对话框时就不必调用Create了,只需调用CWnd::ShowWindow(SW_SHOW)来显示对话框.这样做的好处在于对话框中的数据可以保存下来,供以后使用.由于拥有者窗口在被关闭时会调用DestroyWindow删除每一个所属窗口,故只要非模态对话框是自动清除的,程序员就不必担心对话框对象的删除问题.  

    5.5 标签式对话框

    在设计较为复杂的对话框时,常常会遇到这种情况:对某一事物的设置或选项需要用到大量的控件,以至于一个对话框放不下,而这些控件描述的是类似的属性,不能分开。用普通的对话框技术,这一问题很难解决。 

    MFC提供了对标签式对话框的支持,可以很好的解决上述问题。标签式对话框实际上是一个包含了多个子对话框的对话框,这些子对话框通常被称为页(Page)。每次只有一个页是可见的,在对话框的顶端有一行标签,用户通过单击这些标签可切换到不同的页。显然,标签式对话框可以容纳大量的控件。在象Word和Developer Studio这样复杂的软件中,用户会接触到较多的标签式对话框,一个典型的标签式对话框如图5.10所示。 



    图5.10 典型的标签式对话框 

    5.5.1 标签式对话框的创建 

    为了支持标签式对话框,MFC提供了CPropertySheet类和CPropertyPage类。前者代表对话框的框架,后者代表对话框中的某一页。CPropertyPage是CDialog类的派生类,而CPropertySheet是CWnd类的派生类。虽然CPropertySheet不是CDialog类的派生类,但使用CPropertySheet对象的方法与使用CDialog对象是类似的。标签式对话框是一种特殊的对话框,因此,和普通对话框相比,它的设计与实现既有许多相似之处,又有一些不同的特点。 

    创建一个标签式对话框一般包括以下几个步骤: 

    分别为各个页创建对话框模板,去掉缺省的OK和Cancel按钮。每页的模板最好具有相同的尺寸,如果尺寸不统一,则框架将根据最大的页来确定标签对话框的大小。在创建模板时,需要在模板属性对话框中指定下列属性: 

    指定标题(Caption)的内容。标题的内容将显示在该页对应的标签中。 

    选择TitleBar、Child、ThinBorder和Disable属性。 

    根据各个页的模板,用ClassWizard分别为每个页创建CPropertyPage类的派生类。这一过程与创建普通对话框类的过程类似,不同的是在创建新类对话框中应在Base class一栏中选择CPropertyPage而不是CDialog。 

    用ClassWizard为每页加入与控件对应的成员变量,这个过程与为普通对话框类加入成员变量类似。 

    程序员可直接使用CPropertySheet类,也可以从该类派生一个新类。除非要创建一个非模态对话框,或要在框架对话框中加入控件,否则没有必要派生一个新类。如果直接使用CPropertySheet类,则一个典型的标签式对话框的创建代码如清单5.12所示,该段代码也演示了标签式对话框与外界的数据交换。这些代码通常是放在显示对话框的命令处理函数中。可以看出,对话框框架的创建过程及对话框与外界的数据交换机制与普通对话框是一样的,不同之处是还需将页对象加入到CPropertySheet对象中。如果要创建的是模态对话框,应调用CPropertySheet::DoModal,如果想创建非模态对话框,则应该调用CPropertySheet::Create。 

    若从CPropertySheet类派生了一个新类,则应该将所有的页对象以成员变量的形式嵌入到派生类中,并在派生类的构造函数中调用CPropertySheet::AddPage函数来把各个页添加到对话框中。这样,在创建标签式对话框时就不用做添加页的工作了。 

    清单5.12 典型的标签式对话框创建代码 

    void CMyView::DoModalPropertySheet() 

    { 

    CPropertySheet propsheet; 

    CMyFirstPage pageFirst; // derived from CPropertyPage 

    CMySecondPage pageSecond; // derived from CPropertyPage 



    // Move member data from the view (or from the currently 

    // selected object in the view, for example). 

    pageFirst.m_nMember1 = m_nMember1;  

    pageFirst.m_nMember2 = m_nMember2; 



    pageSecond.m_strMember3 = m_strMember3; 

    pageSecond.m_strMember4 = m_strMember4; 



    propsheet.AddPage(&pageFirst); 

    propsheet.AddPage(&pageSecond); 



    if (propsheet.DoModal() == IDOK) 

    { 

    m_nMember1 = pageFirst.m_nMember1; 

    m_nMember2 = pageFirst.m_nMember2; 

    m_strMember3 = pageSecond.m_strMember3; 

    m_strMember4 = pageSecond.m_strMember4;  

    . . .  

    } 

    } 

    .5.2 标签式对话框的运行机制 

    标签式对话框的初始化包括框架对话框的初始化和页的初始化。页的初始化工作可在OnInitDialog函数中进行,而框架对话框的初始化应该在OnCreate函数中完成。 

    根据CPropertySheet::DoModal返回的是IDOK还是IDCANCEL,程序可判断出关闭对话框时按的是OK还是Cancel按钮,这与普通对话框是一样的。 

    如果标签式对话框是模态对话框,在其底部会有三个按钮,依次为OK、Cancel和Apply(应用)按钮,如果对话框是非模态的,则没有这些按钮。OK和Cancel按钮的意义与普通对话框没什么两样,Apply按钮则是标签对话框所特有的。普通的模态对话框只有在用户按下了OK按钮返回后,对话框的设置才能生效,而设计Apply按钮的意图是让用户能在不关闭对话框的情况下使对话框中的设置生效。由此可见,Apply的作用与前面例子中登录数据的“添加”按钮类似,用户不必退出对话框,就可以反复进行设置,这在某些应用场合下是很有用的。 

    为了对上述三个按钮作出响应,CPropertyPage类提供了OnOK、OnCancel和OnApply函数,用户可覆盖这三个函数以完成所需的工作。需要指出的是这三个函数并不是直接响应按钮的BN_CLICKED消息的,但在按钮按下后它们会被间接调用。这些函数的说明如下: 

    virtual void OnOK( );
    在按下OK或Apply按钮后,该函数将被调用。缺省的OnOK函数几乎什么也不干,象数据交换和关闭对话框这样的工作是在别的地方完成的,这与普通对话框的OnOK函数是不同的。 

    virtual void OnCancel( );
    在按下Cancel按钮后,该函数将被调用。缺省的OnCancel函数也是几乎什么都不干。 

    virtual BOOL OnApply( );
    在按下OK或Apply按钮后,该函数将被调用。缺省的OnApply会调用OnOK函数。函数的返回值如果是TRUE,则对话框中的设置将生效,否则无效。 

    按理说,CPropertySheet类也应该提供上述函数,特别是OnApply。但奇怪的是,MFC并未考虑CPropertySheet类的按钮响应问题。读者不要指望能通过ClassWizard来自动创建按钮的BN_CLICKED消息处理函数,如果需要用到这类函数,那么只好手工创建了。 

    下列几个CPropertyPage类的成员函数也与标签对话框的运行机制相关。 

    void SetModified( BOOL bChanged = TRUE );
    该函数用来设置修改标志。若参数bChanged为TRUE,则表明对话框中的设置已改动,否则说明设置未改动。该函数的一个主要用途是允许或禁止Apply按钮。在缺省情况下,Apply按钮是禁止的。只要一调用SetModified(TRUE),Apply按钮就被允许,而调用SetModified(FALSE)并不一定能使Apply按钮禁止,只有在所有被标为改动过的页都调用了SetModified(FALSE)后,Apply按钮才会被禁止。另外,该函数对OnApply的调用也有影响,当Apply按钮被按下后,只有那些被标为改动过的页的OnApply函数才会被调用。在调用该函数之前,程序需要判断页中的内容是否已被修改,可以通过处理诸如BN_CLICKED、EN_CHANG这样的控件通知消息来感知页的内容的改变。 

    virtual BOOL OnSetActive( );
    当页被激活或被创建时,都会调用该函数。该函数的缺省行为是若页还未创建,就创建之,若页已经创建,则将其激活,并调用UpdateData(FALSE)更新控件。用户可覆盖该函数完成一些刷新方面的工作。 

    virtual BOOL OnKillActive( );
    当原来可见的页被覆盖或被删除时,都会调用该函数。该函数的缺省行为是调用UpdateData(TRUE)更新数据。用户可覆盖该函数完成一些特殊数据的有效性检查工作。 

    需要说明的是,标签对话框中的所有页不一定都会被创建。实际上,那些从未打开过的页及其控件是不会被创建的。因此,在CPropertyPage类的派生类中,只有在确定了页已存在后,才能调用与对话框及控件相关的函数(如UpdateData)。如果收到控件通知消息,或OnSetActive函数被调用,则说明页已经存在。正是由于上述原因,使得标签式对话框的内部数据交换只能在OnSetActive和OnKillActive函数中进行。 

    5.5.3 标签式对话框的具体实例 

    通过上面的分析,读者对标签式对话框已经比较了解了。现在,让我们在前面做过的Register程序中加入一个标签式对话框来试验一下其功能。 

    在Register程序的登录数据对话框中有“个人情况”和“单位情况”两组控件,显然,我们可以创建一个标签式对话框并把两组控件分别放到两个页中。为了简单起见,我们仅要求输入姓名和单位名,简化后的标签式对话框如图5.11所示。 



    图5.11 简化后的标签式对话框 



    通过对标签式对话框的分析,读者已经知道CPropertySheet类未对Apply按钮的控件通知消息进行处理,这是一个不足之处。Register的新版本将向读者演示如何在CPropertySheet类的派生类中手工加入Apply按钮的BN_CLICKED消息处理函数。另外,新版本还演示了对话框与外部对象交流的一种较好办法,即通过发送用户定义消息来向外部对象传递信息。在登录数据对话框中,与外界交流的方法是在对话框内部直接访问派生的视图对象,这样做的优点是方便快捷,缺点则是对外界依赖较大,不利于移植。而用发送用户定义消息的方法则可以避免这个缺点。 

    具体工作请按下面几步进行: 

    在菜单资源中的Edit菜单的“登录数据...”项的后面插入一个名为“标签式对话框...”的菜单项,并指定其ID为ID_EDIT_PROPDLG。然后用ClassWizard,在CRegisterView类内为该菜单命令创建命令处理函数OnEditPropdlg,该函数将用来显示标签式对话框。 

    为标签式对话框的第一页创建对话框模板。去掉缺省的OK和Cancel按钮。注意应选择中文语种和宋体字体。在属性对话框中,指定对话框的ID为IDD_PERSONAL,标题为“个人情况”,在Styles页中,选中TitleBar项,并在Style栏中选择Child,在Border栏中选择ThinBorder。在More Styles页中,选中Disable。然后,在模板中加入控件,如图5.11和表5.6所示。 



    表5.6 

    控件类型  
    控件ID  
    控件标题  

    静态正文  
    缺省  
    姓名:  

    编辑框  
    IDC_NAME  
     





    用ClassWizard为模板IDD_PERSONAL创建CPropertyPage类的派生类,类名为CPersonalPage。在该类中为控件IDC_NAME加入对应的成员变量,变量名为m_strName,类型为CString。为控件IDC_NAME加入EN_CHANGE消息处理函数OnChangeName,当编辑框的内容被改变时,控件会向对话框发出EN_CHANGE消息。在OnChangeName中,应该使Apply按钮允许。 

    仿照步2,为标签式对话框的第二页创建对话框模板。指定其ID为IDD_UNIT,标题为“单位情况”。在模板中加入的控件如图5.11和表5.7所示。 



    表5.7 

    控件类型  
    控件ID  
    控件标题  

    静态正文  
    缺省  
    工作单位:  

    编辑框  
    IDC_UNIT  
     

    用ClassWizard为模板IDD_UNIT创建CPropertyPage类的派生类,类名为CUnitPage。在该类中为控件IDC_UNIT加入对应的成员变量,变量名为m_strUnit,类型为CString。为控件IDC_UNIT加入EN_CHANGE消息处理函数OnChangeUnit。 

    用ClassWizard创建一个CPropertySheet的派生类,类名为CRegisterSheet。 

    在CRegisterApp类的头文件的开头加入下面一行
    #define WM_USER_OUTPUT (WM_USER+200)
    WM_USER_OUTPUT不是标准的Windows消息,而是一个用户定义消息。在本例中,当标签式对话框的Apply按钮被按下后,程序会向编辑视图发送该消息,编辑视图对应的消息处理函数应该输出对话框的数据。用户定义消息的编码范围是WM_USER—0x7FFF。 

    请读者按清单5.13、5.14、5.15修改程序,限于篇幅,这里仅列出了需要修改的部分源代码。 



    清单5.13 CPersonalPage类和CUnitPage类的部分代码 

    void CPersonalPage::OnChangeName()  

    { 

    // TODO: Add your control notification handler code here 



    SetModified(TRUE); //使Apply按钮允许 

    UpdateData(TRUE); 

    } 



    void CUnitPage::OnChangeUnit()  

    { 

    // TODO: Add your control notification handler code here 



    SetModified(TRUE); //使Apply按钮允许 

    UpdateData(TRUE); 

    } 

    当页中的编辑框的内容被改变时,页会收到EN_CHANGE消息,这将导致OnChangeName或OnChangeUnit被调用。对该消息的处理是使Apply按钮允许并调用UpdateData(TRUE)更新数据。 

    清单5.14 CRegisterSheet类的部分代码 

    //文件RegisterSheet.h 

    class CRegisterSheet : public CPropertySheet 

    { 



    . . . . . . 

    // Construction 

    public: 

    CRegisterSheet(UINT nIDCaption, CWnd* pParentWnd = NULL, UINT iSelectPage = 0); 

    CRegisterSheet(LPCTSTR pszCaption, CWnd* pParentWnd = NULL, UINT iSelectPage = 0); 



    public: 

    CPersonalPage m_PersonalPage; 

    CUnitPage m_UnitPage; 

    . . . . . . 

    protected: 

    //{{AFX_MSG(CRegisterSheet) 

    // NOTE - the ClassWizard will add and remove member functions here. 

    //}}AFX_MSG 



    afx_msg void OnApplyNow(); 

    DECLARE_MESSAGE_MAP()  

    }; 





    //文件RegisterSheet.cpp 

    #include "stdafx.h" 

    #include "Register.h" 



    #include "PersonalPage.h" 

    #include "UnitPage.h" 

    #include "RegisterSheet.h" 



    CRegisterSheet::CRegisterSheet(LPCTSTR pszCaption, CWnd* pParentWnd, UINT iSelectPage) 

    :CPropertySheet(pszCaption, pParentWnd, iSelectPage) 

    { 



    AddPage(&m_PersonalPage); //向标签对话框中添加页 

    AddPage(&m_UnitPage); 

    } 



    BEGIN_MESSAGE_MAP(CRegisterSheet, CPropertySheet) 

    //{{AFX_MSG_MAP(CRegisterSheet) 

    // NOTE - the ClassWizard will add and remove mapping macros here. 

    //}}AFX_MSG_MAP 



    ON_BN_CLICKED(ID_APPLY_NOW, OnApplyNow) 

    END_MESSAGE_MAP() 





    void CRegisterSheet::OnApplyNow() 

    { 

    CFrameWnd* pFrameWnd = (CFrameWnd*) AfxGetMainWnd(); 

    //获取指向视图的指针 

    CView* pView = pFrameWnd->GetActiveFrame()->GetActiveView(); 



    //发送用户定义消息,在视图中输出信息 

    pView->SendMessage(WM_USER_OUTPUT, (WPARAM)this); 

    m_PersonalPage.SetModified(FALSE); 

    m_UnitPage.SetModified(FALSE); //使Apply按钮禁止 

    } 

    在CRegisterSheet类内嵌入了CPersonalPage和CUnitPage对象,在该类的构造函数中调用CPropertySheet::AddPage将两个页添加到对话框中。 

    标签式对话框的OK、Cancel和Apply按钮的ID分别是IDOK、IDCANCEL和ID_APPLY_NOW。在按下Apply按钮后,CRegisterSheet对象应该作出响应,由于ClassWizard不能为CRegisterSheet类提供Apply按钮的BN_CLICKED消息处理函数,故必须手工声明和定义消息处理函数OnApplyNow,并在消息映射表中手工加入ID_APPLY_NOW的BN_CLICKED消息映射,该映射是通过ON_BN_CLICKED宏实现的。 

    函数OnApplyNow用CWnd::SendMessage向视图发送用户定义消息WM_USER_OUTPUT,并调用CPropertyPage::SetModified(FALSE)来禁止Apply按钮。在发送消息时,将this指针作为wParam参数一并发送,这是因为视图对象需要指向CRegisterSheet对象的指针来访问该对象。该函数演示了如何在程序的任意地方获得当前活动视图的方法:首先,调用AfxGetMainWnd()返回程序主窗口的CWnd类指针,然后将该指针强制转换成CFrameWnd类型,接着调用CFrameWnd::GetActiveFrame返回当前活动的框架窗口的一个CFrameWnd型指针,最后调用CFrameWnd::GetActiveView返回当前活动视图的一个Cview型指针。 

    在函数OnApplyNow中主要调用了下列函数: 

    CWnd* AfxGetMainWnd( );
    该函数返回一个指向程序的主窗口CWnd指针。程序的主窗口可以是一个框架窗口,也可以是一个对话框。 

    virtual CFrameWnd* GetActiveFrame( );
    函数返回一个CFrameWnd型的指针。如果是MDI(多文档界面)程序,则该函数将返回当前活动的子框架窗口,如果是SDI(单文档界面)程序,该函数将返回主框架窗口本身。 

    CView* GetActiveView( ) const;
    返回一个指向当前活动视图的Cview型指针。 

    LRESULT SendMessage( UINT message, WPARAM wParam = 0, LPARAM lParam = 0 );
    用于向本窗口发送消息。SendMessage会直接调用发送消息的处理函数,直到发送消息被处理完后该函数才返回。参数message说明了要发送的消息,wParam和lParam则提供了消息的附加信息。 

    清单5.15 CRegisterView类的部分代码 

    //文件RegisterView.h 

    class CRegisterView : public CEditView 

    { 



    . . . . . . 

    // Generated message map functions 

    protected: 

    //{{AFX_MSG(CRegisterView) 

    afx_msg void OnEditRegister(); 

    afx_msg void OnEditPropdlg(); 

    //}}AFX_MSG 



    afx_msg LRESULT OnOutput(WPARAM wParam, LPARAM lParam); 

    DECLARE_MESSAGE_MAP() 

    }; 



    //文件RegisterView.cpp 

    #include "stdafx.h" 

    #include "Register.h" 



    #include "RegisterDoc.h" 

    #include "RegisterView.h" 

    #include "RegisterDialog.h" 



    #include "PersonalPage.h" 

    #include "UnitPage.h" 

    #include "RegisterSheet.h" 





    BEGIN_MESSAGE_MAP(CRegisterView, CEditView) 



    . . . . . . 

    ON_MESSAGE(WM_USER_OUTPUT, OnOutput) 

    END_MESSAGE_MAP() 



    void CRegisterView::OnEditPropdlg()  

    { 

    // TODO: Add your command handler code here 



    CRegisterSheet RegisterSheet("登录");  

    RegisterSheet.m_PersonalPage.m_strName="张颖峰"; 

    RegisterSheet.m_UnitPage.m_strUnit="南京邮电学院"; 



    if(RegisterSheet.DoModal()==IDOK) 

    OnOutput((WPARAM)&RegisterSheet,0); 

    } 





    //用户定义消息WM_USER_OUTPUT的处理函数 

    LRESULT CRegisterView::OnOutput(WPARAM wParam, LPARAM lParam) 

    { 

    CRegisterSheet *pSheet=(CRegisterSheet*)wParam; 

    CString str; 



    GetWindowText(str); 



    str+="/r/n"; 



    str+="姓名:"; 

    str+=pSheet->m_PersonalPage.m_strName; 

    str+="/r/n"; 



    str+="工作单位:"; 

    str+=pSheet->m_UnitPage.m_strUnit; 

    str+="/r/n"; 



    SetWindowText(str); 



    return 0; 

    } 

    OnEditPropdlg函数负责初始化和创建标签式对话框,这一过程与创建普通对话框差不多。如果用户是按OK按钮返回的,则调用OnOutput函数输出数据。 

    CRegisterView类的OnOutput函数负责处理标签对话框发来的用户定义消息WM_USER_OUTPUT。用户定义消息的处理函数只能用手工的方法加入。用户定义消息的消息映射是用ON_MESSAGE宏来完成的。 

    函数OnOutput的两个参数wParam和lParam分别对应消息的wParam和lParam值。该函数从wParam参数中获得指向CRegisterSheet对象的指针,然后将该对象中的数据输出到视图中。 



    编译并运行Register,试一试自己设计的标签式对话框。 

    5.6 公用对话框

    在使用Windows的过程中,用户经常会遇到一些常用的有特定用途的对话框。例如,当选择File->Open,会弹出一个文件选择的对话框,用户可以在其中选择想要打开的文件。象文件选择这样的对话框,使用的非常普遍,因此Windows系统本身提供了对该对话框的支持,用户不必自己设计文件选择对话框。与文件选择对话框类似的还有颜色选择、字体选择、打印和打印设置以及正文搜索和替换对话框。这五种对话框均由Windows支持,被称为公用对话框。 

    MFC提供了一些公用对话框类,它们均是CDialog类的派生类,封装了公用对话框的功能。表5.6列出了MFC的公用对话框类。 

    表5.6 公用对话框类 

    通用对话框类  
    用途  

    CColorDialog  
    选择颜色  

    CFileDialog  
    选择文件名,用于打开和保存文件  

    CFindReplaceDialog  
    正文查找和替换  

    CFontDialog  
    选择字体  

    CPrintDialog  
    打印和打印设置  


    通用对话框类使用方便,读者只需知道怎样创建对话框和访问对话框的数据,不必关心它们的内部细节。 

    5.6.1 CColorDialog类 

    CColorDialog类用于实现Color(颜色选择)公用对话框。Color对话框如图5.12所示,在Windows的画板程序中,如果用户在颜色面板的某种颜色上双击鼠标,就会显示一个Color对话框来让用户选择颜色。 



    图5.12 Color对话框 



    Color对话框的创建与一般的对话框没什么两样:首先是在堆栈上构建一个CColorDialog对象,然后调用CColorDialog::DoModal( )来启动对话框。CColorDialog的构造函数为 

    CColorDialog( COLORREF clrInit = 0, DWORD dwFlags = 0, CWnd* pParentWnd = NULL );  
     
    参数clrInit用来指定初始的颜色选择,dwFlags用来设置对话框,pParentWnd用于指定对话框的父窗口或拥有者窗口。 

    根据DoModal返回的是IDOK还是IDCANCEL可知道用户是否确认了对颜色的选择。DoModal返回后,调用CColorDialog::GetColor()可以返回一个COLORREF类型的结果来指示在对话框中选择的颜色。COLORREF是一个32位的值,用来说明一个RGB颜色。GetColor返回的COLORREF的格式是0x00bbggrr,即低位三个字节分别包含了蓝、绿、红三种颜色的强度。 

    读者将在后面的章节中看到颜色选择对话框的例子。 





    5.6.2 CFileDialog类 

    CFileDialog类用于实现文件选择对话框,以支持文件的打开和保存操作。用户要打开或保存文件,就会和文件选择对话框打交道,图5.13显示了一个标准的用于打开文件的文件选择对话框。用MFC AppWizard建立的应用程序中自动加入了文件选择对话框,在File菜单选Open或Save As命令会启动它们。 



    图5.13 文件选择对话框 

    文件选择对话框的创建过程与一般对话框的类似,首先是在堆栈上构建一个CFileDialog对象,然后调用CFileDialog::DoModal( )来启动对话框。文件对话框的构造函数为 

     
    CFileDialog( BOOL bOpenFileDialog, LPCTSTR lpszDefExt = NULL, LPCTSTR lpszFileName = NULL, DWORD dwFlags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, LPCTSTR lpszFilter = NULL, CWnd* pParentWnd = NULL ); 

    如果参数bOpenFileDialog的值为TRUE,将创建Open(打开文件)对话框,否则就创建Save As(保存文件)对话框。参数lpszDefExt用来指定缺省的文件扩展名。lpszFileName用于规定初始文件名。dwFlags用于设置对话框的一些属性。lpszFilter指向一个过滤字符串,用户如果只想选择某种或某几种类型的文件,就需要指定过滤字符串。参数pParentWnd是指向父窗口或拥有者窗口的指针。  
    过滤字符串有特定的格式,它实际上是由多个子串组成,每个子串由两部分组成,第一部分是过滤器的字面说明,如“Text file (*.txt)”,第二部分是用于过滤的匹配字符串,如“*.txt”,子串的两部分用竖线字符“ | ”分隔开。各子串之间也要用“ | ”分隔,且整个串的最后两个字符必须是两个连续的竖线字符“ || ”。一个典型的过滤字符串如下面所示: 

    char szFilter[]= 

    “All files (*.*)|*.*|Text files(*.txt)|*.txt|Word documents(*.doc)|*.doc||”; 

    若CFileDialog::DoModal返回的是IDOK,那么可以用表5.7列出的CFileDialog类的成员函数来获取与所选文件有关的信息。 

    表5.7 CFileDialog类辅助成员函数 

    函数名  
    用途  

    GetPathName  
    返回一个包含有全路径文件名的CString对象。  

    GetFileName  
    返回一个包含有文件名(不含路径)的CString对象。  

    GetFileExt  
    返回一个只含文件扩展名的CString对象。  

    GetFileTitle  
    返回一个只含文件名(不含扩展名)的CString对象。  


    5.6.3 CFindReplaceDialog类 

    CFindReplaceDialog类用于实现Find(搜索)和Replace(替换)对话框,这两个对话框都是非模态对话框,用于在正文中搜索和替换指定的字符串。图5.14显示了一个Find对话框,图5.15显示了一个Replace对话框。 



    图5.14 Find对话框 



    图5.15 Replace对话框 

    由于Find和Replace对话框是非模式对话框,它们的创建方式与其它四类公用对话框不同。CFindReplaceDialog对象是用new操作符在堆中创建的,而不是象普通对话框那样以变量的形式创建。要启动Find/Replace对话框,应该调用CFindReplaceDialog::Create函数,而不是DoModal。Create函数的声明是 

    BOOL Create( BOOL bFindDialogOnly, LPCTSTR lpszFindWhat, LPCTSTR lpszReplaceWith = NULL, DWORD dwFlags = FR_DOWN, CWnd* pParentWnd = NULL ); 

    当参数bFindDialogOnly的值为TRUE时,创建的是Find对话框,为FALSE时创建的是Replace对话框。参数lpszFindWhat指定了要搜索的字符串,lpszReplaceWith指定了用于替换的字符串。dwFlags用来设置对话框,其缺省值是FR_DOWN(向下搜索),该参数可以是几个FR_XXX常量的组合,用户可以通过该参数来决定诸如是否要显示Match case、Match Whole Word检查框等设置。参数pParentWnd指明了对话框的父窗口或拥有者窗口。  
    Find/Replace对话框与其它公用对话框的另一个不同之处在于它在工作过程中可以重复同一操作而对话框不被关闭,这就方便了频繁的搜索和替换。CFindReplaceDialog类只提供了一个界面,它并不会自动实现搜索和替换功能。CFindReplaceDialog使用了一种特殊的通知机制,当用户按下了操作的按钮后,它会向父窗口发送一个通知消息,父窗口应在该消息的消息处理函数中实现搜索和替换。 

    CFindReplaceDialog类提供了一组成员函数用来获得与用户操作有关的信息,如表5.8所示,这组函数一般应在通知消息处理函数中调用。 

    表5.8 CFindReplaceDialog类的辅助成员函数 

    函数名  
    用途  

    FindNext  
    如果用户点击了Findnext按钮,该函数返回TRUE。  

    GetNotifier  
    返回一个指向当前CFindReplaceDialog对话框的指针。  

    GetFindString  
    返回一个包含要搜索字符串的CString对象。  

    GetReplaceString  
    返回一个包含替换字符串的CString对象。  

    IsTerminating  
    如果对话框终止了,则返回TRUE。  

    MatchCase  
    如果选择了对话框中的Match case检查框,则返回TRUE。  

    MatchWholeWord  
    如果选择了对话框中的Match Whole Word检查框,则返回TRUE。  

    ReplaceAll  
    如果用户点击了Replace All按钮,该函数返回TRUE。  

    ReplaceCurrent  
    如果用户点击了Replace按钮,该函数返回TRUE。  

    SearchDown  
    返回TRUE表明搜索方向向下,返回FALSE则向上。  


    CEditView类自动实现了Find和Replace对话框的功能,但MFC AppWizard并未提供相应的菜单命令。读者可以在前面的Register工程的Edit菜单中加入&Find...和&Replace...两项,并令其ID分别为ID_EDIT_FIND和ID_EDIT_REPLACE,则Find/Replace对话框的功能就可以实现。 

    5.6.4 CFontDialog类 

    CFontDialog类支持Font(字体)对话框,用来让用户选择字体。图5.16显示了一个Font对话框。Font对话框的创建过程与Color对话框的类似,首先是在堆栈上构建一个CFontDialog对象,然后调用CFontDialog::DoModal来启动对话框。 



    图5.16 Font对话框 

    CFontDialog类的构造函数如下所示 

    CFontDialog( LPLOGFONT lplfInitial = NULL, DWORD dwFlags = CF_EFFECTS | CF_SCREENFONTS, CDC* pdcPrinter = NULL, CWnd* pParentWnd = NULL );     
    参数lplfInitial指向一个LOGFONG结构,用来初始化对话框中的字体设置。dwFlags用于设置对话框。pdcPrinter指向一个代表打印机的CDC对象,若设置该参数,则选择的字体就为打印机所用。pParentWnd用于指定对话框的父窗口或拥有者窗口。 

    若DoModal返回IDOK,那么可以调用CFontDialog的成员函数来获得所选字体的信息,这些函数在表5.9列出。 

    表5.9 CFontDialog类的辅助成员函数 

    函数名  
    用途  

    GetCurrentFont  
    用来获得所选字体的属性。该函数有一个参数,该参数是指向LOGFONT结构的指针,函数将所选字体的各种属性写入这个LOGFONT结构中。  

    GetFaceName  
    返回一个包含所选字体名字的CString对象。  

    GetStyleName  
    返回一个包含所选字体风格名字的CString对象。  

    GetSize  
    返回所选字体的尺寸(以10个象素为单位)。  

    GetColor  
    返回一个含有所选字体的颜色的COLORREF型值。  

    GetWeight  
    返回所选字体的权值。  

    IsStrikeOut  
    若用户选择了空心效果则返回TRUE,否则返回FALSE。  

    IsUnderline  
    若用户选择了下划线效果则返回TRUE,否则返回FALSE。  

    IsBold  
    若用户选择了黑体风格则返回TRUE,否则返回FALSE。  

    IsItalic  
    若用户选择了斜体风格则返回TRUE,否则返回FALSE。  


    .6.5 CPrintDialog类 

    CPrintDialog类支持Print(打印)和Print Setup(打印设置)对话框,通过这两个对话框用户可以进行与打印有关的操作。图5.17显示了一个Print对话框,图5.18显示了一个Print Setup对话框。 



    图5.17 Print对话框 



    图5.18 Print Setup对话框 

    Print和Print Setup对话框的创建过程与Color对话框类似。该类的构造函数是 

    CPrintDialog( BOOL bPrintSetupOnly, DWORD dwFlags = PD_ALLPAGES | PD_USEDEVMODECOPIES | PD_NOPAGENUMS | PD_HIDEPRINTTOFILE | PD_NOSELECTION, CWnd* pParentWnd = NULL );     
    参数bPrintSetupOnly的值若为TRUE,则创建的是Print对话框,否则,创建的是Print Setup对话框。dwFlags用来设置对话框,缺省设置是打印出全部页,禁止From和To编辑框(即不用确定要打印的页的范围),PD_USEDEVMODECOPIES使对话框判断打印设备是否支持多份拷贝和校对打印(Collate),若不支持,就禁止相应的编辑控件和Collate检查框。pParentWnd用来指定对话框的父窗口或拥有者窗口。 

    程序可以调用如表5.10所示的CPrintDialog的成员函数来获得打印参数。 

    表5.10 CPrintDialog的辅助成员函数 

    函数名  
    用途  

    GetCopies  
    返回要求的拷贝数。  

    GetDefaults  
    在不打开对话框的情况下返回缺省打印机的缺省设置,返回的设置放在m_pd数据成员中。  

    GetDeviceName  
    返回一个包含有打印机设备名的CString对象。  

    GetDevMode  
    返回一个指向DEVMODE结构的指针,用来查询打印机的设备初始化信息和设备环境信息。  

    GetDriverName  
    返回一个包含有打印机驱动程序名的CString对象。  

    GetFromPage  
    返回打印范围的起始页码。  

    GetToPage  
    返回打印范围的结束页码。  

    GetPortName  
    返回一个包含有打印机端口名的CString对象。  

    GetPrinterDC  
    返回所选打印设备的一个 HDC 句柄。  

    PrintAll  
    若要打印文档的所有页则返回TRUE。  

    PrintCollate  
    若用户选择了Collate Copies检查框(需要校对打印拷贝)则返回TRUE。  

    PrintRange  
    如果用户要打印文档的一部分页,则返回TRUE。  

    PrintSelection  
    若用户想打印当前选择的部分文档,则返回TRUE。  


    用缺省配置的MFC AppWizard建立的程序支持Print和Print Setup对话框,用户可以在File菜单中启动它们。 

    5.6.6 公用对话框的使用实例 

    现在,让我们来测试一下公用对话框的使用。请读者用AppWizard创建一个单文档的MFC应用程序,名为CommonDlg。注意别忘了在AppWizard的第一步中选Single document。 

    CommonDlg程序要对所有的公用对话框进行了测试。为此,首先要提供用户命令接口。请读者在CommonDlg的菜单资源中插入一个名为&Common的新菜单,这个菜单插在Help菜单之前。然后,在Common菜单中,请按表5.11创建菜单项。 

    表5.11 Common菜单的菜单项 

    Caption  
    ID  

    &Color...  
    ID_COMMON_COLOR  

    &Open file...  
    ID_COMMON_OPENFILE  

    &Save file...  
    ID_COMMON_SAVEFILE  

    &Font...  
    ID_COMMON_FONT  

    &Print...  
    ID_COMMON_PRINT  

    P&rint setup...  
    ID_COMMON_PRINTSETUP  

    F&ind...  
    ID_COMMON_FIND  

    &Replace...  
    ID_COMMON_REPLACE  


    接下来的工作是编写测试程序的源代码。首先,利用ClassWizard为表5.11的菜单项创建消息处理函数,注意这些处理函数都是CCommonDlgView的成员。接着,请按清单5.10和5.11修改程序。限于篇幅,这里仅列出与测试相关的部分源代码。 

    清单5.10 头文件CommonDlgView.h 

    class CCommonDlgView : public CView 

    { 



    . . . . . . 

    #ifdef _DEBUG 

    virtual void AssertValid() const; 

    virtual void Dump(CDumpContext& dc) const; 

    #endif 



    protected: 

    void DispPrintInfo(CPrintDialog& dlg); 

    protected: 

    CFont m_Font; //正文的字体 

    COLORREF m_ForeColor; //正文的前景色 

    COLORREF m_BackColor; //正文的背景色 



    CFindReplaceDialog *m_pFindReplaceDlg; 

    BOOL m_bFindOnly; 

    // Generated message map functions 

    protected: 



    //Find和Replace对话框通知消息处理函数 

    afx_msg LRESULT OnFindReplaceCmd(WPARAM, LPARAM lParam); 

    //{{AFX_MSG(CCommonDlgView) 

    afx_msg void OnCommonColor(); 

    afx_msg void OnCommonFont(); 

    afx_msg void OnCommonOpenfile(); 

    afx_msg void OnCommonSavefile(); 

    afx_msg void OnCommonPrint(); 

    afx_msg void OnCommonPrintsetup(); 

    afx_msg void OnCommonFind(); 

    afx_msg void OnCommonReplace(); 

    //}}AFX_MSG 

    DECLARE_MESSAGE_MAP() 

    }; 



    . . . . . . 





    清单5.11 文件CCommonDlgView.cpp 

    #include "stdafx.h" 

    #include "CommonDlg.h" 



    #include "CommonDlgDoc.h" 

    #include "CommonDlgView.h" 



    #ifdef _DEBUG 

    #define new DEBUG_NEW 

    #undef THIS_FILE 

    static char THIS_FILE[] = __FILE__; 

    #endif 





    IMPLEMENT_DYNCREATE(CCommonDlgView, CView) 





    //获取对本进程唯一的消息编号 

    static const UINT nMsgFindReplace = ::RegisterWindowMessage(FINDMSGSTRING); 



    BEGIN_MESSAGE_MAP(CCommonDlgView, CView) 

    //{{AFX_MSG_MAP(CCommonDlgView) 

    ON_COMMAND(ID_COMMON_COLOR, OnCommonColor) 

    ON_COMMAND(ID_COMMON_FONT, OnCommonFont) 

    ON_COMMAND(ID_COMMON_OPENFILE, OnCommonOpenfile) 

    ON_COMMAND(ID_COMMON_SAVEFILE, OnCommonSavefile) 

    ON_COMMAND(ID_COMMON_PRINT, OnCommonPrint) 

    ON_COMMAND(ID_COMMON_PRINTSETUP, OnCommonPrintsetup) 

    ON_COMMAND(ID_COMMON_FIND, OnCommonFind) 

    ON_COMMAND(ID_COMMON_REPLACE, OnCommonReplace) 

    //}}AFX_MSG_MAP 

    // Standard printing commands 

    ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint) 

    ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint) 

    ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview) 



    ON_REGISTERED_MESSAGE(nMsgFindReplace, OnFindReplaceCmd) 

    END_MESSAGE_MAP() 



    CCommonDlgView::CCommonDlgView() 

    { 

    // TODO: add construction code here 



    //缺省前景色为黑色,背景色为白色,字体为系统字体 

    m_ForeColor=0; 

    m_BackColor=0xFFFFFF; 

    m_Font.CreateStockObject(SYSTEM_FONT); 



    m_pFindReplaceDlg=NULL;  



    } 



    void CCommonDlgView::OnDraw(CDC* pDC) 

    { 

    CCommonDlgDoc* pDoc = GetDocument(); 

    ASSERT_VALID(pDoc); 



    // TODO: add draw code for native data here 



    int x,y; 

    CFont *pOldFont; 

    TEXTMETRIC TM; 

    int textHeight; 



    //设置正文的字体 

    pOldFont=pDC->SelectObject(&m_Font); 

    //设置正文的前景色和背景色 

    pDC->SetTextColor(m_ForeColor); 

    pDC->SetBkColor(m_BackColor); 



    //计算每行正文的高度 

    pDC->GetTextMetrics(&TM); 

    textHeight=TM.tmHeight+TM.tmExternalLeading; 



    //输出正文 

    x=5;y=5; 

    pDC->TextOut(x,y,"ABCDEFG"); 

    y+=textHeight; 

    pDC->TextOut(x,y,"abcdefg"); 



    //恢复原来的字体 

    pDC->SelectObject(pOldFont); 



    } 



    void CCommonDlgView::OnCommonColor()  

    { 

    // TODO: Add your command handler code here 



    CColorDialog dlg; 



    if(dlg.DoModal()==IDOK) 

    { 

    m_BackColor=dlg.GetColor(); 



    //重绘视图 

    Invalidate(); 

    UpdateWindow(); 

    } 

    } 



    void CCommonDlgView::OnCommonFont()  

    { 

    // TODO: Add your command handler code here 



    CFontDialog dlg; 



    if(dlg.DoModal()==IDOK) 

    { 

    LOGFONT LF; 



    //获取所选字体的信息 

    dlg.GetCurrentFont(&LF); 

    m_ForeColor=dlg.GetColor(); 

    //建立新的字体 

    m_Font.DeleteObject(); 

    m_Font.CreateFontIndirect(&LF); 



    Invalidate(); 

    UpdateWindow(); 

    } 

    } 



    void CCommonDlgView::OnCommonOpenfile()  

    { 

    // TODO: Add your command handler code here 



    //过滤字符串 

    char szFileFilter[]= 

    "Cpp files(*.cpp)|*.cpp|" 

    "Header files(*.h)|*.h|" 

    "All files(*.*)|*.*||"; 



    CFileDialog dlg(TRUE, //Open对话框 

    "cpp", //缺省扩展名 

    "*.cpp", 

    OFN_HIDEREADONLY|OFN_FILEMUSTEXIST, //文件必须存在 

    szFileFilter, 

    this); 



    if(dlg.DoModal()==IDOK) 

    { 

    CString str="The full path name is:"; 

    str+=dlg.GetPathName(); 

    AfxMessageBox(str); 

    } 



    } 



    void CCommonDlgView::OnCommonSavefile()  

    { 

    // TODO: Add your command handler code here 



    char szFileFilter[]= 

    "Cpp files(*.cpp)|*.cpp|" 

    "Header files(*.h)|*.h|" 

    "All files(*.*)|*.*||"; 



    CFileDialog dlg(FALSE, //Save对话框 

    "cpp", 

    "*.cpp", 

    OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT, 

    szFileFilter, 

    this); 



    if(dlg.DoModal()==IDOK) 

    { 

    CString str="The file name is:"; 

    str+=dlg.GetFileName(); 

    AfxMessageBox(str); 

    } 



    } 





    void CCommonDlgView::OnCommonPrint()  

    { 

    // TODO: Add your command handler code here 



    CPrintDialog dlg(FALSE, PD_ALLPAGES); //Print对话框 

    //设置Print对话框的属性 

    dlg.m_pd.nCopies=2; 

    dlg.m_pd.nMinPage=1; 

    dlg.m_pd.nMaxPage=50; 

    dlg.m_pd.nFromPage=1; 

    dlg.m_pd.nToPage=50; 



    if(dlg.DoModal()==IDOK) 

    DispPrintInfo(dlg); 

    } 



    void CCommonDlgView::OnCommonPrintsetup()  

    { 

    // TODO: Add your command handler code here 



    CPrintDialog dlg(TRUE) //Print Setup对话框 

    if(dlg.DoModal()==IDOK) 

    DispPrintInfo(dlg); 

    } 





    void CCommonDlgView::DispPrintInfo(CPrintDialog& dlg) 

    { 

    CString str; 

    CString temp; 



    str+="Driver name:"; 

    str+=dlg.GetDriverName(); 

    str+="/nDevice name:"; 

    str+=dlg.GetDeviceName(); 

    str+="/nPort name:"; 

    str+=dlg.GetPortName(); 

    str+="/nNumber of copies:"; 

    temp.Format("%d",dlg.GetCopies()); 

    str+=temp; 

    str+="/nCollate:"; 

    str+=dlg.PrintCollate()?"Yes":"No"; 

    str+="/nPrint all:"; 

    str+=dlg.PrintAll()?"Yes":"No"; 

    str+="/nPrint range:"; 

    str+=dlg.PrintRange()?"Yes":"No"; 

    str+="/nSelection:"; 

    str+=dlg.PrintSelection()?"Yes":"No"; 

    str+="/nFrom page:"; 

    temp.Format("%d",dlg.GetFromPage()); 

    str+=temp; 

    str+="/nTo page:"; 

    temp.Format("%d",dlg.GetToPage()); 

    str+=temp; 



    AfxMessageBox(str); 



    } 



    void CCommonDlgView::OnCommonFind()  

    { 

    // TODO: Add your command handler code here 



    //判断是否已存在一个对话框 

    if(m_pFindReplaceDlg) 

    { 

    if(m_bFindOnly) 

    { 

    //若Find对话框已打开,则使之成为活动窗口 

    m_pFindReplaceDlg->SetActiveWindow(); 

    return; 

    } 

    else 

    //关闭Replace对话框 

    m_pFindReplaceDlg->SendMessage(WM_CLOSE); 

    } 

    m_bFindOnly=TRUE; 

    //创建Find对话框 

    m_pFindReplaceDlg=new CFindReplaceDialog; 

    m_pFindReplaceDlg->Create(TRUE,NULL,NULL,FR_DOWN,this); 

    } 



    void CCommonDlgView::OnCommonReplace()  

    { 

    // TODO: Add your command handler code here 



    //判断是否已存在一个对话框 

    if(m_pFindReplaceDlg) 

    { 

    if(!m_bFindOnly) 

    { 

    //若Replace对话框已打开,则使之成为活动窗口 

    m_pFindReplaceDlg->SetActiveWindow(); 

    return; 

    } 

    else 

    //关闭Find对话框 

    m_pFindReplaceDlg->SendMessage(WM_CLOSE); 

    } 

    m_bFindOnly=FALSE; 

    //创建Replace对话框 

    m_pFindReplaceDlg=new CFindReplaceDialog; 

    m_pFindReplaceDlg->Create(FALSE,NULL,NULL,FR_DOWN,this); 



    } 





    //Find和Replace对话框通知消息处理函数 

    LRESULT CCommonDlgView::OnFindReplaceCmd(WPARAM, LPARAM lParam) 

    { 

    //判断对话框是否被关闭 

    if(m_pFindReplaceDlg->IsTerminating()) 

    m_pFindReplaceDlg=NULL; 



    return 0; 

    }  

    让我们先来看看对Color对话框的测试。在CCommonDlgView::OnCommonColor中创建了一个Color对话框,在此处该对话框的用途是为视图中显示的正文指定背景色。在CCommonDlgView的构造函数中将背景色m_BackColor的初值设置为白色(0x00000000)。若DoModal返回IDOK,则调用CColorDialog::GetColor获取用户选择的颜色并将之保存在m_BackColor成员中。然后,调用Invalidate和UpdateWindow函数以重绘视图。这两个函数的说明如下:   

    void Invalidate( BOOL bErase = TRUE );
    该函数的作用是使整个窗口客户区无效。窗口的客户区无效意味着需要重绘,例如,如果一个被其它窗口遮住的窗口变成了前台窗口,那么原来被遮住的部分就是无效的,需要重绘。这时Windows会在应用程序的消息队列中放置WM_PAINT消息。MFC为窗口类提供了WM_PAINT的消息处理函数OnPaint,OnPaint负责重绘窗口。视图类有一些例外,在视图类的OnPaint函数中调用了OnDraw函数,实际的重绘工作由OnDraw来完成。参数bErase为TRUE时,重绘区域内的背景将被擦除,否则,背景将保持不变。 

    void UpdateWindow( );
    该函数的作用是使窗口立即重绘。调用Invalidate等函数后窗口不会立即重绘,这是由于WM_PAINT消息的优先级很低,它需要等消息队列中的其它消息发送完后才能被处理。调用UpdateWindow函数可使WM_PAINT被直接发送到目标窗口,从而导致窗口立即重绘。 

    在CCommonView::OnDraw函数中调用了CDC::SetBkColor来设置背景色。CDC类用于绘图,在后面的几章里将会对其作详细介绍。CDC::TextOut函数用于输出正文。两个函数的说明如下: 

     
    virtual COLORREF SetBkColor( COLORREF crColor );
    用于设置背景色。参数crColor指定了背景色的RGB值。返回的是原来的背景色。 

    BOOL TextOut( int x, int y, const CString& str );
    在指定的位置输出正文。参数x和y指定了输出起点的横向和纵向坐标。str参数是输出的字符串。若该函数调用成功则返回TRUE。 

    对文件选择对话框的测试比较简单。在CCommonDlgView::OnCommonOpenfile和CCommonDlgView:OnCommonSavefile函数中,分别创建了一个Open对话框和一个Save对话框。在创建Open对话框时,在CFileDialog的构造函数中规定了OFN_FILEMUSTEXIST属性,这样当用户试图打开一个不存在的文件时,对话框会发出错误信息并让用户从新选择文件。在创建Save对话框时,在CFileDialog的构造函数中规定了OFN_OVERWRITEPROMPT属性,这样,当用户试图覆盖一个已存在的文件时,对话框会询问用户是否真的要覆盖该文件。  
    若用户确认了对文件的选择,那么在文件选择对话框关闭后,程序会将所选文件的文件名或全路径文件名输出到屏幕上。 

    Find和Replace对话框的创建工作分别由CCommonDlgView::OnCommonFind和CCommonDlgView::OnCommonReplace完成。 

    在OnCommonFind函数中,首先判断是否已经打开了一个Find/Replace对话框。这个判断是完全必要的,因为Find/Replace对话框是非模态对话框,打开一个对话框后,用户有可能通过菜单再次执行Find或Replace命令。成员m_pFindReplaceDlg是指向CFindReplaceDialog对象的指针,若该指针不为空,则说明对话框已打开。接着,根据成员m_bFindOnly来判断原先打开的是否是Find对话框,如果原先打开的是一个Find对话框,则此时不必创建新的对话框,只需激活已打开的Find对话框就行了;如果原先打开的是一个Replace对话框,则应该先关闭该对话框,然后再创建Find对话框。然后,给m_bFindOnly赋TRUE值,以表明现在打开的是一个Find对话框。最后,创建一个非模态的Find对话框,请注意其过程与创建模态对话框的不同之处: 

    对话框对象是用new操作符在堆上创建的,而不是以变量的形式创建。 

    对话框的启动是靠调用Create函数实现的,而不是DoModal函数。 

    调用CWnd::SetActiveWindow以激活窗口。调用CWnd::SendMessage(WM_CLOSE)来关闭窗口,这是因为WM_CLOSE消息会导致CWnd::DestroyWindow函数的调用。 

    OnCommonReplace函数的过程与OnCommonFind函数类似。在该函数中,对m_bFindOnly赋值FALSE以表明打开的是Replace对话框。 

    Find/Replace对话框通知消息的处理函数是CCommonDlgView::OnFindReplaceCmd,这个消息处理函数及消息映射均是手工加入的。请注意在CommonDlgView.cpp文件的开头部分定义了一个静态全局变量nMsgFindReplace 

    static const UINT nMsgFindReplace = :: RegisterWindowMessage( FINDMSGSTRING ); 

    nMsgFindReplace变量用于存放消息码,这个消息是由函数RegisterWindowMessage提供的,该函数的声明为 

    UINT RegisterWindowMessage(LPCTSTR lpString); 

    参数lpString是一个消息字符串。调用RegisterWindowMessage函数会返回一个Windows注册消息,注册消息的编码在系统中是唯一的。当有多个应用程序需要处理同一个消息时,应调用该函数注册消息。如果消息是本应用程序专有的,则不必注册。如果两个应用程序使用相同的字符串注册消息,则会返回相同的消息,这样,通过该消息,两个应用程序可以进行通信。 

    注册消息的消息映射宏是ON_REGISTERED_MESSAGE,在CommonDlgView的消息映射中可以找到它。 

    在函数OnFindReplaceCmd中应该进行实际的搜索和替换工作,但在本例中该函数什么工作也不作。该函数只是判断一下对话框是否被关闭,若是,则给m_pFindReplaceDlg赋NULL值,以表明对话框已不存在了。 

    Font对话框的创建由函数CCommonView:: OnCommonFont完成。该函数收集了用户选择的字体的信息,并利用这些信息创建新的字体。成员m_ForeColor用来保存所选字体的颜色,成员m_Font是一个CFont对象,用来保存用户选择的字体。在CCommonView的构造函数中,m_ForeColor被初始化成黑色(0xFFFFFF),m_Font被初始化为系统字体。系统字体的获得是通过调用CGdiObject::CreateStockObject(SYSTEM_FONT)实现的,该函数用于获得系统库存的绘图对象,包括字体、画笔、刷子、调色板等。 

    在OnCommonFont函数中,主要调用了下列函数: 

    调用CFontDialog:: GetCurrentFont以获得用户选择字体的信息,该函数的声明为
    void GetCurrentFont( LPLOGFONT lplf );
    参数lplf是一个指向LOGFONT结构的指针,LOGFONT结构用来存放与字体有关的信息。  
    调用CFontDialog::GetColor来获得所选字体的颜色(前景色)。 

    调用CGdiObject:: DeleteObject()来删除存放在CFont对象m_Font中的老字体。 

    调用CFont::CreateFontIndirect以创建一种字体,该函数的声明是
    BOOL CreateFontIndirect(const LOGFONT* lpLogFont );
    参数lpLogFont是一个指向LOGFONT结构的指针,函数根据该结构提供的信息来初始化Cfont对象。 

    调用CWnd::Invalidate和CWnd::UpdateWindow重绘视图。 

    在CCommonView::OnDraw函数中,利用选择的字体和颜色输出两行正文。当视图需要重绘时,OnDraw就会被调用。在OnDraw函数中主要调用了下列函数:   
    在输出正文前,调用CDC::SelectObject指定输出正文的字体,输出完成后,调用CDC::SelectObject恢复被替换的字体。SelectObject有五个版本,用于为绘图指定画笔、刷子、字体、位图等绘图对象。在用该函数指定绘图对象时,应该把被替换的对象保存起来,在绘图完成后,需要再次调用该函数恢复被替换的绘图对象。如果不进行恢复,则可能会使设备对象CDC中含有非法的句柄。指定字体的SelectObject函数的声明是
    virtual CFont* SelectObject( CFont* pFont );
    参数pFont是指向CFont对象的指针。函数返回一个CFont对象的指针,指向被替代的字体。 

    调用CDC::SetTextColor来指定正文显示的前景色,该函数的声明为
    virtual COLORREF SetTextColor( COLORREF crColor );
    参数crColor指定了RGB颜色。函数返回的是原来的正文颜色。 

    调用CDC:: GetTextMetrics函数获得与绘图字体有关的各种信息,该函数的声明为 BOOL GetTextMetrics( LPTEXTMETRIC lpMetrics ) const; 参数lpMetrics是一个指向TEXTMETRIC结构的指针,该结构包含有字体的信息,其中tmHeight成员说明了字体的高度,tmExternalLeading成员说明了行与行之间的空白应该是多少。把这两个值相加就得到了每行正文的高度。 

    调用CDC::TextOut在指定位置输出正文。 

    在CCommonDlgView::OnCommonPrint和CCommonDlgView::OnCommonPrintsetup()函数中,分别创建了一个Print对话框和Print Setup对话框。在创建Print对话框时,通过CPrintDialog对象的m_pd成员,对对话框进行了一些初始化,这包括对拷贝份数、打印范围等的设置。在两个对话框DoModal返回IDOK后,均调用CCommonDlgView::DispPrintInfo报告打印信息。DispPrintInfo函数的代码较简单,这里就不作解释了。   

    小 结

    本课的要点为: 

    对话框的设计包括对话框模板的设计和对话框类的设计。对话框模板的设计是通过模板编辑器来完成的。对话框类的设计可借助ClassWizard来完成,这包括创建CDialog类的派生类,为对话框类增加与控件对应的成员变量,增加控件通知消息的处理函数等。 

    对话框的数据成员的初始化工作一般在其构造函数中完成,而对话框和控件的初始化是在OnInitDialog函数中完成的。 

    模态对话框拥有自己的消息循环,它垄断了用户的输入。模态对话框对象是以变量的形式构建的,CDialog::DoModal用来启动一个模态对话框,在对话框关闭后该函数才返回。如果用户按下了IDOK按钮确认设置,那么DoModal返回IDOK,若用户按下了IDCANCEL按钮取消设置,则DoModal返回IDCANCEL。 

    非模态对话框与应用程序共用消息循环,它不垄断用户的输入。非模态对话框对象应该用new操作符在堆中创建,应该调用CDialog::Create而不是CDialog::DoModal来显示对话框,需要注意对话框的可见性问题。应该调用CWnd::DestroyWindow而不是CDialog::EndDialog来关闭非模态对话框,所以一般需要重新编写OnOK和OnCancel函数。非模态对话框对象应该是自动清除的,所以应该重写PostNcDestroy函数并在该函数中用delete删除对象本身。

    除了主框架窗口类、视图类和非模态对话框类以外,MFC的窗口类一般都是非自动清除的。不必调用delete来删除一个具有自动清除功能的窗口对象。 

    标签式对话框由多个页(子对话框)组成,可以容纳大量的控件。CPropertySheet类代表对话框的框架,CPropertyPage类代表莫一页。标签式对话框有一个特殊的Apply按钮,可以使用户在不退出对话框的情况下使设置生效。 

    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 访问。

    展开全文
  • 对话框和常用控件

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

    对话框是Windows应用程序中最重要的用户界面元素之一,是与用户交互的重要手段,在程 序运行过程中,对话框可用于扑捉用户的输入信息或数据。对话框是一个特殊类型的窗口,任何对窗口进行的操作(如:移动、最大化、最小化等)都可在对话框中 实施,一般来说,在对话框中通过各种控件(如:按钮、编辑框、列表框、组合框等)来和用户进行交互。控件是在系统内部定义的用于和用户交互的基本单元。

    一、对话框的使用

        Visual C++提供的对话框编辑器能“可视”地进行设计、编辑,并可用

    ClassWizard为对话框从CDialog基类中派生一个类,MFC的CDialog类封装了用于对话框的显示、关闭等操作的许多功能函数,例如:DoModal函数用来显示模式对话框并返回用户操作的结果。

    1、模式对话框(为186附加举例页)

        所谓模式对话框是指,当对话被弹出时,用户必须在对话框中进行相应的操作,在退出对话框之前,对话框所在的应用程序不能继续执行。平常我们所见到的对话框大多是模式对话框。

    例:模式对话框(通过菜单命令弹出)

    1)  建一个单文档(SDI)应用程序

    2)  创建对话框模板

    InsertàResourceà选中Dialogà单击New

    拖过一个静态文本,鼠标对准它,按右键点properties改标题为“新建模式对话框”。

    3)鼠标右键对准对话框的任何位置单击,选择properties选项,设置ID为IDD_MYDIALOG

    4)给对话框创建类

         双击新建对话框的任何位置,单击OK,写类名为“CMyDlg”,保

    证”CDialog”作为该类的基类。

    5)创建菜单命令:

    a)打开资源编辑器的菜单项Menu

    b)  双击IDR_MAINFRAME

    c)  双击右边空白菜单,点开pop_up(让它是空白),在名字处写”弹出对话框(&A)”,ID处写ID_PUPDIALOG

    6)将菜单命令连接到主框架程序中,完成ID_PUPDIALOG的消息映射:

        ViewàClassWizardà保证Class name里是CMainFrame,在ObjectIDs

    里找到ID_PUPDIALOG点黑àMessages里(右边)点COMMAND建立主框架对象方法并加代码:

    void CMainFrame::OnPupdialog()

    { CMyDlg  MyDlg;

    MyDlg.DoModal(); //DoModal()是CDialog类成员函数,通过调用该

    //函数将显示对话框。

    }               

      7)在CMainFrame.cpp文件里加:

              #include “MyDlg.h”   // 之后运行。

     2、无模式对话框(为186附加页)

    非模式对话框,弹出后,可一直保留在屏幕上,用户可继续在应用中进行其它操作或启动其它应用程序,当需要使用对话框时,只需象激活一般窗口一样激活对话框即可。

    1)建一个SDI(单文档)应用程序

    2)创建对话框模板

    InsertàResourceà点黑DialogàNew

    拖过一个静态文本,鼠标对准它,按右键点properties改标题为“新建非模式对话框”。

    3)为对话框创建类

    点出对话框(IDD_DIALOG1缺省的ID号),双击对话框中的任意位置,出现一个表,你点OKà出现一个对话框,你写类名:CDlg保证基类为CDialog

    4)创建菜单

    打开工作区àMenuàIDR_MAINFRAMEà双击空白菜单写“非模式对话框”关闭à再点下面空菜单写名字“显示非模式对话框”ID处写ID_DLG.

    5)添加菜单命令消息

     WiewàClassWizardàMessage Mapsà保证Class name里是Wiew类(视图类),在Object IDS里找到ID_DLG(菜单的ID)点黑右边COMMAND双击它àOK

    6)a、你在Wiew.h里加:#include “Dlg.h”

       b、在public:里加:CDlg *dlg;//创建CDlg对象

       c、在View.cpp的OnDlg()函数里加:

          CMyView::OnDlg()

           { 

              dlg=new CDlg(this);

              dlg->Create(IDD_DIALOG1);//使对话框摸板从资源中创建一个非

    //模式对话框

              dlg->ShowWindow(SW_RESTORE);//显示对话框

            }

    二、使用对话框编辑器(对话框编程)(187页)

    *1、可建一个单文档应用程序名为“对话框编程”,在此程序中加入一个对话框:

       InsertàResourceà点黑Dialogànew 出现188页的图

    2、拖入一些控件,对这些控件进行排序、布局、大小调整、上下对齐、测试等。*最后在对话框IDD_DIALOG1上留一个按钮控件,其标识符为:IDC_BUTTON1

    3、识别控件工具栏(188页)

    4、在加对话框时,InsertàResourceà点开+Dialog见有7类对话框,分别了解其

    不同用途(见192页)。

    5、对话框的属性

    A、ViewàProperties B、按Alt+Enter C、用鼠标右键单击对话框模板àProperties

    都能弹出对话框的属性框,见书193页对属性General的解释。

    *6、为对话框添加类(194页)

        对准对话框的任意非控件区域双击鼠标,将弹出书194页图5.11所示

    的对话框,询问是否为对话框资源创建一个新类à单击OKà弹出书194页

    图5.12所示的对话框à你定义一个新类的名字如:CMyDlg(注意:类名必

    须以C打头),下面的基类Base class和ID标识符Dialog ID内容一般不改。

    * 7、添映射消息(195页)

      接上,点OK出现“MFC ClassWizard”对话框,如书195页图5.13所示à(保证类名处是CMyDlg)选定点黑IDC_BUTTON1à单击BN_CLICKED 消息àAdd Functionà出现书195页图5.14对话框àOKàEdit Code

    *8、添加用户代码(195页)

    接上,到MyDlg.cpp文件中,写:

    void CMyDlg::OnButton1()

    {

       MessageBox(“欢迎进入对话框的设计!”);

    }//这时运行还不见对话框,接下

    *9、在程序中使用对话框(196页)

        由于对话框的代码是以类为模块来设计的,使用时需要在程序中加入该类

    的头文件,并定义一个类对象,然后就可以使用该类的相关成员。

    项目工作区àFileViewà打开应用程序的.cpp文件,在前面加:

      #include “MyDlg.h”

       在InitInstance函数体中的return TRUE语句之前添加下列代码:

         CMyDlg dlg;

         dlg.DoModal();//DoModal()函数是负责对话框的显示和终止。

    运行!则直接显示出对话框,单击按钮则出现“欢迎进入对话框的设计”

    的字样。

    本例建的CMyDlg类及以后在各个项目中建立的类,在文档、视图、主框

    架类中都可同样使用。

    (1)添加对话框资源

    对话框资源类型:

    IDD_DIALOGBAR 对话条,往往和工具条放在一起。

    IDD_FORMVIEW  一个表状风格的对话框,用于无模式对话框或视图类

    IDD_OLE_PROPPAGE_LARGE一个大的OLE属性页

    IDD_OLE_PROPPAGE_SMALL一个小的OLE属性页

    IDD_PROPPAGE_LARGE一个大属性页,用于属性对话框

    IDD_PROPPAGE_MEDIUM一个中等大小的属性页,用于属性对话框

    IDD_PROPPAGE_SMALL一个小的属性页,用于属性对话框

    (2)改变对话框的属性

    ID框:修改或选择对话框的标识符名称

    Caption框:输入对话框的标题名称,中英文均可。

    Font按钮:单击此按钮可选择字体的种类(如宋体)及尺寸(如9号)

    Xpos/Ypos:对话框左上角在父窗口中的X,Y坐标都为0时表示居中

    Menu框:默认值为无,当对话框需要选单时输入或选择指定的选单资源

    Class name:默认值为无,它提供C/C++语言编程时所需要的对话框类名,对

               MFC类库的资源文件来说,该项不被激活

    三、控件的创建和使用方法(197页)

       控件是在系统内部定义的能够完成特定功能的控制程序单元。在应用程序中使用控件不仅简化了编程,还能完成常用的各种功能。为了更好地发挥控件的作用,用户还必须理解和掌握控件的属性、消息以及创建和使用方法。

    注:控件工具栏及各按钮含义:

    1、控件的选择2、静态文本3、组框4、复选框5、组合框6、水平滚动条

    7、旋转按钮8、滑动条9、列表视图10、标签11、复合编辑12、月历

    13、用户定制工具14、静态图片15、编辑框16、按钮17、单选框18、列表框

    19、垂直滚动条20、进展条21、热键22、树形视图23、动画24、日期选择

    25、IP地址26、组合框的扩展(从左边往下数,再从右边往下数)

     

    1、控件的创建和使用方法(197页)

        控件的创建方法有2种:1)是在对话框摸板中用编辑器指定控件,也就是说,将控件的父窗口指定为对话框,如:上面的BUTTON1按钮控件。2)是将控件 看做任意一窗口的子窗口,并通过调用相应的Create函数来创建。下面我们用第2种方法创建控件(此种方法涉及的内容复杂,也不能发挥对话框编辑器可视 化编程的优点,故不提倡此种方法,而用第一种方法)。

    (1)打开上个项目,在CMyDlg类的头文件MyDlg.h里添加一个按钮类CButton

         指针变量:(public:里)

             CButton *m_btnWnd;

    (2)按Ctrl+W或ViewàClassWizard打开MFClassWizard对话框,并切换到

    Message Maps页面,在Object IDs列表中选定点黑CMyDlg项,并在

    Message列表中找到WM_INITDIALOG消息点黑àAddFunctionàEditCode

    (3)添加代码:

    BOOL CMyDlg::OnInitDialog()

    { -----

     m_btnWnd=new CButton();//构造按钮控件

    //下面“”按钮上的字,创建子窗口|窗口最初是可见的|创建的是按键按钮

     m_btnWnd->Create(“你好”,WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,

                CRect(20,20,120,60),this,201);//创建

     CFont *font=this->GetFont();//获取对话框的字体

     m_btnWnd->SetFont(font);//设置控件字体

     return TRUE;//(程序原有的)

    }

    代码中,Create用来创建一个按钮控件,该函数的第一个参数用来指定该按钮的标题,第二个参数用来指定控件的风格,第三个参数用来指定它在父窗口中的位置和大小,第四个参数用来指定父窗口指针,最后一个参数是指定该控件的标识值。

    WS_CHILD表示按钮是作为对话框的一个子窗口来创建的。

    WS_VISIBLE是使控件可见。

    BS_PUSHBUTTON表示创建的是按键按钮。

     (4)编译并运行

    2、控件的数据交换和数据效验(数据成员)(198页)

        使用ClassWizard可以很容易地定义一个控件的成员变量及其数据范围。例如:上面的CMyDlg类的按钮控件IDC_BUTTON1添加并使用其成员变量m_MyBtn,步骤如下:

    (1)打开上例项目,ViewàClassWizardàMember Variablesà选定Class name

    中为CMyDlg,然后在Control IDs列表中à点黑IDC_BUTTON1àAddVariable

    (或双击鼠标左键),弹出Add Member Variable对话框,如书198页图5.17

    à写好数据成员名:m_MyBtn  下面Category和Variables type里的不动

    àOKà见表上已建成(如:书199页图5.18)

    下面是建成员变量的三个对话框图:

    (2)再向对话框加一个编辑控件,并加成员变量m_strEdit,类型为CString置

    数值为Value(注:Category框内可选择Value或Control两种类型。 Control所对应的变量类型就是MFC为该控件封装的控件类。Value所对应的是数值类型。不同控件所提供的关联的数值类型各不同,例如:对于编辑 框来说,Variables type中的数值类型有CString (字符串),int,UINT(32位无符号整数),long(32位带符号整数), DWORD(32位无符号整数,段地址和相关的偏移),float,double,BYTE(8位无符号整数),short,BOOL等)。OK后在下面 写20为最大值。(下面见书199—200页)

    打开本项目的MyDlg.h见到:

       CButton m_MyBtn;

       CString m_strEdit;

    在MyDlg.cpp里见到:

       m_strEdit=_T(“ “);

    在DoDataExchange函数体内见:

      DDX_Control(pDX,IDC_BUTTON1,m_MyBtn);

        DDX_Text(pDX,IDC_EDIT1,m_strEdit);//IDC_EDIT1是标识m_strEdit

    //是成员变量

    DDV_MaxChars(pDX,m_strEdit,20);//校验m_strEdit的最大字符个数不超过20。

     (3)将CMyDlg::OnButton1()修改成:

          void CMyDlg::OnButton1()

    {

       UpdateData();//默认参数值是真TRUE

       m_MyBtn.SetWindowText(m_strEdit);

       //GetDlgItem(IDC_BUTTON1)->SetWindowText(“欢迎”);//用此条代替

    //上条也行,

                                  //单击Button1按钮,此按钮名就是“欢迎”。

    }

    之后运行该程序,当在编辑框输入Hello后,单击Button1按钮,则该按

    钮的名称就变成了编辑框中的内容Hello了,见书200页。

    八、控件的通用属性 (201页)

            在控件的属性对话框中含有许多属性,如:书201页(按钮的属性对话

    框),General(一般属性)、Styles(控件的风格)、Extended Styles(控件的扩

    展风格)。Styles和Extended Styles是用来设定控件的外观的、辅助功能的。

    不同控件具有不同的风格和扩展风格。见201页表5.5.

    控件的General属性:

    ID:控件的标识符,每种控件都有默认的ID,例如按钮控件为IDC_BUTTON1

    Caption:控件的标题,大多数控件都有默认的标题,例如按钮控件为Button1

    Visible:指定控件初始化时是否可见

    Group:指定控件组中的第一个控件,如果该项被选中,则此控件后的所有控件均被看成一组,成组的目的是可以让用户键盘方向键在同一组控件中进行切换

    Help ID:若该项被选中,则为该控件建立一个上下文相关的帮助标识符

    Disabled:指定控件初始化时是否禁用

    TabStop:若该项被选中,则用户可以使用Tab键来选择控件

    九、控件的消息

         对于每个消息,系统都会用一个MSG结构来记录(见201页)。对一般控件来说,其通知消息是一条WM_COMMAND消息(见202页)。例:

    1、打开上面的项目“对话框”

    2、ViewàClassWizardà在CMyDlg里(左边的Object IDs里点黑CMyDlg)

       à在Message里找到OnCommand点黑àAdd FunctionàEdit Codeà写:

     BOOL CMyDlg::OnCommand(WPARAM wParam,LPARAM lParam)

       {

           WORD nCode=HIWORD(wParam);//控件的通知消息

              WORD nID=LOWORD(wParam);//控件的ID号

            if((nID==201)&&(nCode==BN_CLICKED))//用户单击按钮产生的消息

                 MessageBox("你按下了/"你好/"按钮!");

            ----

       }//WORD是16位无符号整数

        //EN_CHANGE是编辑框中的文本被改变时发出的消息

    BN_CLICKED是当用户单击按钮产生的消息:

    单击对话框中的“你好”按钮,弹出"你按下了/"你好/"按钮!"的消息对话框。由于 Create创建的控件无法用ClassWizard直接映射其消息,因此上述方法祢补了ClassWizard的不足,使用时要特别注意。见书202页 通知消息是所有Windows控件所共有的:

    NM_CLICK    在控件中单击鼠标左键按钮

    NM_DBLCLK  在控件中双击鼠标左键按钮

    NM_RDBLCLK在控件中双击鼠标右键按钮

    NM_RETURN当控件具有输入焦点时按下ENTER键

    NM_SETFOCUS控件得到输入焦点

    NM_KILLFOCUS控件失去输入焦点

    NM_OUTOFMEMORY没有足够的内存使控件

    十、常用控件

    静态控件 CStatic  用于向用户显示一些几乎固定不变的文字或图形描述

    按    钮 CButton 用于产生某些命令或改变某些选项设置

    编辑框   CEdit  可完成文字的输入、输出双向操作,使用户能查看并编辑文字

    列表框   CListBox显示一个列表,让用户从中选取一个或多个项

    组合框   CComboBox它把列表框和编辑框有机地组合在一起,用户不仅能选择

    列表中已有的项,还能编辑出新的项

    滚动条   CScrollBar通过滚动块在滚动条上的移动来改变某些数值

    进展条   CProgressCtrl 用于指示一个操作的进度

    旋转按钮 CSpinButtonCtrl 又称“上下控制”,是一对箭头按钮,用户单击它们

    可以增加或减小某个值

    滚动条   CSliderCtrl 是一个包含一个滑动块和可选的刻度线,用户可以用鼠标

    或方向键沿某个方向移动滑动块

    图象列表 CImageList是一系列相同大小的图象的集合

    标签控件 CTabCtrl类似于一个笔记本的分割器或一个文件柜上的标签,使用它

    可以将一个窗口或对话框的相同区域定义为多个页面

    1、静态控件

        静态控件是用于显示一个字符、框、矩形、图标、位图或增强的图元文件,它可以用做标签、框或用于分隔其它的控件。一个静态控件一般不接受用户输入,也不产生通知消息。

    在对话框编辑器的控件工具栏中,属于静态控件的有:静态文本(Static Text)、

    组框(Group Box)、图片控件(Picture)三种。其中,静态图片控件的属性对话框如:书204页图5.21所示,表5.7列出了其一般属性和风格的各个项的意 义。我们可以选择Type(图片类型)、Image(图象资源)两个组合框中的有关选项内容,并可将应用程序资源中的图标、位图等内容显示在该静态图片控 件中。另外,用户还可设置其风格来改变控件的外观以及图象在控件的位置等。

    静态图片控件的General和Style属性对话框:

    Type     图片类型,用户可以从中选择Frame(框)、Rectangle(矩形区域)、Icon(图标)、Bitmap(位图)、Enhanced Metafile(增强图元文件,它是各种绘图命令的集合)

    Image 当图片类型为Icon或Bitmap时,通过此框可选择指定的资源ID号

          设置Frame和Rectangle的颜色,它可以是black(黑色)、white(白色)、

          gray(灰色)、或者是具有3D外观的etched(腐蚀色)

    Sunken        选中时,在控件的周围有下沉的边框

    Notify         选中时,当用户单击或双击图片时会向其父窗口发出通知消息

    Right justify     选中时,用户重置图片大小,图片右下角是固定不变的

    Border          选中时,图片周围有边框

    Center image    选中时,图片显示在控件中央,其余区域由图片左上角的象素

    颜色来填充

    Real size image选中时,按图片的原始大小来显示,超过控件区域的部分被裁剪

       在它的属性中,用户可以选择Type(图片)、Image(图象资源)两个组合框中的有关选项内容,并可将应用程序资源中的图标、位图等内容显示在该静态图片控件中,另外,用户还可以设置其风格来改变控件的外观以及图象在控件的位置等。

    例1:图片控件(将一个.bmp图形显示在图片控件上)(附加举例)

    1)建一个单文档(SDI)应用程序

    2)创建对话框模板:  InsertàResourceà点黑Dialogànew

    3)将图片控件Picture拖到对话框上,并拉大些,将OK和CANCEL拖到下面。

    4)向项目中插入一个 .bmp图片:InsertàResourceà点黑BitmapàImportà在

    出现的表中要下拉出:所有文件(*.*)在某处找到一个.bmp图形àImport放到此项目中。

    5)将这个图片放到图片控件上

         右键对准图片控件单击à出现属性框àType处下拉置Bitmapàimage处下拉置IDB_BITMAP1,就将图片加到了图片控件上。

    6)为刚才建的对话框添加类

         双击新建对话框的任何位置àOKà类名写:CMyDlg

    7)创建一个菜单项,用来显示对话框

       ResourceViewàMenuàIDR_MAINFRAMEà双击空白菜单à点POPUP

    ID处写:ID_DLG 菜单名写:显示图片对话框à关闭

    8)将菜单命令映射到View中去 

         ViewàClassWizardà要加到View中àID-DLGàCOMMANDàEditCode

      (在View.h的头部加:#include “MyDlg.h” 在public:下加:CMyDlg *dlg;)并

    加代码:

            CMyView::OnDlg()

    {  dlg=new CMyDlg(this);

                  dlg->Create(IDD_DIALOG1);

                  dlg->ShowWindow(SW_RESTORE);

            }

    9)编译运行

    2:按钮

    常见的按钮有三种类型:(204页)

    (1)按键按钮

         按键按钮通常可以立即产生某个动作,执行某个命令,因此也常被称为命

    令按钮。

    (2)单选按钮

         其外形是在文本前有一个圆圈,当它被选中时,就标上一个黑点。

    (3)复选框

    其外形是在文本前有一个空心方框,当它被选中时,就加上一个“∨”标记

    1)按钮的消息

       常见的按钮映射消息有两个:

    (1)    BN_CLICKED(单击按钮)(2)BN_DOUBLE_CLICKED(双击按钮)

    见下图:

    2)按钮选中操作

      最常用的按钮操作是设置或获取一个按钮或多个按钮的选中状态。

    CButton类的以下2个成员函数原型如下:

      void SetCheck(int nCheck); //设置指定按钮的选中状态

      int GetCheck()const;      //获取指定按钮的选中状态

      其中:nCheck和GetCheck函数返回的值可以是:0表示不选中,1表示选中,

      2表示不确定(仅用于三态按钮)

      而对于多个单选按钮的选中状态的设置或获取,需要使用CWnd类的成员函数CheckRadioButton和GetCheckedRadioButton,它们的原型如下:

    void CheckRadioButton(int nIDFirstButton,int nIDLastButton,int nIDCheckButton);

    int GetCheckedRadioButton(int nIDFirstButton,int nIDLastButton);

    其中,nIDFirstButton和nIDLastButton分别指定这组单选按钮的第一个和最后一个按钮ID值,nIDCheckButton用于指定要设置选中状态的按钮ID值,函数GetCheckedRadioButton返回被选中的按钮ID值。

    例2:用静态图片、单选按钮、复选框和按键按钮控件设计界面,运行结果如:书205页图 5.23所示。刚开始,所有单选按钮都是灰显的,我们不能选择它们,这种情况称为“禁用”,而当选中“允许”复选框后,所有单选按钮可以使用,用户选定一 个单选框后,单击[应用]按钮,则弹出相应的消息对话框。

    (1)用MFC AppWizard(exe)创建一个名为:“按钮的使用”的基于对话框的应用

    程序。(第一步将类型选择为Dialog Based,然后按[Finish]按钮即可。)

    (2)打开属性对话框,将其标题改为“使用Windows常用控件”。

    (3)参看书205页图5.23的控件布局,用编辑器为对话框添加如书206页表5.8

    所示的一些控件。

    (4)说明:上面4个单选按钮的Tab次序应连续,而且从IDC_RADIO1到IDC_RADIO4依次增加。

    (5)ViewàClassWizard(或按Ctrl+W快捷键)à切换到Member Variables页面,在Class name中选择CMyDlg,在Control IDs里选中点黑IDC_CHECK1复选框ID号àAdd Variables按钮,为其添加一个BOOL类型的成员变量

         m_bEnabledàOK。

    (6)切换到ClassWizard的Message Maps页面,分别选中点黑复选框IDC_CHECK1和按钮IDC_BUTTON1,分别为其添加映射消息,并添如下代码:

          void CMyDlg::OnCheck1()

           {

             UpdateData();

             for(int i=0;i<4;i++)

             GetDlgItem(IDC_RADIO1+i)->EnableWindow(m_bEnabled);

    }//EnableWindow是使一个控件窗口禁用或允许使用,它取决于该函数

    //的参数,为TRUE时表示可以使用,否则禁用。

    void CMyDlg::OnButton1()

    {

      UpdateData();

      if(!m_bEnabled) return;

      int nID=GetCheckedRadioButton(IDC_RADIO1,IDC_RADIO4);

      if(nID==IDC_RADIO1)

       { MessageBox(“1”); }

        if(nID==IDC_RADIO2)

        { MessageBox(“2”); }

    if(nID==IDC_RADIO3)

        { MessageBox(“3”); }

    if(nID==IDC_RADIO4)

        { MessageBox(“4”); }

    (7)在此文件中找到CMyDlg::OnInitDialog函数体,添加下列代码:

         BOOL  CMyDlg::OnInitDialog()

          {

             CheckRadioButton(IDC_RADIO1,IDC_RADIO4,IDC_RADIO1);

                         //设置第一个单选按钮为选中

             OnCheck1();

             return TRUE;//此条是原有的

    (8)编译运行

    3、编辑框

       编辑框是一个让用户从键盘输入和编辑文本的矩形窗口,用户可以通过它,很方便地输入各种文本、数字或口令,也可使用它来编辑和修改简单的文本内容。

       当编辑框被激活且具有输入焦点时,就会出现一个闪动的插入符(又可称为文本光标),表明当前插入点的位置。

      1)编辑框的属性和风格

      

    Align text 各行文本对齐方式:Left,Center,Right,默认时为Left

    Multiline  选中时为多行编辑框,否则为单行编辑框

    Number   选中时控件只能输入数字

    Horizontal scroll 水平滚动,仅对多行编辑框有效

    Auto HScroll当用户在行尾键入一个字符时,文本自动向右滚动

    Vertical scroll 垂直滚动,仅对多行编辑框有效

    Auto VScroll 当用户在最后一行按ENTER键时,文本自动向上滚动一页,仅对

    多行编辑框有效

    Password      选中时,键入编辑框的字符都将显示为”*”,用于口令设置,仅对

    单行编辑框有效

    No hide selection通常情况下,当编辑框失去键盘焦点时,被选择的文本仍然反

    色显示,选中时,则不必具备此功能

    OEM convert   选中时,实现对特定字符集的字符转换

    Want return     选中时,用户按下ENTER键,编辑框中就会插入一个回车符

    Border         选中时,在控件的周围存在边框

    Uppercase      选中时,键入在编辑框的字符全部转换成大写形式

    Lowercase      选中时,键入在编辑框的字符全部转换成小写形式

    Read-Only      选中时,防止用户键入或编辑文本

    多行编辑框具有简单文本编辑器的常用功能,例如:它可以有滚动条,用户按Enter键另起一行以及文本的选定、复制、粘贴等常见操作。而单行编辑框功能较简单,它仅用于单行文本的显示和操作

      2)编辑框的基本操作

    A、设置口令

       口令设置在编辑框中不同于一般的文本编辑框,用户输入的每个字符都被一个特殊的字符代替显示。这个特殊的字符称为口令字符。默认的口令字符是”*”。

    应用程序可以用成员函数CEdit::SetPasswordChar 来定义自己的口令字符,其函数原形如下:

        void SetPasswordChar(TCHAR ch);

    其中,参数ch表示设定的口令字符;当ch=0时,编辑框内将显示实际字符。

    B、选择文本

    编程选择文本,调用成员函数CEdit::SetSel来实现,还有:

    CEdit::GetSel和CEdit::ReplaceSel,它们分别用来获取编辑框中选择的开始和结束的位置以及替换被选择的文本。

    C、设置编辑框的页面边距

    用CEdit::SetMargins函数来实现,其函数原型如下:

    void SetMargins(UINT nLeft,UINT nRight);

      其中,参数nLeft和nRight分别用来指定左、右边距的象素大小。

    D、剪帖板操作

    E、获取多行编辑框文本(见书208页---209页)。

    如何设置口令(密码):附加例题见后面(教案页)

    3)编辑框的通知消息

    当编辑框的文本修改或者被滚动时,会向其父窗口发送一些消息,这些消息

    是:(参看书209页表5.10并由下图所示:)

    EN_CHANGE当编辑框中的文本已被修改,在新的文本显示之后发送此消息

    EN_HSCROLL当编辑框的水平滚动条被使用,在更新显示之前发送此消息

    EN_KILLFOCUS编辑框失去键盘输入焦点时发送此消息

    EN_MAXTEXT文本数目到达了限定值时发送此消息

    EN_SETFOCUS编辑框得到键盘输入焦点时发送此消息

    EN_UPDATE编辑框中的文本已被修改,新的文本显示之前发送此消息

    EN_VSCROLL当编辑框的垂直滚动条被使用,在更新显示之前发送此消息

     例:使用静态文本、组框、编辑框以及按钮等控件设计界面,运行结果参书209

    页图5.25即下图所示。当用户在“成绩1”、“成绩2”、和“成绩3”编辑框中输

    入成绩后,单击[计算平均分]按钮,将显示出这三个成绩的平均分。

     程序设计步骤如下:

    (1)    打开前面的项目“按钮的使用”。

    (2)    向应用程序中添加一个对话框资源,insertàResourceà点黑Dialogànew

    à出现一个新的对话框à右键单击这个新对话框àpropertiesà打开其属

    性对话框àFontà将其字体设置为“新宋体,9”,标题改为“使用编辑框”,

    ID号改为IDD_EDIT,删除默认的Cancel按钮。

    (3)、(4)、(5)按书210页往下作

    书(6)m_strAve="0.00";

              UpdateData(FALSE);//将成员变量数据传给控件,并在控件中显示

    书(7)UpdateData();//将控件显示的数据传给成员变量

              double ave=(double)(m_nScore1+m_nScore2+m_nScore3)/3.0;

              m_strAve.Format("%6.2f",ave);//Format是CString类的一个经常使用的成

    //员函数,它通过格式操作使任意类型的数据转换成一个字符串

              UpdateData(FALSE);//将成员变量数据传给控件,并在控件中显示

    书(8)定位到void CMyDlg::OnButton1()

           { ---------//注意,这是你上个程序的按钮命令,在里找到:

             if(nID= =IDC_RADIO1)

             {  CEditDlg dlg;     //注意,将原来MessageBox("1");去掉

                dlg.DoModal();   //加上这2条

             }

    书(9)在上个程序的:按钮的使用Dlg.cpp即CMyDlg.cpp的头文件处加:

           #include "EditDlg.h"(就是(8)所在的文件)

        编译运行

    4、列表框

    列表框是一个列有许多项目让用户选择的控件。它与单选按钮组或复选框组一样,都可让用户在其 中选择一个或多个项。但不同的是,列表框中项的数目是可灵活变化的,程序运行时可往列表框中添加或删除某些项。并且,当列表框中项的数目较多而不能一次全 部显示时,还可以自动提供滚动条来让用户浏览其余的列表项。

    1)列表框的风格

    按性质来分,列表框有单选、多选、扩展多选以及非选四种类型,默认风格

    下的单选列表框让用户一次只能选择一个项,多列表框可让用户一次选择几个项,而扩展多项列表 框允许用户用鼠标拖动或其它特殊组合键进行选择,非选列表框不提供选择功能。还有其它一系列风格,用于定义列表框的外观及操作方式,这些风格可在下图所示 的列表框属性对话框中设置:列表框的Styl属性:

    Selection指定列表框的类型:单选(Single)、多选(Multiple)、扩展多选(Extended)、不选(None)

    Owner draw自画列表框,默认为No

    Has strings选中时,在自画列表框的项目中含有字符串文本

    Border选中时,使列表框含有边框

    Sort选中时,列表框的项目按字母顺序排列

    Notify选中时,当用户对列表框操作就会向父窗口发送通知消息

    Multi-column选中时,指定一个具有水平滚动的多列列表框

    Horizontal scroll选中时,在列表框中创建一个水平滚动条

    Vertical scroll选中时,在列表框中创建一个垂直滚动条

    No redraw选中时,列表框发生变化后不会自动重画

    Use tabstops选中时,允许使用停止位来调整列表项的水平位置

    Want key input选中此项,当用户按键且列表框有输入焦点时,就会向列表框的父窗口发送

    相应消息

    Disable no scroll选中时,即使列表框的列表项能全部显示,垂直滚动条也会显示,但此时

    是禁用的(灰显)

    No integral height选中时,在创建列表框的过程中,系统会把用户指定的尺寸完全作为列表

    框的尺寸,而不论是否有项目在列表框,也不能完全显示出来

    2)  列表框的基本操作

    当列表框创建之后,往往要添加、删除、改变或获取列表框中的列表项,这些操作都可以调用MFC的CListBox类成员函数加以实现。

    索引:表明项目在列表框中排列的位置,它是以0为基数的,即列表框中第一项的索引是0,第二项的索引是1,依次类推。

    (1)添加列表项

    列表框创建时是一个空的列表,需要用户添加或插入一些列表项,其函数原型为:

    int AddString(LPCTSTR lpszItem);         

    int InsertString(int nIndex,LPCTSTR lpszItem);

     其中:列表项的字符串文本由参数pszItem来指定,成功返回列表在列表框的索引,错

    误返回LB_ERR,空间不够返回LB_ERRSPACE。

    但:InsertString函数不会将列表项进行排序,不论列表框控件是否具有sort属性,只是将列表项插在指定索引的列表项之前,若nIndex等于-1,则列表项添加在列表框末尾。

    而:AddString函数在当列表框控件具有sort属性时会自动将添加的列表项进行排序。

    以上2个函数只能将字符串增加到列表框中,但有时用户还会需要根据列表项使用其他数据。这时,ListBox的SetItemData和SetItemDataPtr能有效解决这个问题,它们能使用户数据和某个列表项关联起来:

    int SetItemData(int nIndex,DWORD dwItemData);

    int SetItemDataPtr(int nIndex,void *pData);

    其中,SetItemData是将一个32位数与某列表项(由nIndex指定)关联起来,而

          SetItemDataPtr可以将用户的数组、结构体等大量的数据与列表项关联

          若产生错误,它们都返回LB_ERR

    而:GetItemData和GetItemDataPar分别用来获取相关联的用户数据。

     

    以下等待修改

    (2)删除列表项(3)查找列表项(4)列表框的单项选择

    (5)列表框的多项选择

    3)列表框的通知消息

        当列表框中发生了某个动作,如用户双击选择了列表框中某仪项时,列表框就会向父窗口发送一条通知消息。常用的通知消息如书214页表5.14所示。

    例:将一个SCORE结构(含有三门成绩的数据成员)数据和列表中每一个学生

    姓名列表关联起来。当用户单击[添加记录]按钮时,学生成绩记录中的“姓

    名“被添加在列表框中,且该学生的成绩与该列表项关联。当用户单击[删

    除记录]按钮时,列表框中当前选择项被删除,相关联的数据所占的内存空

    间被释放。任何时候选中列表框中某个学生,相应的记录数据被显示出来,

    如:书214页图5.27所示。

    (1)    打开前面的基于对话框的项目“按钮的使用”。

    (2)    向项目中添加一个对话框资源IDD_LISTBOX,标题为“使用列表框”,并用ClassWizard为此对话框建类为:CListBoxDlg。

    接着按书214页往下作:(3)、(4)-------

    (可在此处将第9步、第11步作出…………. 因为第5步要用到SCORE结构体 )

    书(5)

    UpdateData(TRUE);

           if(m_strName.IsEmpty())//判断m_strName是否为空

           { MessageBox("姓名不能为空!");

             return;

           }

           m_strName.TrimLeft();//裁剪m_strName左边的空格

           m_strName.TrimRight();//裁剪m_strName右边的空格

           if((m_List.FindString(-1,m_strName))!=LB_ERR)

           {  MessageBox("列表框中已有相同姓名,不能添加!");

              return;

           }

           int nIndex=m_List.AddString(m_strName);//向列表框添加学生

                             //姓名将该学生成绩与新增的列表项关联起来

           SCORE data;

           data.score1=m_nScore1;

           data.score2=m_nScore2;

           data.score3=m_nScore3;

           m_List.SetItemDataPtr(nIndex,new SCORE(data));

    书(6)

           int nIndex=m_List.GetCurSel();//获得当前选项的索引

           if(nIndex!=LB_ERR)

           {  m_List.DeleteString(nIndex);//删除当前选择项

              m_strName.Empty();

              m_nScore1=m_nScore2=m_nScore3=0;

              UpdateData(FALSE);

           }

           else MessageBox("当前没有选择项或列表框操作失败!");

    书(7)

    int nIndex=m_List.GetCurSel();

           if(nIndex!=LB_ERR)

           {  m_List.GetText(nIndex,m_strName);

              SCORE *data=(SCORE *)m_List.GetItemDataPtr(nIndex);

              m_nScore1=data->score1;

           m_nScore2=data->score2;

           m_nScore3=data->score3;

              UpdateData(FALSE);

           }

    书(8)

    for(int nIndex=m_List.GetCount()-1;nIndex>=0;nIndex--)

           { //删除所有与列表相关联的SCORE结构数据,并释放内存

                  delete(SCORE *)m_List.GetItemDataPtr(nIndex);

           }

    CDialog::OnDestroy();//关闭对话框

    说明:对话框被清除时发送WM_DESTROY消息。用户在此消息的映射函数中添加一些对象删除代码,以便在对话框清除前有效地释放内存空间。

    书(9)

    struct SCORE

       {  int score1;

          int score2;

             int score3;

       };

    书(10)定位到void CMyDlg::OnButton1()

           { ---------//注意,这是你上个程序的按钮命令,在里找到:

             if(nID= =IDC_RADIO2)

             {  CListBoxDlg dlg;     //注意,将原来MessageBox("2");去掉

                dlg.DoModal();   //加上这2条

             }

    }

    书(11)在上个程序的:按钮的使用Dlg.cpp即CMyDlg.cpp的头文件处加:

           #include "ListBoxDlg.h"

    //(就是(10)所在的文件)

        编译运行

    注意:第4步完后,可以先将第9步和第11步作出,以能点出相应函数。

    5、组合框

          列表框中不能输入列表项之外的内容,而编辑框也没有列表框的选择操

    作。于是就把常用的项,列在列表框中以供选择,而同时提供编辑框,允许

    用户输入列表框中所没有的新项,这就是组合框控件。它结合列表框和编辑

    框的特点,取二者之长,从而完成较为复杂的输入功能。

    1)  组合框的风格及类型

    按照组合框的主要风格特征,可分为三类:简单组合框、下拉式组合框、

    下拉式列表框。简单组合框和下拉式组合框都包含列表框和编辑框,但是简单组合框中的列表框不需要下拉,是直接显示出来的,而当用户单击下拉式组合框中的下拉按钮时,下拉的列表框才被显示出来。下拉式列表框虽然具有下拉式的列表,却没有文字编辑功能。

    组合框的一些其它风格,见书217页图5.28所示的组合框的属性对话框

    中的设置。其各项含义见书218页表5.17所示。

    2)  组合框常见操作

       组合框的操作大致分为两类,一类是对组合框中的列表框进行操作,

    另一类是对组合框中的编辑框进行操作。这些操作都可以调用CComboBox

    成员函数来实现,见书218页表5.18所示。由于组合框的一些编辑操作与

    编辑框CEdit的成员函数相似,如:GetEditSet,SetEditSel等,因此这些

    成员函数没有在上述表中列出。

    3)  组合框的通知消息

       在组合框的通知消息中,有的是操作列表列表框发出的,有的是操作

    编辑框发出的,如:书219页表5.19所示。

    例:根据用户从组合框中选择的填充样式,在对话框中绘制一个矩形区域,      如:书220页图5.29所示。由于对话框是一个窗口,所以用户可以调用GDI绘图函数在对话框内的任何部分进行绘图,至于绘图时需要的一些技巧见书 220页的说明:(1)、(2)、(3)。本例操作步骤如下:

    (1)打开前面创建的基于对话框应用程序“按钮的使用”。

    (2)向应用程序中添加一个对话框资源IDD_COMBO,标题为“使用组合框”,如:书220页图5.30所示。并用ClassWizard为新加的对话框定义类,名为:CComboDlg。

    (3)、(4)、(5)按书220--221页整个操作步骤做完。

    书(6)

    int nIndex=m_Pattern.GetCurSel();//获得当前选项的索引

              if(nIndex!=CB_ERR)

              {  m_nDrawPattern=m_Pattern.GetItemData(nIndex);//获得与当

                                                    //前选项相关联的数据

                 Invalidate();//强制系统调用OnPaint函数重新绘制

              }

    书(7)

    CWnd *pWnd=GetDlgItem(IDC_DRAW);//获得控件IDC_DRAW的窗口指针

              pWnd->UpdateWindow();//避免系统自动重绘

              CDC *pDC=pWnd->GetDC();//获得所需要的绘图设备环境

              CBrush drawBrush;//定义一个画刷

              drawBrush.CreateHatchBrush(m_nDrawPattern,RGB(0,0,0));//创建画刷

              CBrush *pOldBrush=pDC->SelectObject(&drawBrush);//将画刷选入当前设

    //备环境中

              CRect rcClient;//定义一个CRect变量

              pWnd->GetClientRect(rcClient);//获得窗口客户区大小

              pDC->Rectangle(rcClient);//用当前画刷绘制一个矩形区域

              pDC->SelectObject(pOldBrush);//恢复设备环境原来的画刷设置

    书(8)

    CDialog::OnInitDialog();

              CString str[6]={"水平线","竖直线","向下斜线","向上斜线",

                  "十字线","交叉线"};

              int nIndex;

              for(int i=0;i<6;i++)

              {  nIndex=m_Pattern.AddString(str[i]);

                 m_Pattern.SetItemData(nIndex,i);

              }

              m_Pattern.SetCurSel(0);

              m_nDrawPattern=0;

    书(9)

    CComboDlg dlg;

       dlg.DoModal();

    书(10)#include “ComboDlg.h”

    -------------------------

    6、滚动条和进展条

        进展条通常用来说明操作的进度,并在操作完成时从左到右充填进展条,

    这个过程可以让用户看到还有多少任务要完成。滚动条也是一种控件,它能

    完成诸如定位之类的操作。

    1)  滚动条

    滚动条是一个独立的窗口,虽然它具有直接的输入焦点,但却不能自动地滚动窗口的内容,因此,它的使用受到一定的限制。根据滚动条的走向,可分为垂直滚动条和水平滚动条两种类型。

    (1)滚动条的基本操作:滚动条的基本操作一般包括设置和获取滚动条的范围及滚动块的相应位置。由于滚动条控件的默认滚动范围是0---0,因此如果不设置滚动条范围,那麽滚动条中的滚动块就滚动不起来。

    在MFC的CScrollBar类中,函数SetScrollRange是用来设置滚动条范围的,其原型如下:

       SetScrollRange(int nMinPos,int nMaxPos,BOOL bRedraw=TRUE);

    其中:nMinPos和nMaxPos表示滚动位置的最小值和最大值。bRedraw

    为重画标志,当为TRUE时,滚动条被重画。在CScrollBar类中,设置滚

    动块位置操作是由SetScrollPos函数来完成的,其原型如下:

      int SetScrollPos(int nPos,BOOL bRedraw=TRUE);

    其中:nPos表示滚动块的新位置,它必须是在滚动范围之内。获取滚动条

    的当前范围以及当前滚动位置的两个函数原型是:

         void GetScrollRange(LPINT lpMinPos,LPINT lpMaxPos);

         int GetScrollPos();

    需要说明的是:在CScorllBar类的成员函数中,还可以用SetScrollInfo

    和GetScrollInfo来代替上面提到的4个函数。与前面的函数相比,使用

    SetScrollInfo函数还能使滚动块的大小随内容的多少而改变,并且这两个函

    数都使用下面的SCROLLINFO结构:见书223页这个结构体的描述:

    (2)当用户对滚动条进行操作时,滚动条就会向父窗口发送WM_HSCROLL(水平滚动 条)或WM_VSCROLL(垂直滚动条)消息。这些消息是通过ClassWziard在其对话框(滚动条的父窗口)中进行映射的,并产生相应的消息映射 函数OnHScroll和OnVScroll,这两个函数原型如下:

    afx_msg void OnHScroll(UINT nSBCode,UINT nPos,CScrollBar *pScrollBar);

    afx_msg void OnVScroll(UINT nSBCode,UINT nPos,CScrollBar *pScrollBar);

    其中:nPos表示滚动块的当前位置,pScrollBar表示由滚动条控件的指针,

    nSBCode表示滚动条的通知消息。见书223页表5.21所示。

    2)  进展条

    进展条是一个如书224页图5.33所示的控件。除了能表示一个过程的进展情况外,使用进展条还可以表明温度、水平面或类似的测量值。进展条的设置范围、当前位置、设置增量等,都是通过:CProgressCtrl类的成员函数实现的。

      int SetPos(int nPos);

      int GetPos();

    这两个函数分别用来设置和获取进展条的当前位置,需要说明的是,这个当前位置是指在SetRange中的上限和下限范围之间的位置。

     voit SetRange(short nLower,short nUpper);

     void SetRange32(int nLower,int nUpper);

    void GetRange(int &nLower,int &nUpper);

    它们分别用于设置和获取进展条范围的上限和下限值,一旦设置后,还会重画

    此进展条来反映新的范围,成员函数SetRange32为进展条设置32位的范围。

    参数:nLower和nUpper分别表示范围的下限(默认值为0)和上限(默认值

    为100)。

       int SetStep(int nStep);

    该函数用于设置进展条的步长并返回原来的步长,默认步长为10。

       int SetpIt();

    该函数将当前位置向前移动一个步长并重画进展条,以反映新的位置,函数

    返回进展条上一次的位置。

    7、  旋转按钮控件和滑动条

    “旋转按钮控件”(也称为上下控件)是一对箭头按钮,用户单击它们来增加

    或减小某个值,比如一个滚动位置或显示在相应控件中的一个数字。

    一个“滑动条控件”(也称为跟踪器)是一个包含一个滑动块和可选的刻度线的窗口。这两个控件的说明见书225页。

    1)旋转按钮控件

    (1)旋转按钮控件常用的风格

    见书225页图5.36所示,其中各项含义见书225页表5.22所示

    (2)旋转按钮控件的基本操作

    MFC的CSpinButtonCtrl类提供了旋转按钮控件的各种操作函数,使用

    它们用户可以进行基数、范围、位置设置和获取等基本操作。成员函数:

    SetBase是用于设置基数的,这个基数值决定了伙伴窗口显示的数字是十进

    制的还是十六进制的。函数原型如下:

    int SetBase(int nBase);// 是用于设置基数的

    其中参数nBase表示控件的新的基数。与此函数相对应的成员函数GetBase

    是获取旋转按钮控件的基数。

    int SetPos(int nPos);//设置旋转按钮控件的当前位置

    int SetRange(int nLower,int nUpper);// 设置旋转按钮控件的当前范围。

    参数:nPos表示控件的新位置,它必须在控件的上限和下限指定的范围之内。

          nLower和nUpper表示控件的上限和下限,任何一个界限值都不能大于Ox7fff或小于-Ox7fff.

    函数GetPos()和GetRange()分别用于获取旋转按钮控件的当前位置和范围成员函数:SetAccel()和GetAccel()分别用于设置和获取旋转按钮控件的加速度。其中函数SetAccel的原型如下:

         BOOL SetAccel(int nAccel,UDACCEL *pAccel);

    参数:nAccel表示由pAccel指定的UDACCEL结构的数目,pAccel指向一

    个UDACCEL结构数组的指针,该数组包含加速信息。见书226页说明。

    (3)旋转按钮的通知消息是:UDN_DELTAPOS它是当控件的当前数值将要改

    变时向其父窗口发送的。

    3)滑动条

        滑动条控件是由滑动块和可选的刻度线组成的。当用户用鼠标或方向键移动滑动块时,该控件发送通知消息来表明这些改变。见书226—227页说明。

    (1)滑动条的风格

    见书227页图5.38所示,其中各项含义见书227页表5.23所示。

    (2)滑动条的基本操作

    MFC的CSliderCtrl类提供了滑动条控件的各种操作函数,这其中包括范围、位置设置和获取等。

    void SetPos(int nPos);//设置滑动条的位置

    void SetRange(int nMin,int nMax,BOOL bRedraw=FALSE);// 设置滑动条的范围

    void GetPos();//获取滑动条的位置

    void GetRange();//获取滑动条的范围

    BOOL SetTic(int nTic);//设置滑动条控件中的一个刻度线的位置

    void SetTicFreq(int nFreq);//设置显示在滑动条中的刻度线的疏密程度

    void ClearTics(BOOL bRedraw=FALSE);

    void SetSelection(int nMin,int nMax);

    参数:nPos表示新的滑动条位置

          bMin和nMax表示滑动条的最小和最大位置

          bRedraw表示重画标志,为TRUE时,滑动条被重画。

          nTic表示刻度线的位置

          nFreq表示刻度线的疏密程度,如果参数设置为2,则在滑动条的范围中,

               每两个增量显示一个刻度线,要使这个函数有效,必须在属性对话

    框中选中Auto ticks项。

          bRedraw表示重画标志,若该参数为TRUE,则在选择被清除后,重画滑

    动条

          nMin和nMax表示滑动条的开始和结束位置

    (3)    滑动条的通知消息

              滑动条的通知消息代码常见的有:

    TB_BOTTOM,TB_ENDTRACK,

    TB_LINE_DOWN,TB_LINEUP,TB_PAGEDOWN,TB_PAGEUP,

    TB_THUMBPOSITION,TB_THUMBTRACK和TB_TOP等。这些消息代码都来自于WM_HSCROLL或WM_VSCROLL消息,其具体含义同滚动条。

    例:用滚动条、滑动条和旋转按钮控件分别来调整RGB的三种颜色分量:R

    (红色分量)、G(绿色分量)和B(蓝色分量),并根据用户指定的颜色填充

    一个矩形区域,如书228页图5.39所示:

    操作步骤如下:

    (1)打开前面创建的基于对话框的应用程序“按钮的使用”。

    (2)、(3)、(4)、(注意:在这可先将第(9)、(10)步作出,因第5步要用m_bEditOK

    和Draw)按书228---231页做完

    书(5)

    if(!m_bEditOK) return;

           UpdateData();

           m_Scroll.SetScrollPos(m_RValue);

           m_Slider.SetPos(m_GValue);

           Draw();

    书(6)

    CWnd *pWnd=GetDlgItem(IDC_DRAW);

           pWnd->UpdateWindow();

           Draw();

    书(7)

           //设置滚动条和滑动条的范围和当前位置

           m_Scroll.SetScrollRange(0,255);

           m_Scroll.SetScrollPos(m_RValue);

           m_Slider.SetRange(0,255);

           m_Slider.SetPos(m_GValue);

           //设置旋转按钮的范围

           m_Spin.SetRange(0,255);

           UpdateData(FALSE);//将数据传给控件

           m_bEditOK=TRUE;

    书(8)

    int nID=pScrollBar->GetDlgCtrlID();

           if(nID==IDC_SLIDER1)//是滑动条产生水平滚动消息

           {  m_GValue=m_Slider.GetPos();  }//获得滑动条当前的位置

           if(nID==IDC_SCROLLBAR1)//是滚动条产生水平滚动消息

           {

                  switch(nSBCode)

                  { 

                case SB_LINELEFT:m_RValue--;//单击滚动条左边箭头

                               break;

                    case SB_LINERIGHT:m_RValue++;//单击滚动条右边箭头

                                        break;

                    case SB_PAGELEFT:m_RValue-=10;

                                        break;

                    case SB_PAGERIGHT:m_RValue+=10;

                                        break;

              case SB_THUMBTRACK:m_RValue=nPos;

                                        break;

                  }

                  if(m_RValue<0)  m_RValue=0;

                  if(m_RValue>255)m_RValue=255;

                  m_Scroll.SetScrollPos(m_RValue);

           }

           UpdateData(FALSE);

           Draw();

    书(9)第4步将Draw()函数加上了,在这里添加代码:

           CWnd *pWnd=GetDlgItem(IDC_DRAW);

           CDC *pDC=pWnd->GetDC();//获得窗口当前的设备环境指针

           CBrush drawBrush;//定义画刷变量

           drawBrush.CreateSolidBrush(RGB(m_RValue,m_GValue,m_BValue));

           //以上创建一个填充颜色画刷,RGB是一个颜色宏,用来将指定的红、

           //绿、蓝三种颜色分量转换成一个32位的RGB颜色值

           CBrush *pOldBrush=pDC->SelectObject(&drawBrush);

           CRect rcClient;

           pWnd->GetClientRect(rcClient);

           pDC->Rectangle(rcClient);

           pDC->SelectObject(pOldBrush);

    书(10)第4步已作了,别忘了在CScrollDlg.cpp的构造函数例将m_bEditOK

            设为:FALSE

    书(11)还是定位到原始程序的OnButton1函数处,改MessageBox("4")代码为:

    CScrollDlg dlg;

          dlg.DoModal();

    书(12)在按钮的使用Dlg.cpp的开头处加:#include “ScrollDlg.h”

    书(13)编译运行

    附:设置口令(附加举例)

    (1)建一个多文档应用程序(选MultipleDocuments后,直接按Finish)

    (2)插入一个对话框ResourseViewà点中Dialog单击右键àInsertDialogà出现对话框

    (3)将这个对话框的ID改为IDD_PASSWORD_DIALOG,名字处写:口令

    (4)将OK和CANCEL改为确定和取消(并拖到最下边)

    (5)拖一个静态文本StaticText写请输入口令,并拖一个编辑控件Edit Box

         改ID为:IDC_PASSWORD_EDIT,在Styles中选PassWord属性。

    (6)再加一个静态文本StaticText写:口令为0----9999之间的一个整数。

      在风格ExtendedStyles中置Client edge、Static edge、Modal frame。

    (7)可测试一下对话框布局

    (8)创建对话框类:

          双击对话框任意位置àOKà出见类表你写类名为:CPassWordDialog

    (9)加成员变量:

      ViewàClassWizardàMemberVariablesà(classname中是CPassWordDialog

    点中IDC_PASSWORD_EDITàAddVariableà名字写:m_password,TYPE

    类型写:int àOK后回来处,在下面的Minimumvalue处写:0

    Maximumvalue处写:9999。

    (10)在应用程序的InitInstance()函数中加代码:

         Bool CMyApp::InitInstance()

          {  -----

             CPassWordDialog  CDlg;

             if(CDlg.DoModal()==IDOK)  //用户按了确定按钮

                {

                  if(CDlg.m_password!=1949) //口令为整数1949

                  {

                    AfxMessageBox(“口令错误,确定后将退出程序”);

                    return FALSE;

                   }

                 }

              else  //如果按下取消按钮程序也结束

                 return FALSE;

              return TRUE;

            }

    (11)在文件“设置口令.cpp“头部加:

           #include “PassWordDialog.h”

    (12)编译、运行

     

    ---------------------

    8、图像列表和标签控件

        “图像列表”是一系列相同大小的图像的集合,每一个图像提供一个以0为基数的索引号。而一个“标签控件”类似于一个笔记本中的分割器,或一个文件上的标签。

      1)图象列表控件  

        图象列表控件常用来有效地管理多个位图和图标。在MFC中,图象列表控件是使用CImageList类来创建、显示或管理图象的。

    (1)创建一个图象列表首先要声明一个CImageList对象,然后调用Create

    函数。由于Create函数的重载很多,故这里只给出最常用的一个原型:

    BOOL Create(int cx,int cy,UINT nFlags,int nInitial,int nGrow);

    参数:cx和cy用于指定图象的像素大小;

    nFlags表示要创建的图象类型,一般取其ILC_COLOR和ILC_MASK(指定屏蔽图像)的组合,默认的ILC_COLOR为ILC_COLOR4(16色),ILC_COLOR8(256色)ILC_COLOR16(16位色)等。

    nInitial用于指定图像列表中最初的图像数目

    nGrow表示当图像列表的大小发生改变时,图像可以增加的数目。

    (2)图像列表常见的基本操作:

       int Add(CBitmap *pbmImage,CBitmap *pbmMask);

       int Add(CBitmap *pbmImage,COLORREF crMask);

       int Add(HICON hIcon);

    如上函数用来向一个图像列表添加一个图标或多个位图,成功时返回第一个新图像的索引号,否则返回-1。

         参数:pbmImage表示包含图像的位图指针

               pbmMask表示包含屏蔽的位图指针

               crMask表示屏蔽色

               hIcon表示图标句柄

     BOOL Remove(int nImage);//用于从图像列表中删除一个由nImage指定的图像

     BOOL Drew(CDC *pdc,int nImage,POINT pt,UINT nStyle);//用于在由pt指定的位//置处绘制一个图像

    参数:pdc表示绘制的设备环境指针

    nImage表示要绘制的图像的索引号

    nStyle用于指定绘制图像时采用的方式

    HICON ExtractIcon(int nImage);//用于将nImage指定的图像扩展为图标

    COLORREF SetBkColor(COLORREF cr);//设置图像列表的背景色,COLORREF是一种专门用于定义颜色的数据类型。

    2)标签控件

    通过使用标签控件,应用程序可以将一个窗口或对话框的相同区域定义为多个页面。每一页包含一 套信息或一组控件。当用户选择了相应的标签时,应用程序就会显示相应的信息或控件。一种特殊类型的标签控件把标签显示得象按钮一样。单击一个按钮将立即执 行一条命令而不是显示一个页,这也是标签控件与属性页的最根本区别。

    (1)标签控件的风格

    见书232页图5.40所示,书233页表5.26列出该属性对话框的各项含义。

    (2)标签控件的基本操作

     MFC的CTabCtrl类提供了标签控件的各种操作函数。

      CSize SetItemSize(CSize size);//设置某个项的宽度和高度

      void SetPadding(CSize size);//设置标签和标签周围的间隔

      int SetMinTabWidth(int cx);//设置标签项的最小宽度

     BOOL GetItemRect(int nItem,LPRECT lpRect)const;//获取标签的边界大小

     BOOL InsertItem(int nItem,LPCTSTR lpszItem);//

     BOOL InsertItem(int nItem,LPCTSTR lpszItem,int nImage);

     //如上函数表示在一个标签控件中插入某一标签项

     BOOL DeleteItem(int nItem);// 表示在一个标签控件中删除某一标签项

     BOOL DeleteAllItem();//表示在一个标签控件中删除所有标签项

    参数:nItem用于指定一个标签索引(0表示第一个标签)

          lpszItem用于指定标签文本

          nImage用于指定标签的图标在图像列表中的图像索引。

    说明:在标签控件中使用图像列表时,必须调用:

        CTabCtrl::SetImageList()函数来指定一个已创建的图像列表,函数

    原型如下:

     CImageList *SetImageList(CImageList *pImageList);

                 参数:pImageList用于表示一个图像列表指针

            int SetCurSel(int nItem);//设置当前选择的标签

            int GetCurSel()const;//获取当前选择的标签

          参数:nItem用于表示当前选择项的索引

            void DeselectAll(BOOL fExcludeFocus);//重新设置一个标签控件中的

    //项,清除任何被按下的项

            BOOL HighlightItem(int idItem,BOOL fHighlight=TRUE);//使某一个标签

    //项处于高亮状态

            参数:fExcludeFocus用于指定一个重排标志

                  idItem用于指定一个标签的索引

                  fHighlight指定要设置的高亮状态

     (3)标签控件的通知消息。

              常见的标签通知消息有:

    TCN_KEYDOWN  //表示用户按下某键

    TCN_SELCHANGE//表示标签选项已被改变

    和TCN_SELCHANGING//当前标签选项将要改变

    例: 使用无模式对话框来构造某个标签页面,一开始将所有的标签页面创建好,然后根据所选择的当前标签选项,决定哪个页面需要显示,哪个页面需要隐藏和禁用。

       操作步骤如下:

    (1)用MFC AppWizard(exe)创建一个基于对话框的应用程序“图像列表和标签控件”。按书234---237页做完。

    (2)、(3)、(4)、(5)、(6)、(7)、(8)、(9)注意:第9步的工作已经在第8

    步自动加完了,这里就不做什么了。

    书(10)切到ClassView右键对准CScrollDlg击出对话框后,加函数时要这样加:

         类型:void  函数名:SetDlgState(CWnd *pWnd,BOOL bShow)

         类型:void  函数名:DoTab(int nTab)

    void CMyDlg::SetDlgState(CWnd *pWnd,BOOL bShow)  

    {  pWnd->EnableWindow(bShow);

       if(bShow)

       {  pWnd->ShowWindow(SW_SHOW);

          pWnd->CenterWindow();//居中显示

       }

       else pWnd->ShowWindow(SW_HIDE);

    }

    void CMyDlg::DoTab(int nTab)

    {  if(nTab>2) nTab=2;//确定nTab值不能超过范围

       if(nTab<0) nTab=0;

       BOOL bTab[3];

       bTab[0]=bTab[1]=bTab[2]=FALSE;

       bTab[nTab]=TRUE;

       //切换对话框的显示和隐藏

       SetDlgState(m_pTab1Dlg,bTab[0]);

       SetDlgState(m_pTab2Dlg,bTab[1]);

       SetDlgState(m_pTab3Dlg,bTab[2]);

    }

    书(11)     int nSelect=m_Tab.GetCurSel();

                  if(nSelect>=0)

                      DoTab(nSelect);

    书(12)……

    书(13)

    m_pTab1Dlg=new CTab1Dlg();//为无模式对话框分配空间

        m_pTab2Dlg=new CTab2Dlg();

        m_pTab3Dlg=new CTab3Dlg();

           //创建无模式对话框,指定标签控件为无模式对话框的父窗口

           m_pTab1Dlg->Create(IDD_DIALOG1,&m_Tab);

        m_pTab2Dlg->Create(IDD_DIALOG2,&m_Tab);

        m_pTab3Dlg->Create(IDD_DIALOG3,&m_Tab);

           m_ImageList.Create(16,16,ILC_COLOR|ILC_MASK,3,0);//创建图象列表

           m_ImageList.Add(AfxGetApp()->LoadIcon(IDI_ICON1));//从图标加到图象列

    //表中

        m_ImageList.Add(AfxGetApp()->LoadIcon(IDI_ICON2));

        m_ImageList.Add(AfxGetApp()->LoadIcon(IDI_ICON3));

           m_Tab.SetImageList(&m_ImageList);//设置TAB控件所使用的图象列表

           m_Tab.InsertItem(0,"基本信息",0);

        m_Tab.InsertItem(1,"成绩",1);

        m_Tab.InsertItem(2,"备注",2);

           m_Tab.SetMinTabWidth(80);//设置标签项的最小宽度

           m_Tab.SetPadding(CSize(12,3));//设置标签项和图标周围的间隔

           m_Tab.SetCurSel(0);

           DoTab(0);

    书(14)if(m_pTab1Dlg) delete m_pTab1Dlg;

            if(m_pTab2Dlg) delete m_pTab2Dlg;

            if(m_pTab3Dlg) delete m_pTab3Dlg;

    书(15)CheckRadioButton(IDC_RADIO_MAN_1,IDC_RADIO_WOMAN_1,

                   IDC_RADIO_MAN_1);

    书(16)编译运行

    9、通用对话框和消息对话框

       A)通用对话框

         Windows提供了一组标准用户对话框,它们都具有相应的MFC库中的类

    来支持。所有通用对话框类都是从一个公共的基类CCommonDialog派生而来

    的。见书238页表5.28列出了这些通用对话框。

        这些对话框都有一个共同特点:它们都从用户获取信息,但并不对信息进

    行处理。例如:文件对话框可以帮助用户选择一个用于打开的文件,但它实际

    上只是给程序提供了一个文件路径名,用户的程序必须调用相应的成员函数才

    能打开文件。类似地,字体对话框只是填充一个描述字体的逻辑结构,但它并

    不创建字体。

    *** 例如:制作一个利用CFileDialog(文件对话框)类,弹出“打开”对话框的应用程序

    在打开一个文本文件或是位图文件时,往往要使用到CFileDialog类,CFileDilog类可以创建一个“打开”对话框,再通过调用其成员函数完成相应的功能,这里举出一些常用的函数。

            CFileDialog(BOOL bOpenFileDialog,LPCTSTR lpszDefExt=NULL,

              LPCTSTR lpszFileName=NULL,DWORD dwFlags=

    OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT,

    LPCTSTR lpszFilter=NULL,CWnd *pParentWnd=NULL);

    //该函数用于创建CFileDialog类对象,如:

    CFileDialog dlg;

             Virtual int DoModal();//用于显示对话框,如:dlg.DoModal();

             CString GetPathName()const;//用于获取文件名和文件路径名

             CString GetFileName()const;//用于获取文件名

    操作步骤如下:

    1)创建一个单文档应用程序,名为:通用对话框

    2)创建弹出“打开”对话框的菜单命令

    MenuàIDR_MAINFRAMEà双击空白菜单à名处写:打开,ID处写:

    ID_FILEOPEN

    3)将ID_FILEOPEN连到view.cpp中去

    4)添加代码:

    (1)在view.h的public里加:

    CString FilePathname;//用于存储将要打开文件的路径名

    CString FileName; //用于存储文件名

    (2)完成菜单命令函数的代码:

       void CMyView::OnFileopen()

    {

       CFileDialog dlg(TRUE,_T(“TXT”),_T(“*.TXT”),

         OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT,

         _T(“文本文件(*.TXT)|*.TXT|”));//设置打开文件为”.TXT”

       if(IDOK==dlg.DoModal())

        {

          FilePathname.Format(“%s %s”,”filepath:”,dlg.GetPathName());

          FileName.Format(“%s %s”,”filename:”,dlg.GetFileName());

    }

            Invalidate();

    }

     (3)在OnDraw()函数中添加如下代码:

    -----

     pDCàTextOut(0,0,FileName);//显示文件名

     pDCàTextOut(0,20,FilePathname);//显示路径名

    5)运行后,点刚建立的菜单,出现对话框,你随便写一个已有的文本文件(*.txt)文件名,便在屏幕上显示处文件名、路径名。

    B)消息对话框

         消息对话框是最简单的一类对话框,它只是用于显示信息。在Visual C++6.0

    的MFC类库中就提供相应的函数实现这样的功能,使用时,只要在用户程序

    任何地方调用它们即可。函数原型如下:

    int AfxMessageBox(LPCTSTR lpszText,UINT nType=MB_OK,UINT nIDHelp=0);

     //是全程函数,可以用在任何地方

    int MessageBox(LPCTSTR lpszText,LPCTSTR lpszCaption=NULL,UINT nType=

         MB_OK); //只能用于控件、对话框、窗口等一些窗口类中。

    参数:IDOK表示用户单击[OK]按钮

          lpszText表示在消息对话框中显示的字符串文本

          lpszCaption表示消息对话框的标题,为NULL时使用默认标题

          nIDHelp表示消息的上下文帮助ID号

          nType表示消息对话框的图标类型以及所包含的按钮类型,这些类型是

    用MFC预先定义的一些标识符来指定的,见书240页表5.29和5.30所示。

    例:消息对话框的使用

    (1)建一个基于对话框的应用程序

    (2)拖入对话框中两个按钮控件:BUTTON1 和BUTTON2

    (3)将这两个控件的映射消息分别加到:CMyDlg.cpp中

         ViewàClassWizard àClassnameàCMyDlgàIDC_BUTT

    本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/z443538426/archive/2008/01/12/2040082.aspx

    展开全文
  • 这四种对话框都使用Alert控件表达,并通过对话框类型加以区分,例如AlertType.INFORMATION表示消息对话框,AlertType.WARNIN表示警告对话框,AlertType.ERROR表示错误对话框,AlertType.CONFIRMATION表示确认...

    JavaFX的对话框主要分为提示对话框和文件对话框两类,其中提示对话框又分作消息对话框、警告对话框、错误对话框、确认对话框四种。这四种对话框都使用Alert控件表达,并通过对话框类型加以区分,例如AlertType.INFORMATION表示消息对话框,AlertType.WARNIN表示警告对话框,AlertType.ERROR表示错误对话框,AlertType.CONFIRMATION表示确认对话框。另外,Alert工具还提供了下列方法来操作对话框:
    setTitle:设置对话框的标题。
    setHeaderText:设置对话框的头部文本。
    setContentText:设置对话框的内容文本。
    show:显示对话框。
    showAndWait:显示对话框,并等待按钮返回。该方法的返回类型是Optional,它用来描述确认对话框选择的是确定按钮还是取消按钮。

    接下来分别列举几种提示对话框的实现例子,首先是消息对话框,它的调用代码示例如下:

    Button btn1 = new Button("消息对话框"); // 创建一个按钮
    btn1.setOnAction(new EventHandler<ActionEvent>() { // 设置按钮的单击事件
    	@Override
    	public void handle(ActionEvent arg0) { // 处理单击事件
    		Alert alert = new Alert(Alert.AlertType.INFORMATION); // 创建一个消息对话框
    		alert.setHeaderText("今日天气"); // 设置对话框的头部文本
    		// 设置对话框的内容文本
    		alert.setContentText("今天白天晴转多云,北转南风2、3间4级,最高气温28℃;夜间多云转阴,南风2级左右,最低气温16℃。");
    		alert.show(); // 显示对话框
    	}
    });
    flowPane.getChildren().add(btn1); // 往流式窗格上添加按钮
    

    运行包含以上代码的测试程序,单击按钮后弹出的对话框如下图所示,可见消息对话框的提示图标是个内嵌感叹号的圆圈。
    在这里插入图片描述
    其次是警告对话框,它的调用代码示例如下:

    Button btn2 = new Button("警告对话框"); // 创建一个按钮
    btn2.setOnAction(new EventHandler<ActionEvent>() { // 设置按钮的单击事件
    	@Override
    	public void handle(ActionEvent arg0) { // 处理单击事件
    		Alert alert = new Alert(Alert.AlertType.WARNING); // 创建一个警告对话框
    		alert.setHeaderText("编译警告"); // 设置对话框的头部文本
    		// 设置对话框的内容文本
    		alert.setContentText("您在本代码的第60行未初始化变量,可能导致空指针异常。");
    		alert.show(); // 显示对话框
    	}
    });
    flowPane.getChildren().add(btn2); // 往流式窗格上添加按钮
    

    运行包含以上代码的测试程序,单击按钮后弹出的对话框如下图所示,可见警告对话框的提示图标是个内嵌感叹号的三角框。
    在这里插入图片描述
    再次是错误对话框,它的调用代码示例如下:

    Button btn3 = new Button("错误对话框"); // 创建一个按钮
    btn3.setOnAction(new EventHandler<ActionEvent>() { // 设置按钮的单击事件
    	@Override
    	public void handle(ActionEvent arg0) { // 处理单击事件
    		Alert alert = new Alert(Alert.AlertType.ERROR); // 创建一个错误对话框
    		alert.setHeaderText("致命错误"); // 设置对话框的头部文本
    		// 设置对话框的内容文本
    		alert.setContentText("系统即将关机,请赶紧保存文件。");
    		alert.show(); // 显示对话框
    	}
    });
    flowPane.getChildren().add(btn3); // 往流式窗格上添加按钮
    

    运行包含以上代码的测试程序,单击按钮后弹出的对话框如下图所示,可见错误对话框的提示图标是个内嵌叉号的圆角方框。
    在这里插入图片描述
    最后是确认对话框,它使用showAndWait方法替换了show方法,具体的调用代码示例如下:

    Button btn4 = new Button("确认对话框"); // 创建一个按钮
    btn4.setOnAction(new EventHandler<ActionEvent>() { // 设置按钮的单击事件
    	@Override
    	public void handle(ActionEvent arg0) { // 处理单击事件
    		Alert alert = new Alert(Alert.AlertType.CONFIRMATION); // 创建一个确认对话框
    		alert.setHeaderText("温馨提示"); // 设置对话框的头部文本
    		// 设置对话框的内容文本
    		alert.setContentText("尊敬的用户,你真的要卸载我吗?");
    		// 显示对话框,并等待按钮返回
    		Optional<ButtonType> buttonType = alert.showAndWait();
    		// 判断返回的按钮类型是确定还是取消,再据此分别进一步处理
    		if (buttonType.get().getButtonData().equals(ButtonBar.ButtonData.OK_DONE)) { // 单击了确定按钮OK_DONE
    			label.setText("您选择了“确定”按钮。虽然依依不舍,但是只能离开了");
    		} else { // 单击了取消按钮CANCEL_CLOSE
    			label.setText("您选择了“取消”按钮。让我再陪你三百六十五个日夜");
    		}
    	}
    });
    flowPane.getChildren().add(btn4); // 往流式窗格上添加按钮
    

    运行包含以上代码的测试程序,单击按钮后弹出的对话框如下图所示,可见确认对话框的提示图标是个内嵌问号的圆圈。
    在这里插入图片描述
    各自单击确认对话框上的确定按钮和取消按钮,对话框消失后的窗口界面分别如下面两张图所示。
    在这里插入图片描述在这里插入图片描述
    除了提示对话框这一大类,还有文件对话框FileChooser,它又细分为文件打开对话框与文件保存对话框两种,FileChooser的常见方法说明如下:
    setTitle:设置文件对话框的标题。
    setInitialDirectory:设置文件对话框的初始目录。
    getExtensionFilters:获得文件对话框的扩展过滤器。调用过滤器的add方法或者addAll方法可以添加新的文件类型过滤器。
    showOpenDialog:显示文件打开对话框。该方法返回一个选中的文件对象。
    showOpenMultipleDialog:显示文件打开对话框,且该对话框支持同时选择多个文件。该方法返回一个选中的文件清单。
    showSaveDialog:显示文件保存对话框。该方法返回一个待保存的文件对象,文件可能存在也可能不存在。

    接着看个文件对话框的运用场景,现在准备打开某张图片,以便对该图片进行加工。鉴于图片文件包含jpg、gif、bmp、png等多种格式,在创建文件类型过滤器时得添加主要的几种图片扩展名,下面便是打开图片的对话框调用代码例子:

    Button btn1 = new Button("文件打开对话框"); // 创建一个按钮
    btn1.setOnAction(new EventHandler<ActionEvent>() { // 设置按钮的单击事件
    	@Override
    	public void handle(ActionEvent arg0) { // 处理单击事件
    		FileChooser chooser = new FileChooser(); // 创建一个文件对话框
    		chooser.setTitle("打开文件"); // 设置文件对话框的标题
    		chooser.setInitialDirectory(new File("E:\\")); // 设置文件对话框的初始目录
    		// 给文件对话框添加多个文件类型的过滤器
    		chooser.getExtensionFilters().addAll(
    				new FileChooser.ExtensionFilter("所有文件", "*.*"),
    				new FileChooser.ExtensionFilter("所有图片", "*.jpg", "*.gif", "*.bmp", "*.png"));
    		// 显示文件打开对话框,且该对话框支持同时选择多个文件
    		File file = chooser.showOpenDialog(stage); // 显示文件打开对话框
    		if (file == null) { // 文件对象为空,表示没有选择任何文件
    			label.setText("未选择任何文件");
    		} else { // 文件对象非空,表示选择了某个文件
    			label.setText("准备打开的文件路径是:"+file.getAbsolutePath());
    		}
    	}
    });
    flowPane.getChildren().add(btn1); // 往流式窗格上添加按钮
    

    运行包含以上代码的测试程序,单击按钮后弹出的对话框如下图所示。
    在这里插入图片描述
    在文件对话框中选择某个目录下的某个图片文件,此时对话框界面如下图所示。
    在这里插入图片描述
    然后单击确定按钮,回到主程序的窗口界面如下图所示,可见主程序成功获取到了该文件的完整路径。
    在这里插入图片描述
    再来验证文件保存对话框的使用过程,这次期望将一段文字保存到文本文件,那么具体实现的对话框代码如下所示:

    Button btn2 = new Button("文件保存对话框"); // 创建一个按钮
    btn2.setOnAction(new EventHandler<ActionEvent>() { // 设置按钮的单击事件
    	@Override
    	public void handle(ActionEvent arg0) { // 处理单击事件
    		FileChooser chooser = new FileChooser(); // 创建一个文件对话框
    		chooser.setTitle("保存文件"); // 设置文件对话框的标题
    		chooser.setInitialDirectory(new File("E:\\")); // 设置文件对话框的初始目录
    		// 创建一个文件类型过滤器
    		FileChooser.ExtensionFilter filter = new FileChooser.ExtensionFilter("文本文件(*.txt)", "*.txt");
    		// 给文件对话框添加文件类型过滤器
    		chooser.getExtensionFilters().add(filter);
    		File file = chooser.showSaveDialog(stage); // 显示文件保存对话框
    		if (file == null) { // 文件对象为空,表示没有选择任何文件
    			label.setText("未选择任何文件");
    		} else { // 文件对象非空,表示选择了某个文件
    			label.setText("准备保存的文件路径是:"+file.getAbsolutePath());
    		}
    	}
    });
    flowPane.getChildren().add(btn2); // 往流式窗格上添加按钮
    

    运行包含以上代码的测试程序,单击按钮后弹出的对话框如下图所示。
    在这里插入图片描述
    在文件对话框中进入到指定目录,并在对话框下方的文件名一栏填写待保存的文件名称,此时对话框界面如下图所示。
    在这里插入图片描述
    然后单击确定按钮,回到主程序的窗口界面如下图所示,可见主程序也获取到了该文件的完整路径。
    在这里插入图片描述

    更多Java技术文章参见《Java开发笔记(序)章节目录

    展开全文
  • 这类窗口通常称作对话框,依据消息交互的过程,可将对话框分为三类:消息对话框、确认对话框、输入对话框,分别介绍如下: 1、消息对话框 这类对话框仅仅向用户展示一段文本,告诉用户发生了什么事情。它起到了...
  • Win32汇编--使用资源--对话框--在对话框中使用子窗口控件(2)   2、子窗口控件的通用使用方法  由于子窗口控件实际上就是窗口,大部分窗口函数对它们都是适用的,如可以用EnableWindow在灰化和允许状态之间...
  • 对话框就是一个窗口,它不仅可以接收消息,而且可以被移动和关闭,甚至可以在客户区进行绘图。对话框相当于一个容器,在它上面能够放置各种各样的标准控件和扩展控件,使程序支持用户输入的手段更加丰富。 1.1 常用...
  • PyQt学习Ⅲ(事件,信号和对话框

    千次阅读 2018-07-28 12:18:13
     提供用于选择颜色值的对话框小部件。       这条线(this line)翻译为这条线让人很无语,应该翻译为这一行。 参考了http://www.kuqin.com/qtdocument/qframe.html#details     ...
  • 本节书摘来自异步社区《ANSYS 14热力学/电磁学/耦合...2.3 对话框及其组件 ANSYS 14热力学/电磁学/耦合场分析自学手册单击ANSYS菜单栏或主菜单,可以看到有4种不同的后缀符号,代表不同的含义。 表示可以打开级联...
  • Photoshop技巧集锦八十条

    千次阅读 2014-05-15 14:31:07
    拖动,画第二个选择区域(鼠标十字形旁出现一个乘号,表示重合的该区域将被保留)。  35. 在选择区域中删除正方形或圆形,首先增加任意一个选择区域,然后在该选择区域内,按Alt键拖动 矩形或椭圆的面罩工具。然后...
  • word方框中打对勾的方法

    千次阅读 2013-04-26 20:42:36
    最后,在弹出的“带圈字符”窗口中选中“增大圈号”,再选中“圈号”下的方框,点击“确定”按钮就可以看见框中打钩的效果了;word2010同word2007。 方法2:先输入√,选中它,点击word工具栏上的字符边框,就是...
  • [数字图像处理] 四.MFC对话框绘制灰度直方图 [数字图像处理] 五.MFC图像点运算之灰度线性变化、灰度非线性变化、阈值化和均衡化处理详解 [数字图像处理] 六.MFC空间几何变换之图像平移、镜像、旋转、缩放详解 ...
  • 单选框默认设置——MFC学习

    千次阅读 2013-09-11 19:26:21
    单选钮用来表示一系列的互斥选项,这些互斥项常常被分成若干个组,每组仅允许...最后选中的控件是对齐的基准,仔细观察,它周围的8个小方框是实心的,而其它被选控件周围的小方框是空心的。  在Layout菜单中选择M
  • 在“CSS规则对话框”中,我们可以通过类型、背景、区块、方框、边框、列表、定位和扩展项的设置,来美化我们的页面,当然啦,我们在定义某个CSS样式的时候,不需要对每一个项都进行设置,需要什么效果,选择相应的项...
  • Java快速入门-03-知识汇总篇(全)

    千次阅读 2018-10-13 21:53:23
    Java快速入门-03-知识汇总篇 前两篇介绍了JAVA入门的一系知识,本篇介绍一些比较偏的,说不定什么时候会用到,有用记得 Mark 一下 快键键 常用快捷键(熟记) 快捷键 快捷键作用 Ctrl +...
  • 关于Word在方框(□)中打勾的方法

    千次阅读 2010-12-01 09:11:00
    小方框可以通过插入表格来实现,也就是做一个最小的单行单列的表格就是小方框了,里面可以插入对勾和叉,勾或叉可以从“插入->特殊符号->数学符号”中选择。 方法6: 选择插入—符号—数学运算符子集里面的√...
  • 1.方框滤波:模糊图像 2.均值滤波:模糊图像 3.高斯滤波:信号的平滑处理,去除符合正太分布的噪声 非线性滤波 1.中值滤波:去除椒盐噪声 2.双边滤波:保边去噪 下面对滤波方法进行一一介绍: 方框滤波(box ...
  • 使用EA画类图

    万次阅读 多人点赞 2017-06-06 20:04:14
    本文介绍使用EnterpriseArchtect进行建模是,类图的使用方法。篇幅较长,请慢慢阅读。示例中使用的...启动EA后选择【New Project】菜单项,在出现的【New Project】对话框中输入文件名后按下【保存】按钮。
  • MFC中tree control的使用

    2021-05-08 19:19:21
    TVS_HASBUTTONS:显示带有"+"或"-"的小方框表示某项能否被展开或已展开 TVS_HASLINES:在父节点与子节点间连线以更清晰地显示树的结构 TVS_LINESATROOT:在根节点处连线 头文件定义: CTreeCtrl m_Tree; ...
  • Eclipse常用快捷键

    2017-11-13 21:45:14
    Eclipse常用快捷键 1几个最重要的快捷键 代码助手:Ctrl+Space(简体中文操作系统是...显示搜索对话框:Ctrl+H 快速Outline:Ctrl+O 打开资源:Ctrl+Shift+R 打开类型:Ctrl+Shift+T 显示重构菜单:Alt+Shift+
  • MFC单选控件Radio使用总结

    千次阅读 2015-05-12 16:32:33
    单选钮用来表示一系列的互斥选项,这些互斥项常常被分成若干个组,每组仅允许用户...最后选中的控件是对齐的基准,仔细观察,它周围的8个小方框是实心的,而其它被选控件周围的小方框是空心的。  在Layout菜单中选
  • 单选钮用来表示一系列的互斥选项,这些互斥项常常被分成若干个组,每组仅允许...最后选中的控件是对齐的基准,仔细观察,它周围的8个小方框是实心的,而其它被选控件周围的小方框是空心的。 在Layout菜单中选择Make
  • JS execCommand命令汇总及实例

    千次阅读 2018-02-11 11:33:01
    交互方式, 动态参数]) ,其中:sCommand为指令参数(如下例中的”2D-Position”),交互方式参数如果是true的话将显示对话框,如果为false的话,则不显示对话框(下例中的”false”即表示不显示对话框),
  • 原标题:【解惑】怎样去掉表格中打勾的方格?来源:Excel在工程中的应用一、问题描述今天在群里看到一个网友提问: 怎样去掉表格中的打勾的方格? 二、问题分析这个问题大家会发现,这些框框是不是不能选中?...
  • VC++中单选与复选按钮的使用单选钮用来表示一系列的互斥选项,这些互斥项常常被分成若干个组,每组仅允许...最后选中的控件是对齐的基准,仔细观察,它周围的8个小方框是实心的,而其它被选控件周围的小方框是空心的。
  • 单选钮用来表示一系列的互斥选项,这些互斥项常常被分成若干个组,每组仅允许用户...最后选中的控件是对齐的基准,仔细观察,它周围的8个小方框是实心的,而其它被选控件周围的小方框是空心的。 在Layout菜单中选择M

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 4,956
精华内容 1,982
关键字:

对话框的小方框表示