当前位置 博文首页 > 一位初中编程爱好者的博客:使用MFC制作安装程序——向导对话框

    一位初中编程爱好者的博客:使用MFC制作安装程序——向导对话框

    作者:[db:作者] 时间:2021-08-29 22:30

    参考:鸡啄米
    我们平常安装软件时,都会弹出一个对话框告诉我们步骤,这就是向导对话框。今天,我们就来研究一下创建向导对话框的方法。
    向导对话框最基本的两个类是属性页类CMFCPropertyPage和属性表类CMFCPropertySheet。每个属性页都相当于一个特殊的对话框,而属性表是这些对话框的集成,也就是主对话框。下面,我们就来模拟一个安装程序。

    一、创建资源

    新建一个基于对话框的MFC程序,名为Guide。VS自动生成的框架是基于CDialogEx的,我们要用CMFCPropertySheet,所以先把自动生成的对话框资源和GuideDlg.h GuideDlg.cpp删除。
    现在,我们开始创建几个属性页的对话框资源,可以任意放置控件。我创建了3个属性页,如图所示:
    1
    2
    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对此提供了很多虚函数供我们重载:

    1. CMFCPropertyPage::OnSetActive
      这个函数的说明是:当此页成为活动页(即被显示)时调用。也就是说,这是个初始化函数。我们可以在这里修改显示的按钮。
    2. CMFCPropertyPage::OnWizardNext/Back/Finish
      这个函数的说明是:当使用向导对话框时,在单击“下一步”/“上一步”/“完成”时调用。我们可以在这里处理按下按钮后需要的行为。

    简介属性页

    我们首先来看第一个属性页(简介)。这个属性页不需要任何额外操作,我们只要在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();
    

    我们运行程序,发现有以下两个问题:

    1. 向导对话框标题未正常显示;
    2. 下面有一个很烦人的“帮助”按钮。

    对于这两个问题,我们可以通过修改CGuideSheet的OnInitDialog函数解决。
    首先使用SetTitle设置标题,再通过获取“帮助”按钮句柄的方式把它隐藏掉。(可选)

    BOOL CGuideSheet::OnInitDialog()
    {
    	BOOL bResult = CMFCPropertySheet::OnInitDialog();
    	SetTitle(m_strCaption);
    	GetDlgItem(IDHELP)->ShowWindow(SW_HIDE);
    	return bResult;
    }
    
    

    五、运行结果

    1
    2
    3
    4

    cs