设计属性页
在项目中添加属性页资源,资源类型是 Dialog 下的 Proppage。
在设计器中进行界面的设计。同样地,我们为每一页都设计一份属性页资源。
创建属性页类
声明一个继承自 CPropertyPage 的类(包含于头文件 afxdlgs.h),这个类对应我们刚才设计的一个属性页。(这里手写代码,也可以使用类向导。)
class Page0 : public CPropertyPage
{
...
};
在类中插入如下代码(假设属性页的 ID 是 IDD_PROPPAGE0),我们让枚举 IDD 等于 IDD_PROPPAGE0,这样就指示了在设计器中进行设计(使用类向导)时,该类与对应 ID 的资源相关联。
class Page0 ...
{
...
#ifdef AFX_DESIGN_TIME
enum { IDD = IDD_PROPPAGE0 };
#endif
...
};
到目前为止,这个类还基本上是一个空类,还不能做任何事情。为了能用这个类构造出一个真正的属性页,在构造函数中,我们调用基类的构造函数,用相应的资源 ID 初始化自身。
class Page0 ...
{
...
public:
Page0() :CPropertyPage(IDD_PROPPAGE0) {}
...
};
完整的类定义如下:
class Page0 : public CPropertyPage
{
public:
Page0() :CPropertyPage(IDD_PROPPAGE0) {}
#ifdef AFX_DESIGN_TIME
enum { IDD = IDD_PROPPAGE0 };
#endif
};
同样地,可以完成其他属性页类的定义。
创建属性表类
声明一个继承自 CPropertySheet 的类,作为我们的属性表类。
class Sheet :public CPropertySheet
{
...
};
把以上声明的属性页作为该类的成员变量。(如果不作为成员变量的话,运行时会报错,我也不知道为什么。)
class Sheet ...
{
...
protected:
Page0 m_page0;
Page1 m_page1;
Page2 m_page2;
Page3 m_page3;
...
};
在属性表的构造函数中调用 AddPage 函数,逐个把属性页添加到属性表中。调用 SetWizardMode 把属性表改为向导。
class Sheet ...
{
...
public:
Sheet();
...
};
Sheet::Sheet()
{
AddPage(&m_page0);
AddPage(&m_page1);
AddPage(&m_page2);
AddPage(&m_page3);
SetWizardMode();
}
使用属性表(示例)
有了属性表类,我们可以利用刚才定义好的表单显示一个向导了。在应用程序类的 InitInstance 函数中,为刚刚创建的属性表类创建一个对象,使用 DoModal 函数显示一个模式属性表。因为这里整个程序就是一个向导,向导结束程序就结束了,所以直接返回 FALSE,结束程序的运行。(当然也可以不这么做!)
class MyApp :public CWinApp
{
protected:
virtual BOOL InitInstance();
} theApp;
BOOL MyApp::InitInstance()
{
Sheet sheet;
sheet.DoModal();
return FALSE;
}
此时运行程序,就会看见一个向导了。
Aero 向导
使用 Aero 向导
首先需要添加如下的编译杂注才能使用 Aero 向导。(用 MFC 向导生成的应用程序会自动添加这个编译杂注)添加此编译杂注以使用 Windows XP 及以后具有良好视觉效果的公共控件,而不是 Windows 2000 及以前的 3D 风格按钮。
#if defined _UNICODE
#if defined _M_IX86
#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='x86' publicKeyToken='6595b64144ccf1df' language='*'\"")
#elif defined _M_X64
#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='amd64' publicKeyToken='6595b64144ccf1df' language='*'\"")
#else
#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
#endif
#endif
添加前后的效果对比:
Windows Vista 引入了全新的 Aero 向导。要使用 Aero 样式的向导,我们在属性表类的构造函数中加入以下代码。CPropertySheet 类的 m_psh 成员是一个 PROPSHEETHEADER 结构,修改这个结构中 dwFlags 变量的值,指示使用 Aero 向导。
Sheet::Sheet()
{
...
m_psh.dwFlags |= PSH_WIZARD | PSH_AEROWIZARD;
...
}
dwFlags 可以包含以下标志。
标志 | 风格 |
---|---|
PSH_WIZARD | 没有标题或位图的简单向导。 |
PSH_WIZARD_LITE | 类似于 PSH_WIZARD,外观有一些细微差异;例如,按钮上方的分隔符设置为窗口的完整宽度。 |
PSH_WIZARD97 | 经典向导。包含标题、位图和水印。 |
PSH_WIZARD |PSH_AEROWIZARD | Aero 向导。 |
再次运行程序,我们就会看到一个漂亮的 Aero 向导。(为了美观,这里使用了 Windows Vista 截图 。)
设置向导标题
PROPSHEETHEADER 结构的 pszCaption 变量指定整个向导的标题。我们将这个变量设置为我们想要的值。
Sheet::Sheet()
{
...
m_psh.pszCaption = TEXT("寻找最好的伴侣");
...
}
运行程序,我们可以看到标题栏上出现了我们设置的标题。
设置页标题
要为页面设置标题,首先要将属性页的 m_psp.dwFlags 变量设置一个 PSP_USEHEADERTITLE 标志(否则 pszHeaderTitle 会被忽略),然后把 pszHeaderTitle 设置为我们想要的值。
Sheet::Sheet()
{
...
m_page0.m_psp.dwFlags |= PSP_USEHEADERTITLE;
m_page0.m_psp.pszHeaderTitle = TEXT("标题");
...
}
运行程序,可以看到我们的向导页有了标题。
设置导航按钮
使用类向导为属性页类重写 OnSetActive 虚函数。在 OnSetActive 虚函数中,调用属性表的 SetWizardButtons 函数设置导航按钮。SetWizardButtons 函数必须在属性表打开的情况下,即 DoModal 被调用后调用,否则不会起到作用。一般在属性页的 OnSetActive 函数中调用这个函数。这里使用 GetParent 函数得到属性页的父窗口,即属性表。类似地,在需要改变按钮的地方(不必每一页都设置,但要考虑到单击“下一步”以及“上一步”后按钮会改变的情形)为其他属性页设置设置按钮。
可用的按钮如下表所示,多个标志可以叠加。
标志 | 按钮 |
---|---|
PSWIZB_BACK | 返回 |
PSWIZB_NEXT | 下一步 |
PSWIZB_FINISH | 完成 |
PSWIZB_DISABLEDFINISH | 禁用“完成” |
class Page0 : public CPropertyPage
{
...
protected:
virtual BOOL OnSetActive();
...
};
BOOL Page0::OnSetActive()
{
CPropertySheet* pPropSheet = (CPropertySheet*)GetParent();
pPropSheet->SetWizardButtons(PSWIZB_NEXT);
return CPropertyPage::OnSetActive();
}
运行程序,我们可以看到我们的导航窗口已经有了正确的标题和按钮,并且这些按钮能够正常工作。
对控件数据的处理
我们的导航窗口中有一些控件,我们希望在运行这个导航窗口的时候,能够得到这些控件的数据加以处理。MFC 的数据交换(DDX)函数可以很方便地获取和设置控件数据。首先设置数据交换,我们为控件添加变量。
在“类别”一栏中选择“值”。
也可以手写代码。在属性页类中声明两个变量保存选择项的索引,由于在以后的页面还需要用到这两个值,因此在这里声明为公有变量。在属性页的 DoDataExchange 函数中,用 DDX_CBIndex 函数交换组合框(combo box)选择项的索引。函数的参数为形参 pDX、控件 ID 和对应的变量。
class Page1 : public CPropertyPage
{
...
public:
int m_yourGender, m_herGender;
...
};
void Page1::DoDataExchange(CDataExchange* pDX)
{
DDX_CBIndex(pDX, IDC_COMBO1, m_yourGender);
DDX_CBIndex(pDX, IDC_COMBO2, m_herGender);
CPropertyPage::DoDataExchange(pDX);
}
对于单选按钮,如果一组单选按钮有连续的 Tab 序列,并且第一个单选按钮的 Group 属性为 True,随后的单选按钮的 Group 属性为 False,它们就会自动组成一组。要获得一组单选按钮的选择项的索引,我们只需要对其中第一个单选按钮添加变量。这里使用 DDX_Radio 函数,用法类似。
class Page2 : public CPropertyPage
{
...
public:
int m_requirement;
...
};
void Page2::DoDataExchange(CDataExchange* pDX)
{
DDX_Radio(pDX, IDC_RADIO1, m_requirement);
CPropertyPage::DoDataExchange(pDX);
}
以后当用参数 TRUE(默认值) 调用 UpdateData 时,就会调用 DoDataExchange,继而通过 DoDataExchange 中的 DDX 函数读取控件的数据并且写入到变量中;如果 UpdateData 的参数为 FALSE,就会用变量的值更新控件。
对导航的控制
CPropertyPage 类中有一组函数,当我们单击导航按钮时就会被调用。我们可以通过这些函数来对导航进行控制。
函数 | 说明 |
---|---|
OnWizardBack | 单击“上一步” |
OnWizardNext | 单击“下一步” |
OnWizardFinish | 单击“完成” |
OnCancel | 单击“取消”按钮、关闭按钮、或按 ESC 键退出了向导 |
首先调用 UpdateData 获取控件数据,然后对数据进行处理。此处如果两个组合框选择的项的索引相同,我们就弹出一个消息框,并且返回 -1 以阻止进入下一个页面。
LRESULT Page1::OnWizardNext()
{
UpdateData();
if (yourGender == herGender)
{
MessageBox(TEXT("暂不支持寻找同性伴侣。"), TEXT("寻找最好的伴侣"), MB_ICONERROR);
return -1;
}
return CPropertyPage::OnWizardNext();
}
这样当我们为这两个组合框选择相同的项时,就会弹出一个错误警告,并且阻止我们进入下一页。
对于一个向导,属性表的 DoModal 函数有两种返回值。(测试发现 Aero 向导返回的是 IDOK 而不是 ID_WIZFINISH)
返回值 | 说明 |
---|---|
ID_WIZFINISH | 单击了“完成”按钮 |
IDCANCEL | 单击“取消”按钮、关闭按钮、或按 ESC 键退出了向导 |
我们在属性表类中处理数据。
class Sheet :public CPropertySheet
{
...
public:
CString GetName();
...
};
CString Sheet::GetName()
{
CString result;
if (m_page1.m_herGender == 0)
{
switch (m_page2.m_requirement)
{
case 0:result = TEXT("玉树临风的男人"); break;
case 1:result = TEXT("风度翩翩的男人"); break;
case 2:result = TEXT("英姿飒爽的男人"); break;
case 3:result = TEXT("帅哥"); break;
default:result = TEXT("外星人");
}
}
else
{
switch (m_page2.m_requirement)
{
case 0:result = TEXT("倾国倾城的女人"); break;
case 1:result = TEXT("沉鱼落雁的女人"); break;
case 2:result = TEXT("眉清目秀的女人"); break;
case 3:result = TEXT("美女"); break;
default:result = TEXT("外星人");
}
}
return result;
}
在程序结束前输出结果。
BOOL MyApp::InitInstance()
{
Sheet sheet;
if(sheet.DoModal() == IDOK)
AfxMessageBox(TEXT("我们为您找到了一个") + sheet.GetName() + TEXT("!"), MB_ICONINFORMATION);
return FALSE;
}