参考:鸡啄米
我们平常安装软件时,都会弹出一个对话框告诉我们步骤,这就是向导对话框。今天,我们就来研究一下创建向导对话框的方法。
向导对话框最基本的两个类是属性页类CMFCPropertyPage和属性表类CMFCPropertySheet。每个属性页都相当于一个特殊的对话框,而属性表是这些对话框的集成,也就是主对话框。下面,我们就来模拟一个安装程序。
新建一个基于对话框的MFC程序,名为Guide。VS自动生成的框架是基于CDialogEx的,我们要用CMFCPropertySheet,所以先把自动生成的对话框资源和GuideDlg.h GuideDlg.cpp删除。
现在,我们开始创建几个属性页的对话框资源,可以任意放置控件。我创建了3个属性页,如图所示:
这里要注意,我们不需要在属性页上添加“上一步”“下一步”等按钮,这些按钮都被封装在了属性表类里。
接着,我们把每个属性页的边框都设为Thin,样式设为Child,标题栏设为False,并修改ID分别为IDD_SYNOPSIS(简介)IDD_CONFIRM(确认)IDD_INSTALL(安装)。
我们分别为每一个属性页右击添加类,类名随意,但一定要注意,基类要修改为CMFCPropertyPage!!!
创建属性页类后,我们打开源文件,发现自动生成的构造函数有点问题:
原来,CMFCPropertyPage类的构造函数只有这几个:
// Construction
public:
CMFCPropertyPage();
CMFCPropertyPage(UINT nIDTemplate, UINT nIDCaption = 0);
CMFCPropertyPage(LPCTSTR lpszTemplateName, UINT nIDCaption = 0);
显然,我们需要使用第二个,通过资源ID创建属性页,所以我们分别把每一个属性页构造函数后面的pParent删掉:
CConfirmPage::CConfirmPage(CWnd* pParent /*=nullptr*/)
: CMFCPropertyPage(IDD_CONFIRM)
{
}
每一个属性页都有自己特定的功能和特定的按钮,如第一页不能有“上一步”,安装完成后不能有“上一步”“下一步”等等。因为每个属性页都不同,所以我们不能在属性表类里设置,只能在属性页类里设置。MFC对此提供了很多虚函数供我们重载:
我们首先来看第一个属性页(简介)。这个属性页不需要任何额外操作,我们只要在OnSetActive函数中设置显示“下一步”按钮就行了。
注:不能在OnInitDialog中设置,因为我们切换属性页的过程其实是在显示和隐藏窗口,并没有重新创建,所以OnInitDialog函数只调用一次,不能满足需求。
CMFCPropertySheet::SetWizardButtons
在向导对话框上启用或禁用Back、Next或Finish按钮,应在调用DoModal之前调用此函数。函数原型为:
void SetWizardButtons (
DWORD dwFlags
);
参数dwFlags:设置向导按钮的外观和功能属性。可以是以下值的组合: PSWIZB_BACK 启用“Back”按钮,如果不包含此值则禁用“Back”按钮。
PSWIZB_NEXT 启用“Next”按钮,如果不包含此值则禁用“Next”按钮。
PSWIZB_FINISH 启用“Finish”按钮。
PSWIZB_DISABLEDFINISH 显示禁用的“Finish”按钮。
我们可以用这个函数来显示“下一步”按钮。
BOOL CSynopsisPage::OnSetActive()
{
// 获得父窗口,即属性表CMFCPropertySheet类
CMFCPropertySheet* psheet = (CMFCPropertySheet*)GetParent();
// 设置属性表只有“下一步”按钮
psheet->SetWizardButtons(PSWIZB_NEXT);
return CMFCPropertyPage::OnSetActive();
}
第二个属性页(确认)属性页就有些复杂了。这个属性页需要显示“上一步”和“下一步”按钮,还要在单击“下一步”时弹出对话框确认。
我们可以在OnSetActive函数设置按钮。
BOOL CConfirmPage::OnSetActive()
{
// 获得父窗口,即属性表CMFCPropertySheet类
CMFCPropertySheet* psheet = (CMFCPropertySheet*)GetParent();
// 设置属性表有“上一步”和“下一步”按钮
psheet->SetWizardButtons(PSWIZB_BACK | PSWIZB_NEXT);
return CMFCPropertyPage::OnSetActive();
}
要想弹出对话框确认,我们可以重载CMFCPropertyPage::OnWizardNext函数。但关键问题是,这个函数只是一个附加代码的接口,切换属性页的代码并不在里面,所以,我们在这个函数里的操作并不会影响切换属性页,这意味着如果我们取消安装,直接return,依然会切换到下一页!幸好,MFC提供了一个模拟按钮按下的函数:
CMFCPropertySheet::PressButton
模拟按下某指定的按钮。函数原型为:
void PressButton(
int nButton
);
参数nButton:要模拟按下的按钮,它可以是下列值之一:
PSBTN_BACK 选择“Back”按钮。
PSBTN_NEXT 选择“Next”按钮。
PSBTN_FINISH 选择“Finish”按钮。
PSBTN_OK 选择“OK”按钮。
PSBTN_APPLYNOW 选择“Apply”按钮。
PSBTN_CANCEL 选择“Cancel”按钮。
PSBTN_HELP 选择“帮助”按钮。
这样,如果用户选择了取消,我们可以模拟按下“上一步”按钮,就可以抵消了。
LRESULT CConfirmPage::OnWizardNext()
{
//弹出对话框确定
LRESULT result = CMFCPropertyPage::OnWizardNext();
if (MessageBoxW(L"确定安装吗?", L"安装向导", MB_ICONQUESTION | MB_YESNO) == IDNO)
((CMFCPropertySheet*)GetParent())->PressButton(PSBTN_BACK);
return result;
}
由于我们只是模拟安装,所以这一个属性页不需要执行真正的安装操作,只是模拟安装完成的状态,隐藏“上一步”“下一步”和“取消”按钮,显示“完成”。
我们可以通过SetWizardButtons设置,但还有一种更简单的方法
SetFinishText
在向导属性表中设置“完成”按钮的文本,显示并启用该按钮,并隐藏“下一步”和“上一步”按钮。函数原型为:
void SetFinishText(
LPCTSTR lpszText
);
参数lpszText:指向包含“完成”按钮新文本的以空字符结尾的字符串的长指针。
使用这个函数还能修改文本,所以我们使用这个函数试试。
BOOL CInstallPage::OnSetActive()
{
// 获得父窗口,即属性表CMFCPropertySheet类
CMFCPropertySheet* psheet = (CMFCPropertySheet*)GetParent();
//设置属性表只有“完成”按钮
psheet->GetDlgItem(IDCANCEL)->ShowWindow(SW_HIDE);//隐藏“取消”按钮
psheet->SetFinishText(L"完成安装");
return CMFCPropertyPage::OnSetActive();
}
到此为止,属性页创建完毕。
我们点击菜单中的项目-类向导,弹出类向导对话框后,点击添加类的下拉按钮,选择MFC类,添加名为CGuideSheet的类,基类选择为CMFCPropertySheet。
在CGuideSheet.h中包含三个属性页头文件,然后分别添加每个属性页类的实例作为私有成员。
#pragma once
#include "CSynopsisPage.h"
#include "CConfirmPage.h"
#include "CInstallPage.h"
class CGuideSheet :
public CMFCPropertySheet
{
public:
CGuideSheet(LPCTSTR pszCaption, CWnd* pParentWnd = nullptr, UINT iSelectPage = 0);
private:
CSynopsisPage m_page0;
CConfirmPage m_page1;
CInstallPage m_page2;
};
在构造函数中,我们要把三个属性页添加到属性表中。
CMFCPropertySheet::AddPage
为属性对话框添加新的属性页。函数原型为:
void AddPage(
CPropertyPage *pPage
);
参数pPage:要添加的新的属性页的对象指针。
CGuideSheet::CGuideSheet(LPCTSTR pszCaption, CWnd* pParentWnd, UINT iSelectPage)
: CMFCPropertySheet(pszCaption, pParentWnd, iSelectPage)
{
AddPage(&m_page0);
AddPage(&m_page1);
AddPage(&m_page2);
}
这样,完整的属性表类就创建完毕了。
现在我们已经创建完向导对话框,但还有一个问题:怎么显示它呢?我们打开Guide.cpp,在CGuideApp::InitInstance函数的中间可以找到原来弹出对话框的代码:
CGuideDlg dlg;
m_pMainWnd = &dlg;
INT_PTR nResponse = dlg.DoModal();
我们只需要把这里改为显示向导对话框的代码就可以了。代码如下:
CGuideSheet sheet(L"安装向导程序");
sheet.SetWizardMode();
m_pMainWnd = &sheet;
INT_PTR nResponse = sheet.DoModal();
我们运行程序,发现有以下两个问题:
对于这两个问题,我们可以通过修改CGuideSheet的OnInitDialog函数解决。
首先使用SetTitle设置标题,再通过获取“帮助”按钮句柄的方式把它隐藏掉。(可选)
BOOL CGuideSheet::OnInitDialog()
{
BOOL bResult = CMFCPropertySheet::OnInitDialog();
SetTitle(m_strCaption);
GetDlgItem(IDHELP)->ShowWindow(SW_HIDE);
return bResult;
}