【MFC 学习之路】创建一个具有 Windows Vista 风格的向导

设计属性页

在项目中添加属性页资源,资源类型是 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_AEROWIZARDAero 向导。

再次运行程序,我们就会看到一个漂亮的 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;
}

大功告成

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值