如何创建向导

向导是一种属性表,它提供了一种简单而强大的方法来指导用户完成过程。

向导是简化用户体验的关键之一。 你可通过向导执行复杂的操作,例如应用程序配置,并将其分解成一系列简单的步骤。 在该过程中的每个点,你都可以提供所需内容的说明,并显示允许用户进行选择和输入文本的控件。

向导实际上是一种属性表。 属性表本质上是页面集合的容器,其中每个页面都是一个单独的对话框。 常规属性表允许用户随时访问任何页面,而向导则按顺序显示页面。 可使用按钮而不是选项卡来向前和向后导航。 页面的显示顺序由应用程序控制,并且可以根据用户输入进行修改。

向导有两种主要样式:旧版 Wizard97 样式和 Windows Vista 中引入的 Aero 样式。 有关插图,请参阅关于属性表。 (只使用 PSH_WIZARD 或 PSH_WIZARD_LITE 标志的第三种样式呈现一个简单的属性表序列,没有标头或水印。)

备注

向导上下文中的“水印”是显示在某些页面的左边距中的位图。

 

本文档大部分内容的讨论都假定你要为具有版本 5.80 或更高版本的通用控件的系统实现向导。 如果尝试将 Wizard97 样式与早期版本的通用控件一起使用,则应用程序可能会执行编译,但不会正确显示。 有关如何在早期系统上创建与 Wizard97 兼容的向导的讨论,请参阅本主题后面的“后向兼容性向导”。

需要了解的事项

技术

先决条件

  • C/C++
  • Windows 用户界面编程

Instructions

向导实现

向导的实现类似于常规属性表的实现。 在最基本的级别,需要在定义属性表的 PROPSHEETHEADER 结构中设置以下标志之一或标志组合

标志 样式
PSH_WIZARD 没有标头或位图的简单向导。
PSH_WIZARD_LITE 与 PSH_WIZARD 类似,但外观上有一些细微差别;例如,按钮上方的分隔线设置为窗口的整个宽度。
PSH_WIZARD97 具有(可选)标头、标头位图和水印的 Wizard97 向导。
PSH_WIZARD | PSH_AEROWIZARD Aero 向导。 Aero 向导不使用水印或标头位图。 它们需要单线程单元 (STA) 模型。

 

实现向导的基本过程如下所示:

  1. 为每个页面创建对话框模板。
  2. 为每个页面创建 PROPSH企业版TPAGE 结构以定义页面。 此结构定义页面,并包含指向对话框模板和任何位图或其他资源的指针。
  3. 将上一步中创建的 PROPSHEETPAGE 结构传递给 CreatePropertySheetPage 函数以创建页面的 HPROPSHEETPAGE 句柄
  4. 为向导创建 PROPSH企业版THEADER 结构以定义向导
  5. PROPSH企业版THEADER 结构传递给 PropertySheet 函数以显示向导
  6. 为每个页面实现对话框过程,以处理来自页面控件和向导按钮的通知消息,以及处理其他 Windows 消息传递。

创建对话框模板

向导页面有两种基本类型:外部和内部。 外部页面是简介(欢迎)和完成页面。 所有其他页面都是内部页面。

外部页面对话框模板

简介和完成页面的基本布局是相同的。 下图显示了带有占位符水印的 Wizard97 简介页面示例。

screen shot showing a wizard page with a graphic on the left, title and body text on the right, and back, next and cancel buttons at the bottom

对于 Wizard97 外部页面,对话框模板为 317x193 对话框单元。 它填充了向导的所有部分,但标题和底部包含“后退”、“下一步”和“取消”按钮的区域除外。 模板左侧(为“水印”位图保留的区域)不应包含任何控件。 水印在向导的 PROPSHEETHEADER 结构中进行指定,并自动添加到页面中。 设计资源模板时,必须为水印留出空间。

创建水印位图时,请记住,如果用户选择较大的系统字体,则对话框的大小可能会增加。 不同的语言也往往有不同的字体规格。 当页面增长时,为水印保留的区域将按比例增加。 但无法更改水印位图,也无法拉伸位图以填充更大的区域。 相反,位图以其原始大小保留在保留区域的左上部分。 较大保留区域中未被水印覆盖的部分将自动填充位图左上角像素的颜色。

如果需要针对不同的字体规格使用不同大小的水印位图,有以下两种可能的解决方案:

  • 在创建向导之前获取字体规格,并指定适当大小的水印位图。
  • 创建向导时不指定水印位图。 Wizard97 会将水印区域留空。 然后在为水印保留的区域上绘制适当大小的位图。

