📄 08.2.3 向导的创建8.3 本章小结.txt
字号:
8.2.3 向导的创建
创建一个向导类型的对话框,
性表单的步骤来实现,但在调用属性表单对象的DoModal函数之前,应该先调用SetWizardMode这一函数。因此,我们在Prop工程的CPropView类的OnPropertysheet函数(上述例 8-4所示代码)中,在调用 DoModal函数之前添加下面这条语句:
propSheet.SetWizardMode();
Build井运行Prop程序,单击【属性表单】菜单命令,即可出现如图8.51所示的对话框,可以发现该对话框己经变成了一种向导的模式,它底部的按钮变成了:【上一步】、【下一步】和【取消】。
通过单击如图8.51所示属性页上的【下一步】按钮,进入该向导的下一个页面,如图
8.
52所示。通过单击如图8.52所尽属性页上的【下一步】按钮,进入该向导的第三个页面,如图
8.
53所示。
但是,我们可以看到,上述这个向导对话框仍存在一些问题:在第一个页面上,不应该有"上一步"这个按钮:在最后一个页面上,不应该是"下一步"按钮,而应该是"完成"按钮。在前面定义属性页资源时,我们并没有增加这些按钮,可见这些按钮是属于属性表单的,那么就需要调用属性表单的相关函数来修改它的按钮。 CPropertySheet类提供了一个SetWizardButtons成员函数,可以用来设置向导对话框上的按钮。该函数的声明原型如下所示:
void SetWizardButtons( DWORD dwFlags );
该函数有一个参数: dwFlags,可以是表8.3中所列各值的组合。
表8.3 dwFlags参数的取值
值PSWIZB BACK PSWIZB NEXT 意义 设置一个上一步按钮 设进一个下一步按钮
PSWIZB FINISH PSWIZB DISABLEDFINISH 设置一个完成按钮 设置 '个禁用的完成按钮
一般来说,应该在属性页的 OnSetActive函数中调用 SetWizardButtons这个函数。当
属性页被选中,从而成为一个活动的页面时,应用程序框架就会调用 OnSetActive这个函数。 OnSetActive函数是一个虚函数,因此,应该在属性页子类中重写这个函数,然后根据需要设置该属性页上的按钮。
下面,我们首先在第一个属性页资源关联的类 CCProp1 )中重写 OnSetActive函数,方法是在ClassView选项卡中,在CProp1类名上单击鼠标右键,从弹出的快捷菜单上选择 <Add Virtual Function. .】菜单命令,即可显示如图8.54所示的添加虚函数对话框。在该对话框左边的虚函数列表中找到OnSetActive并选中,然后单击【Add and Edit】按钮,即可完成添加该虚函数的重写井定位到这个重写的函数定义处。
图 8.54 New Virtual Override for c1ass CProp1对话框
然后,在这个重写的OnSetActive函数中,调用属性表单对象的 SetWizardButtons函数设置第一个属性页上的按钮。但是,这里如何获得属性表单对象呢?因为属性页是被添加到属性表单中的,也就是说,属性表单是属性页的父窗口,所以,就可以通过GetParent函数获取属性页父窗口的指针,即属性表单的指针,但该函数返回的是CWnd类型的指针,而我们需要的是属性表单类型,因此还需要进行一个强制转换,将CWnd类型的指针转换为 CPropertySheet类型的指针。然后利用此指针,调用 SetWizardButtons函数。对于第)个属性页来说,应该只有一个【下一步】按钮,因此, SetWizardButtons函数的参数应该为PSWIZB NEXT。所以, CPropl类的OnSetActive函数的具体实现代码如例8-6所示。
例8-6
BOOL CProp1 : :OnSetActive()
{
// TODO : Add your specialized code here and/or call the base class
((CPropertySheet*)GetParent())->SetWizardButtons(PSWIZB_NEXT) ;
return CPropertyPage : :OnSetActive(} ;
接下来,为第二个属性页 CCProp2类〉添加OnSetActive虚函数。因为第二个属性页应该有【上一步】和【下一步】按钮,所以,它的具体实现代码应该如例8-7所示。 例8-7
BOOL CProp2 : :OnSetActive(} { // TODO: Add your specialized code here and/or call the base class
((CPropertySheet*)GetParent() )->SetWizardButtons(PSW工ZB_BACK PSW工ZB_NEXT) ;
return CPropertyPage :: OnSetActive() ;
最后,再为第三个属性页 CCProp3类〉添加OnSetActive虚函数。因为第三个属性页应该有【上一步】和【完成】按钮,因此,它的具体实现代码应该如例8-8所示。 例8-8
BOOL Cprop3 : : OnSetActive()
{
11 TODO : Add your spec工alized code here and/or call the base class
((CPropertySheet*)GetParent())->SetWizardButtons(PSWIZB_BACK I PSWIZB_
F工N工SH) ;
return CPropertyPage: : OnSetActive() ;
Build井运行Prop程序,单击【属性表单】菜单命令,将显示如图8.55所示的向导对话框。可以看到,这时第一个页面上的【上一步】按钮是不可用的。
单击如图8.55所示属性页上的【下一步】按钮,将显示第二个页面,如图8.56所示。可以看到,该页面上的【上一步】和【下一步】这两个按钮均可用。
单击如图 8.56所示属性页上的【下一步】按钮,将显示第三个页面,如图 8.57所示。可以看到,这时它具有【上一步】、【完成】和【取消】按钮。
图 8 .57向导对话框的第三页
1.处理第一个页面
对于向导来说,通常是希望用户在每个属性页中进行一些选择。接下来,我们就对每个页面进行一个判断,检查用户是否做出选择,如果没有,就禁止程序进入下一个页面。也就是说,用户必须进行了一项选择之后,才能进入下一个页面。
首先处理第一个页面,为了操作方便,为这个页面上的单选按钮关联一个成员变量。在第一个单选按钮(其 ID为 IDC_RADIO 1)上单击鼠标右键,从弹出的快捷菜单中选择 <Class Wizard. .】,这时,系统会弹出一个如图 8.58所示的对话框,提示用户: IDD_PROPl 是一个新资源,可以为它创建一个新类,或选择一个己有的类。
实际上,我们已经为这个 IDD PROPl属性页资源创建了一个相关类 : CPropl,但是这个信息在 ClassWizard中丢失了。这里,可以不用理会这个提示,单击该提示对话框上的【 Cancel】按钮即可。之后会出现 ClassWizard对话框,它的 Message Maps选项卡如图
8.59所示。在 Objects IDs列表中可以看到第一个属性页资源上的三个单选按钮控件的 ID: IDC_RADIOl、 IDC RADI02和 IDC RADI03。
选择 Member Variable选项卡,如图 8.60所示。但是,我们在 CPropl类的控件 E列表中并没有看到单选按钮的囚。
图8.59 ClassWizard对话框的MessageMaps选项卡
图 8.60 ClassWizard对话框的Message Variables选项卡
这是因为对一组单选按钮来说,需要设置该组中第一个单选按钮的Group属性。打开第一个单选按钮的属性对话框,选中Group选工页,如图 8.61所示。
RADIOl这个 ID号了。 选中该由,井单击 【 Add Variable...】按钮,将弹出如图 8.62所示
的添加成员变量对话框,为这个控件增加一个值类型的成员变量: m_occupation,并且发现在Variable type下拉列表中只有int类型可供选择,也就是说,如果想要为单选按钮增加值类型的成员变量,则只能增加一个int类型的变量。
图 8.62 Add Member Variable对话框这时,在Prop程序中,可以在CPropl类的头文件中看到m_occupation变量的定义,并可以看到在CPropl类的构造函数中将m_occupation变量初始化为-1,代码如例 8-9所
当为第一个单选按钮设置了Group选项后,随后的两个单选按钮就和这个按钮属于同一组了,直到遇到下一个(按照 Tab顺序)具有 Group属性的控件为止。这样,在Prop程序运行时,当选中第一个单选按钮后,它所关联的成员变量m_occupation的值就是0;当选中第二个单选按钮后, m_occupation变量的值就是 1 ;当选中第三个单选按钮后, m_occupation变量的值就是2。于是,在程序中,通过判断这个成员变量的值就可以知道当前选中的是哪个单选按钮控件。这里,将m_occupation变量初始化为-1,表明初始显示时,三个单选按钮一个也没有选中。因此在程序中就可以对这个变量进行判断,如果其值为-1,就说明用户没有选择单选按钮选I页。
另外,在CPropl的DoDataExchange函数(如例 8-10所示)中,可以看到添加了一条DDX_Radio函数的调用,用来在单选按钮控件与成员变量之间交换数据。
CPropertyPage : : DoDataExchange (pDX) ; // {{AFX_DATA_MAP(CProp1) DDX_Radio(pDX,工DC_RADI01, rn一occupation) ; //} }AFX_DATA_MAP
当用户单击第一个属性页上的【下一步】按钮后,应该判断用户是否选择了某个职业,只有当用户选择了某个职业时,程序才能进入下一个属性页。实际上,当用户单击属性页上的【下一步】按钮后,程序将调用OnWizardNext这个虚函数,如果这个函数返回0,那么程序自动进入当前向导的下一个属性页:如果返回-1,将禁止属性页发生变更。因此,我们为CPropl类添加OnWizardNext这个虚函数的处理,来完成对该属性页上【下一步】按钮的命令响应。该虚函数的添加方法与前面 OnSetActive虚函数的添加方法相同。添加之后,我们就可以在这个虚函数中判断ffi_occupation变量的值,如果是-1,说明用户没有选择任何一个职业,则会弹出一个对话框,提示用户应选择一个职业,然后让这个虚函数返回一L禁止进入下一个属性页。具体的实现代码如例8-11所示。
例8-11
LRESULT CPropl : :OnwizardNext()
'
// TODO : Add your specia1ized code here and/ or ca11 the base c1ass
if (m_occupation -1)
MessageBox ( "请选择你的职业!") ;
return -1;
return CPropertyPage : :OnW工zardNext() ;
Build并运行Prop程序,单击【属性表单】菜单命令,将显示向导对话框,可以看到初始显示时,并没有选中任何一种职业。我们任选一种职业,然后单击【下一步】按钮,程序立即弹出一个对话框,提示:"请选择你的职业!",如图8.63所示。
可是我们已经选择了一种职业,为什么还会出现这个提示对话框呢?问题出在哪里呢?请读者回想一下第 7章介绍的内容,控件与成员变量的数据交换是通过 DoData
Exchange函数来完成的,而程序中并不直接调用这个函数,而是通过调用 UpdateData函数来调用它。对后者来说,当它的参数为TRUE时,是从控件得到成员变量的值;当参数值为FALSE时,是用成员变量的值初始化控件。如果在CPropl类的OnWizardNext函数中想要从控件得到相关联的变量的值,就应该以TRUE为参数来调用 UpdateData函数,另外,这个参数的默认值就是 TRUE,因此,此时可以以不带参数的形式直接调用 UpdateData函数,即这时的OnWizardNext函数代码如例 8-12所示。
i~tl 8-12
LRESULT CProp1 : :OnWizardNext()
// TODO : Add your special工zed code here and/or call the base class
UpdateData();
if(m_occupation ---1)
{
MessageBox(
return -1;
return CPropertyPage ::O口WizardNext ( ) ;
再次Build井运行Prop程序,单击【属性表单】菜单命令,将显示向导对话框。我们先
不做任何选择,直接单击【下一步】按钮,程序立即弹出一个对话框,提示:"请选择你的
职业!",如图 8.64所示。
图 8.64未选择职业直接单击下一步按钮时出现的提示对话框
关闭这个提示对话框,然后选择一种职业,再单击【下一步】按钮,程序就进入到第二个属性页面。
接下来,为第一个属性页添加对工作地点的选择进行判断的代码。那么首先需要在工作地点列表框中增加一些工作地点。根据第7章的知识,我们知道应该在响应这个属性页的 WM_INITDIALOG消息的函数中完成这一任务,也就是在这个属性页显示之前向列表框中增加一些工作地点。因此,首先为CPropl类添加WM INITDIALOG消息的响应函数 (OnInitDialog)。前面已经介绍过,在MFC编程中,对控件的操作都是通过相关的MFC类来完成的。对于列表框,也有一个与之对应的 MFC类: CListBox。该类提供了一个成
BOOL CPropl : :OnInitDialog ( )
CPropertyPa ge : :OnlnitDialog() ;
// TODO: Add extra工nitialization here
( (CListBox*)GetDlgItem (IDC_LIST1) ) ->AddString ( "北京") ;
((CListBox*)GetDlgItem(IDC_LIST1))->AddString("天津") ;
( (CListBox*)GetDlgItem(IDC_LIST1))->AddString("上海") ;
return TRUE; // return TRUE un less you set the focus to a control // EXCEPTION : OCX Property Pages shou l d r eturn FALSE
先运行这时的 Prop程序,打开属性表单,将会看到在工作地点列表框中显示了我们添加的三个地址,如图8.65所示。图 8.65添加工作地点后的第一个属性页
现在,就可以对工作列表框控件进行判断,让用户必须选择一个工作地点,否则,不能进入下一个属性页面。同前面的单选按钮一样,首先需要给这个列表框控件关联一个成员变量,方法同样是通过ClassWizard来完成,在如图8.66所示的添加成员变量对话框中,设置这个成员变量的名称为: m workAddr,并选择值类型,同时,可以发现这时变量类型只能为CStringo
同前面的m_occupation变量一样,CPropl类在其构造函数中对m workAddr变量也需要进行初始化,代码如下所示:
m_workAddr = _T("");
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -