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