可以将控件放置在水印右侧的区域中,就像放置常规对话框一样。 此区域的背景色由系统确定,并且不需要你执行任何操作。 通常可在此区域中放置两个静态控件。 上方的控件包含标题并使用大粗体字体(12 磅 Verdana Bold for Wizard97)。 用于解释性文本的另一个控件使用对话框标准字体。

简介页和完成页之间的主要区别在于向导按钮和静态控件中的文本。 简介页通常包含“下一步”和“后退”按钮,并仅启用“下一步”按钮。 完成页已启用“后退”按钮,并且“下一步”按钮将替换为“完成”按钮

备注

在“Aero 向导”中,“后退”按钮被替换成描述文字栏中的箭头按钮

 

可通过向向导发送 PSM_SETFINISHTEXT 消息来修改“完成”按钮上的文本。 默认情况下,“完成”按钮不包含键盘快捷键。 要定义键盘快捷键,请在传递给 PSM_SETFINISHTEXT 的文本字符串中包含 & 符号。 例如,“&Finish”将“F”定义为键盘快捷键。

内部页面对话框模板

内部页面的外观与外部页面略有不同。 下图显示了带有占位符标头位图的 Wizard97 内部页面示例。

screen shot of a wizard page with title and subtitle text and a graphic at the top, text in the middle, and buttons on the bottom

页面顶部的标头区域由属性表处理,因此它不包括在模板中。 标头的内容在页面的 PROPSHEETPAGE 结构和向导的 PROPSHEETHEADER 结构中进行指定。 由于内部页面需要位于标头和按钮之间,因此 Wizard97 对话框模板为 317x143 对话框单元,比外部页面模板稍小。

下图显示了使用同一模板创建的 Aero 向导。

screen shot that differs from the previous one by having a title area at the top, and only next and cancel buttons at the bottom

定义向导页面

创建对话框模板和相关资源(如位图和字符串表)后,可以创建属性表页。 此过程类似于标准属性表的过程。 首先,填写 PROPSHEETPAGE 结构的适当成员。 (某些成员特定于向导。)然后调用 CreatePropertySheetPage 函数来创建页面的 HPROPSHEETPAGE 句柄

可在 PROPSHEETPAGE 结构的 dwFlags 成员中设置以下与向导相关的标志

标记 说明
PSP_HIDEHEADER 在 Wizard97 中为外部页面设置此标志。 不显示标头,但可以显示水印。
PSP_USEHEADERTITLE 为内部页面设置此标志,以便在 Wizard97 的标头区域或 Aero 向导的工作区顶部放置标题。
PSP_USEHEADERSUBTITLE 为内部页面设置此标志,以便在 Wizard97 的标头区域中放置副标题。

 

如果已设置 PSP_USEHEADERTITLE 或 PSP_USEHEADERSUBTITLE,请将标题和副标题文本分别分配给 pszHeaderTitle 和 pszHeaderSubtitle 成员。 将文本字符串分配给 PROPSHEETPAGEPROPSHEETHEADER 结构的成员时,可以分配字符串指针或使用 MAKEINTRESOURCE 宏从字符串资源分配值。 字符串资源可从向导的 PROPSHEETHEADER 结构的 hInstance 成员中指定的模块进行加载

调用 CreatePropertySheetPage 以创建页面时,请将结果分配给 HPROPSHEETPAGE 句柄数组的元素。 创建属性表时会使用此数组。 页面句柄的数组索引确定其显示的默认顺序。 创建页面的 HPROPSHEETPAGE 句柄后,可重用相同的 PROPSHEETPAGE 结构,通过向相关成员分配新值来创建下一页

创建页面的另一种方法是为每个页面使用单独的 PROPSHEETPAGE 结构,并创建一个结构数组。 创建属性表时,使用此数组代替 HPROPSHEETPAGE 句柄数组。 使用单独的 PROPSHEETPAGE 结构无需调用 CreatePropertySheetPage,但会消耗更多内存。 否则,这两种方法之间没有显著差异。

以下示例通过向 PROPSHEETPAGE 结构赋值来定义内部 Wizard97 页面。 在此示例中,页面的标题、副标题和对话框模板均由其资源 ID 标识。 然后调用 CreatePropertySheetPage 函数来创建页面的 HPROPSHEETPAGE 句柄。 由于该句柄将是第二个出现的页面,因此句柄将分配给句柄数组 ahpsp(索引为 1)

// g_hInstance is the global HINSTANCE of the application.
// IntPage1DlgProc is the dialog procedure for this page.
// ahpsp is an array of HPROPSHEETPAGE handles.

PROPSHEETPAGE psp = { sizeof(psp) };

psp.hInstance         = g_hInstance;
psp.dwFlags           = PSP_USEHEADERTITLE | PSP_USEHEADERSUBTITLE;
psp.lParam            = (LPARAM) &wizdata;
psp.pszHeaderTitle    = MAKEINTRESOURCE(IDS_TITLE1);
psp.pszHeaderSubTitle = MAKEINTRESOURCE(IDS_SUBTITLE1);
psp.pszTemplate       = MAKEINTRESOURCE(IDD_INTERIOR1);
psp.pfnDlgProc        = IntPage1DlgProc;

ahpsp[1] = CreatePropertySheetPage(&psp);

自定义页面数据

创建页面时,可使用 PROPSHEETPAGE 结构的 lParam 成员为其分配自定义数据,通常是为其分配指向用户定义结构的指针

首次选择页面时,其对话框过程会收到 WM_INITDIALOG 消息。 消息的 lParam 值指向页面的 PROPSHEETPAGE 结构的副本,你可从中检索自定义数据。 然后,可使用 SetWindowLongPtr 并以 GWL_USERDATA 作为索引参数来存储此数据,以便在后续消息中使用。 多个页面可以有一个指向相同数据的指针,并且一个页面对数据所做的任何更改都可用于对话框过程中的其他页面。

定义向导属性表

与普通属性表一样,可通过填写 PROPSHEETHEADER 结构的成员来定义向导的属性表。 可使用此结构指定组成向导的页面、它们显示的默认顺序以及几个相关参数。 然后,通过调用 PropertySheet 函数来启动向导

在 Wizard97 样式中,PROPSHEETHEADER 结构的 pszCaption 成员将被忽略。 相反,向导将显示在当前页面的对话框模板中指定的描述文字。 如果模板缺少描述文字,则会显示上一页中的描述文字。 因此,要在所有页面上显示相同的描述文字,请在简介页面的模板中指定描述文字。

在“Aero 向导”样式中,对话框描述文字取自 pszCaption

如果已为页面创建了 HPROPSHEETPAGE 句柄数组,请将该数组分配给 phpage 成员。 如果创建了一个 PROPSHEETPAGE 结构数组,请将该数组分配给 ppsp 成员,并在 dwFlags 成员中设置 PSH_PROPSHEETPAGE 标志

以下示例将值分配给 psh (一种 PROPSHEETHEADER 结构),并调用 PropertySheet 函数来启动向导。 Wizard97 样式向导具有水印和标头图形,由其资源 ID 指定。 ahpsp 数组包含所有 HPROPSHEETPAGE 句柄并定义它们显示的默认顺序

// g_hInstance is the global HINSTANCE of the application.
// ahpsp is an array of HPROPSHEETPAGE handles.

PROPSHEETHEADER psh = { sizeof(psh) };

psh.hInstance      = g_hInstance;
psh.hwndParent     = NULL;
psh.phpage         = ahpsp;
psh.dwFlags        = PSH_WIZARD97 | PSH_WATERMARK | PSH_HEADER;
psh.pszbmWatermark = MAKEINTRESOURCE(IDB_WATERMARK);
psh.pszbmHeader    = MAKEINTRESOURCE(IDB_BANNER);
psh.nStartPage     = 0;
psh.nPages         = 4;

PropertySheet(&psh);

对话框过程

向导的每个页面都需要一个对话框过程来处理 Windows 消息,特别是来自其控件和向导的通知。 几乎所有向导都能处理的三条消息是 WM_INITDIALOGWM_DESTROYWM_NOTIFY

在显示页面之前以及单击向导的任何按钮时都会收到 WM_NOTIFY 消息。 消息的 lParam 参数是指向 NMHDR 标头结构的指针。 通知的 ID 包含在结构的 code 成员中。 大多数向导都需要处理的四条通知如下所示:

代码 说明
PSN_SETACTIVE 在显示页面之前发送。
PSN_WIZBACK 单击“后退”按钮时发送
PSN_WIZNEXT 单击“下一步”按钮时发送
PSN_WIZFINISH 单击“完成”按钮时发送

 

处理 WM_INITDIALOG 和 WM_DESTROY

当页面首次显示时,其对话框过程将收到 WM_INITDIALOG 消息。 处理此消息使向导能够执行任何所需的初始化任务,例如存储自定义数据或设置字体。

当属性表被销毁时,你会收到 WM_DESTROY 消息。 向导由系统自动销毁,但处理此消息可执行任何所需的清理。

处理 PSN_SETACTIVE

每次页面即将变得可见时都会发送 PSN_SETACTIVE 通知代码。 第一次访问页面时,PSN_SETACTIVE 遵循 WM_INITDIALOG 消息。 如果随后重新访问该页面,则该页面将只收到 PSN_SETACTIVE 通知。 通常会处理此通知以初始化页面数据并启用适当的按钮。

默认情况下,向导将显示“后退”、“下一步”和“取消”按钮,并启用所有按钮。 要禁用按钮或显示“完成”而不是“下一步”,必须发送 PSM_SETWIZBUTTONS 消息。 发送此消息后,即使选择了新页面,按钮的状态也会保留,直到被另一条 PSM_SETWIZBUTTONS 消息修改为止。 通常,所有 PSN_SETACTIVE 处理程序都会发送此消息以确保每个页面的按钮状态都正确。

可随时更改此消息的按钮状态。 例如,你可能希望最初禁用“下一步”按钮。 用户输入所有必要的信息后,你可以发送另一条 PSM_SETWIZBUTTONS 消息以启用“下一步”按钮并让用户继续进入下一页

以下代码片段使用 PropSheet_SetWizButtons 宏在显示内部页面之前启用内部页面上的“后退”和“下一步”按钮

case WM_NOTIFY :
    {
        LPNMHDR pnmh = (LPNMHDR)lParam;
        
        switch(pnmh->code)
        {
        
        ...
        
        case PSN_SETACTIVE :
        
            ...
            
            // This is an interior page.
            PropSheet_SetWizButtons(hwnd, PSWIZB_NEXT | PSWIZB_BACK);
            
            ...
        }
    ...
    
    }

处理 PSN_WIZNEXT、PSNWIZBACK 和 PSN_WIZFINISH

单击“下一步”或“后退”按钮时,你将收到 PSN_WIZNEXTPSN_WIZBACK 通知代码。 默认情况下,向导将按照创建属性表时定义的顺序自动转到下一页或上一页。 处理这些通知的一个常见原因是防止用户切换页面或替代默认页面顺序。

要防止用户切换页面,请处理按钮通知,调用 SetWindowLong 函数并将 DWL_MSGRESULT 值设置为 –1,然后返回 TRUE。 例如:

case PSN_WIZNEXT :

        ...
        
        // Do not go to the next page yet.
        SetWindowLong(hwnd, DWL_MSGRESULT, -1);
        
        return TRUE;
        
        ...

要替代标准顺序并转到特定页面,请调用 SetWindowLong 并将 DWL_MSGRESULT 值设置为页面的对话框资源 ID,然后返回 TRUE。 例如:

case PSN_WIZNEXT :

        ...
        
        // Go straight to the completion page.
        SetWindowLong(hwnd, DWL_MSGRESULT, IDD_FINISH);
        
        return TRUE;
        
        ...

单击“完成”或“取消”按钮时,你将分别收到 PSN_WIZFINISHPSN_RESET 通知代码。 单击这些按钮中的任何一个时,系统会自动销毁该向导。 但是,如果需要在向导被销毁之前执行清理任务,你可以处理这些通知。 要防止向导在收到 PSN_WIZFINISH 通知时被破坏,请调用 SetWindowLong 并将 DWL_MSGRESULT 值设置为 TRUE,然后返回 TRUE。 例如:

case PSN_WIZFINISH :

        ...
        
        // Not finished yet.
        SetWindowLong(hwnd, DWL_MSGRESULT, TRUE);
        
        return TRUE;
        
        ...

后向兼容性向导

上一节假设你要为具有版本 5 或更高版本的通用控件的系统实现向导。

如果要为具有早期版本的通用控件的系统编写向导,则上一节讨论的许多功能将不可用。 Wizard97 样式使用的 PROPSHEETHEADERPROPSHEETPAGE 结构的许多成员仅受通用控件版本 5 及更高版本支持。 但仍可实现后向兼容性向导,并且外观与 Wizard97 样式类似。 为此,必须显式实现以下内容:

  • 将水印图形添加到简介和完成页面的对话框模板。
  • 使所有模板的大小都相同。 内部页面没有单独的系统定义的标头区域。
  • 在模板上显式创建内部页面的标头区域。
  • 请勿使用标头图形,因为如果向导更改大小,它可能会与标题或副标题冲突。

有关后向兼容性向导的进一步讨论,请参阅后向兼容性向导 97

备注

有关 Wizard97 设计问题的完整讨论,请参阅 Windows SDK 中其他地方的 Wizard97 规范。 本文档提供了有关对话框尺寸、位图尺寸和颜色以及控件放置等方面的指南。

使用属性表

Windows 通用控件演示 (CppWindowsCommonControls)