属性页和属性表单在程序中应用很广,一般在安装程序或者一些设置向导中的都是属性表单,这一次在看完孙鑫老师的书后总结一下一些关于属性表单和属性页的一些基本的操作。
要创建属性表单首先就要先创建属性页,属性页对应的MFC类就是 CPropertyPage类,它是从CDialog类中派生而来的,所以属性页也是一个对话框。这次的程序要创建三个属性页,首先要添加三个属性页资源,可以直接添加三个IDD_PROPPAGE_LARGE的对话框资源,当然也可以直接添加一般的对话框资源,然后在它的属性设置中设置为满足属性页的,至于什么属性才是属性页对话框的属性,大家可以自己创建一个属性页资源和一个一般对话框资源然后比较一个,本程序是直接创建三个属性页资源,下面是最终的三个属性页的效果:
要注意的是属性页资源默认的是英语,所以要在属性中将语言改为简体中文,不然就会出现乱码了。还有就是在第一个属性页的的list Box和第三个属性页的Combo Box中,在他们的属性中都有一个排序的属性,默认情况下是选中的,这种情况下你添加进去的内容的顺序和最后显示出来的顺序一般是不一样的,所以这次的程序是不要排序的,要把那个勾去掉。
为了创建属性表单,首先就要创建一个CPropertySheet 对象,然后在这个对象中添加三个属性页的对象(CPropertyPage 类型),然后调用AddPage函数添加每一个属性页,最后调用DoModal函数创建一个模态属性表单,或者是Create函数创建一个非模态属性表单。
要创建CpropertySheet对象,当然首先是要添加一个派生于CPropertySheet类的子类,在这里命名为CPropSheet. 下面是AddPage函数的原型:
void AddPage( CPropertyPage *pPage );
参数是一个属性页的指针,所以在CPropSheet类中添加了三个属性页的对象后,就可以在属性表单的构造函数中进行属性页的添加了,发现CpropSheet类有两个构造函数
CPropSheet::CPropSheet(UINT nIDCaption, CWnd* pParentWnd, UINT iSelectPage) :CPropertySheet(nIDCaption, pParentWnd, iSelectPage) { // void AddPage(CPropertyPage *pPage); AddPage(&m_prop1); AddPage(&m_prop2); AddPage(&m_prop3); } CPropSheet::CPropSheet(LPCTSTR pszCaption, CWnd* pParentWnd, UINT iSelectPage) :CPropertySheet(pszCaption, pParentWnd, iSelectPage) { AddPage(&m_prop1); AddPage(&m_prop2); AddPage(&m_prop3); }
这两个构造函数对应的父类的构造函数只有第一个参数不一样,下面是类CPropertySheet的两个构造函数:
CPropertySheet( UINT nIDCaption, CWnd *pParentWnd= NULL, UINT iSelectPage = 0 );
CPropertySheet( LPCTSTR pszCaption, CWnd *pParentWnd= NULL, UINT iSelectPage = 0 );
当然还有一个是没参数的构造函数,那个不鸟先,在这两个构造函数里面后面两个参数是一样的没而且都有设默认值。第二个参数设为空,也就是说属性表单的父窗口就是应用程序的主窗口,第三个函数是指定属性表单初始选择的属性页,默认是第一个页面,我们可以修改这个来改变属性表单第一页的显示。
CPropertySheet类是派生于CWnd类的,而不是CDialog类,但是CPropertySheet和CDialog类的操作是类似的。
接下来在菜单项里添加一个新的菜单“属性表单”,通过它来显示属性表单。在属性表单正确创建后,我们希望把它创建成一个向导类型的对话框,那么就要在调用DoModal函数之前先调用SetWizardMode函数,这样出现的效果就会是上一步,下一步这样的向导型的按钮了。这时候这些按钮的设置是不正确的,第一页的上一步按钮应该是不可以活动的,最后一个也应该是完成按钮而不是下一步,要设置向导按钮的显示,需要调用SetWizardButtons函数,要在哪里调用呢。在属性页资源里我们并没有看到这些按钮,所以这些按钮是属于属性表单的,所以要在属性表单类里去调用这个函数。一般情况下,应该在属性页的OnSetActive函数里面去调用设置向导按钮的函数,当属性页被选中的时,成为一个活动页面,应用程序框架就会调用OnSetActive函数。这个函数是一个虚函数,因此应该在属性页子类中重写这个函数,然后再设置该属性页上面的向导按钮。下面是三个属性页的OnSetActive函数的代码:
BOOL CProp1::OnSetActive() { // TODO: Add your specialized code here and/or call the base class ((CPropSheet*)GetParent())->SetWizardButtons(PSWIZB_NEXT); return CPropertyPage::OnSetActive(); }BOOL CProp2::OnSetActive() { // TODO: Add your specialized code here and/or call the base class ((CPropSheet*)GetParent())->SetWizardButtons(PSWIZB_NEXT | PSWIZB_BACK); return CPropertyPage::OnSetActive(); }BOOL CProp3::OnSetActive() { // TODO: Add your specialized code here and/or call the base class ((CPropSheet*)GetParent())->SetWizardButtons(PSWIZB_BACK | PSWIZB_FINISH); return CPropertyPage::OnSetActive(); }这样三个属性页的向导按钮的显示就符合操作习惯了,新的问题出来了,现在我们要让操作者有了选择以后才可以进入下一个页面,我们一个一个页面来操作:
处理第一个页面:
先要为单选框添加关联变量,我们在MFC 的Class Wizard 的Member Variable里面找不到单选按钮的ID,这是因为我们这三个单选按钮是在一个组框里面的,所以要把第一个单选按钮的Ground属性勾上,这样就可以在Member Variable里面找到第一个单选框的ID了。变量命名为m_occupation,在CProp1的构造函数里,这个变量被初始化为-1,就是没有一个单选按钮被选中,第一个被选中的话,该值就是0,以此类推。接下来要为list Box添加可以选择的工作地点,要在List Box中添加串的话,一般是在WM_INITDIALOG的响应函数中进行添加。下面是添加工作地点的函数代码:
BOOL CProp1::OnInitDialog() { CPropertyPage::OnInitDialog(); // TODO: Add extra initialization here ((CListBox*)GetDlgItem(IDC_LIST1))->AddString("广州"); ((CListBox*)GetDlgItem(IDC_LIST1))->AddString("汕头"); ((CListBox*)GetDlgItem(IDC_LIST1))->AddString("上海"); ((CListBox*)GetDlgItem(IDC_LIST1))->AddString("北京"); ((CListBox*)GetDlgItem(IDC_LIST1))->AddString("杭州"); ((CListBox*)GetDlgItem(IDC_LIST1))->AddString("天津"); return TRUE; // return TRUE unless you set the focus to a control // EXCEPTION: OCX Property Pages should return FALSE }AddString函数是CListBox类的成员函数,所以获得该控件的指针后要进行强制转换后才可以掉用。
现在第一个页面已经初始化完毕了,接下来就是要在操作者按下 下一步 按钮的时候判断作者是否两个都选择了,要怎么判断呢?在MFC中,当用户单击下一步按钮后,程序会自动调用OnWizardNext这个虚函数,如果这个函数返回0,那么就会进入下一个属性页,如果返回-1,就会禁止属性页的变更。因此,我们可以在CProp1中添加这个函数进行判断,下面是这个函数的代码:
LRESULT CProp1::OnWizardNext() { // TODO: Add your specialized code here and/or call the base class UpdateData(); if(m_occupation==-1) { MessageBox("请选择你的职业!"); return -1; } if(m_workAddrs=="") { MessageBox("请选择你的工作地点!"); return -1; } return CPropertyPage::OnWizardNext(); }其中m_workAddrs是List Box 控件的关联变量。CString 类型。在上面的函数中,UpdateData()这个函数很重要,如果忘记添加的话,那么控件的关联变量的值就不会更新,那样就算你都按要求选择了,也是进步了第二个页面的。
处理第二个页面:
第二个页面是六个复选框,为每一个复选框关联一个变量,这里就用6个BOOL类型(只有这个类型的选择而已)的变量来关联,命名就是m_+运动项目的英文。例如:m_tennis,m_football等。可以看到在CProp2的构造函数里,他们都被初始化为FALSE;
在CProp2中类似Cprop1那样添加OnWizardNext函数
LRESULT CProp2::OnWizardNext() { // TODO: Add your specialized code here and/or call the base class UpdateData(); if(m_basketball || m_football || m_tabletennis || m_tennis || m_badminton || m_volleyball) return CPropertyPage::OnWizardNext(); else { MessageBox("请选择你的兴趣爱好!"); return -1; } }处理第三个页面:
第三个页面是一个组合框由一个编辑框和一个列表框组成,对应的是MFC的CCombo Box类,该类也有AddString函数,用来向组合框中添加字符串选项。因此在CProp3的OnInitDialog中初始化。另外我们还希望在第三个属性页初始化显示的时候,在组合框中一个初始选择的项,这个可以通过组合框的一个函数:SetCulSel来实现,该函数的功能是选择组合框的列表框的一个字符串,并将其显示在该组合框的编辑框中。
int SetCurSel( int nSelect );
该函数的参数nSelect,是一个基于0的索引,指定选择项的索引位置。如果设为-1,那么将移除该组合框的当前选择,并清空组合框的编辑框中的内容。
BOOL CProp3::OnInitDialog() { CPropertyPage::OnInitDialog(); // TODO: Add extra initialization here ((CComboBox*)GetDlgItem(IDC_COMBO1))->AddString("1000元以下"); ((CComboBox*)GetDlgItem(IDC_COMBO1))->AddString("1000元-2000元"); ((CComboBox*)GetDlgItem(IDC_COMBO1))->AddString("2000元-3000元"); ((CComboBox*)GetDlgItem(IDC_COMBO1))->AddString("3000元-4000元"); ((CComboBox*)GetDlgItem(IDC_COMBO1))->AddString("4000元以上"); ((CComboBox*)GetDlgItem(IDC_COMBO1))->SetCurSel(0); return TRUE; // return TRUE unless you set the focus to a control // EXCEPTION: OCX Property Pages should return FALSE }到这一步,属性表单中的所有控件都已经设置完毕了,这个也是可以用的了。我们现在希望当用户选好按下完成后,可以在窗口中输出他的选择。
像响应下一步按钮的OnWinzardNext一样,相应完成按钮的函数是 OnWizardFinish 。
先要保存用户的选择,用一个CString变量 m_strSalary来保存。
BOOL CProp3::OnWizardFinish() { // TODO: Add your specialized code here and/or call the base class int index; index=((CComboBox*)GetDlgItem(IDC_COMBO1))->GetCurSel(); ((CComboBox*)GetDlgItem(IDC_COMBO1))->GetLBText(index,m_strSalary); return CPropertyPage::OnWizardFinish(); }其中函数GetCulSel是返回用户选择的索引,用那个作为函数GetLBText的参数就可以保存用户的选择了。
void GetLBText( int nIndex, CString&rString ) const;
第一个参数就是索引值,第二个是用来装载的变量。
接下来为了可以在窗口中显示用户得选择,需要定义变量来保存用户在每一页中的选择。定义的变量和初始值如下:
CString m_strSalary; BOOL m_bLike[6]; CString m_strWorkAddr; int m_iOccupation;CPropView::CPropView() { // TODO: add construction code here m_iOccupation=-1; m_strWorkAddr=""; memset(m_bLike,0,sizeof(m_bLike)); m_strSalary=""; }这里要再说一个重点的东西,一般情况下,CPropertySheet类的DoModal函数返回的是IDOK或者是IDCANCEL。但是如果属性表单已经被创建为向导了,那么该函数的返回值就是ID_WIZFINISH或者ID_CANCEL。因此要在VIEW中对DoModal的返回值进行判断。
还要注意,当DoModal函数返回后,属性表单窗口就被摧毁了,但是propSheet这个属性表单对象的生命期还没有结束。因此,仍然可以利用这个对象去访问它的内部成员。
void CPropView::OnPropertysheet() { // TODO: Add your command handler code here CPropSheet propsheet("属性表单"); //设置向导对话框 propsheet.SetWizardMode(); if(ID_WIZFINISH==propsheet.DoModal()) { m_iOccupation=propsheet.m_prop1.m_occupation; m_strWorkAddr=propsheet.m_prop1.m_workAddrs; m_bLike[0]=propsheet.m_prop2.m_tennis; m_bLike[1]=propsheet.m_prop2.m_badminton; m_bLike[2]=propsheet.m_prop2.m_tabletennis; m_bLike[3]=propsheet.m_prop2.m_football; m_bLike[4]=propsheet.m_prop2.m_basketball; m_bLike[5]=propsheet.m_prop2.m_volleyball; m_strSalary=propsheet.m_prop3.m_strSalary; //让视图窗口无效,从而引起窗口重绘 Invalidate(); } }在函数后面那个Invaliddate()函数调用后,窗口就会发生重绘,这样就可以在OnDraw函数中进行输出了。下面是OnDraw函数的代码:
void CPropView::OnDraw(CDC* pDC) { CPropDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); // TODO: add draw code for native data here CFont font; font.CreatePointFont(300,"宋体"); CFont *pOldFont; pOldFont=pDC->SelectObject(&font); pDC->SetTextColor(RGB(44,23,111)); CString strtemp; strtemp="你的职业:"; switch(m_iOccupation) { case 0: strtemp+="程序员"; break; case 1: strtemp+="系统工程师"; break; case 2: strtemp+="项目经理"; break; default: break; } pDC->TextOut(0,0,strtemp); strtemp="你的工作地点:"; strtemp+=m_strWorkAddr; TEXTMETRIC tm; pDC->GetTextMetrics(&tm); pDC->TextOut(0,tm.tmHeight,strtemp); strtemp="你的兴趣爱好:"; if(m_bLike[0]) { strtemp+="网球 "; } if(m_bLike[1]) { strtemp+="羽毛球 "; } if(m_bLike[2]) { strtemp+="乒乓球 "; } if(m_bLike[3]) { strtemp+="足球 "; } if(m_bLike[4]) { strtemp+="篮球 "; } if(m_bLike[5]) { strtemp+="排球 "; } pDC->TextOut(0,tm.tmHeight*2,strtemp); strtemp="你的薪资水平:"; strtemp+=m_strSalary; pDC->TextOut(0,tm.tmHeight*3,strtemp); pDC->SelectObject(pOldFont); }OK啦这就是这一次的总结 下面是最终的运行效果: