关于属性表

属性表是一个允许用户查看和编辑项目属性的窗口。 例如,电子表格应用程序可以使用属性表来让用户设置单元格的字体和边框属性,或者查看和设置磁盘驱动器、打印机或鼠标等设备的属性。

本部分讨论了以下主题。

属性表基础知识

要在应用程序中实施属性表,请在项目中包含 Prsht.h 头文件。 Prsht.h 包含与属性表一起使用的所有标识符。

一个属性表包含一个或多个重叠的子窗口(称为页面),每个子窗口都包含用于设置一组相关属性的控制窗口。 例如,页面可以包含用于设置项目字体属性的控件,包括字体样式、点大小、颜色等。 每个页面都有一个选项卡,用户可以选择该选项卡,将页面置于属性表的前景。 例如,“日期-时间”控制面板应用程序会显示以下属性表。

screen shot of a property sheet with two tabs, one of which shows a clock and a monthly calendar control

带有多个选项卡页面的标准属性表让用户能够随意访问所有的属性。 如果按顺序设置属性更合适,则可以使用向导

属性表对话框

属性表及其包含的页面实际上就是对话框。 属性表是一个系统定义的对话框,用于管理页面并为其提供一个通用容器。 属性表对话框可以是模式的,也可以是无模式的。 它包括一个框架、一个标题栏和四个按钮:确定取消应用和(可选)帮助。 当用户单击按钮时,页面的对话框程序会以 WM_NOTIFY 消息的形式接收通知代码。

注意

本节中的信息并非全部适用于向导,因为向导的外观和行为会略有不同。 例如,向导有一组不同的按钮,但没有选项卡。 有关详细信息,请参阅创建向导

属性表中的每个页面都是应用程序定义的无模式对话框,用于管理用于查看和编辑项目属性的控制窗口。 提供用于创建每个页面的对话框模板,以及管理控件和设置相应项目属性的对话框过程。

当页面激活或取消激活时,以及当用户单击确定取消应用帮助按钮时,属性表会向页面的对话框过程发送通知代码。 通知会以 WM_NOTIFY 消息的形式发送。 lParam 参数是 NMHDR 结构的地址,其中包含属性表对话框的窗口句柄。

某些通知代码要求页面返回 TRUEFALSE 以响应 WM_NOTIFY 消息。 为此,页面必须使用 SetWindowLong 函数将页面对话框的 DWL_MSGRESULT 值设置为 TRUEFALSE

页面

属性页必须至少包含一个页面,但不能超过 Windows 头文件中定义的 MAXPROPPAGES 值。 每个页面都有一个从 0 开始的索引,由属性表根据页面被添加到属性表中的顺序来分配。 这些索引用于向属性表发送的消息中。

一个属性页面可以包含一个嵌套对话框。 如果这样,则必须包含顶层对话框的 WS_EX_CONTROLPARENT 样式,并使用父对话框的句柄来调用 IsDialogMessage 函数。 这样,用户就可以使用助记键和对话框导航键将焦点移至嵌套对话框中的控件。

每个页面都有相应的图标和标签。 属性表会为每个页面创建一个选项卡,并在选项卡中显示图标和标签。所有属性表页面都应使用非粗体字体。 要确保字体不是粗体,请在对话框模板中指定 DS_3DLOOK 样式。

页面的对话框过程不得调用 EndDialog 函数。 这样做会破坏整个属性表,而不仅仅是页面。

属性表页面的最小大小为横向 212 个对话框单位,纵向 114 个对话框单位。 如果页面对话框小于此单位,则页面将被放大,直至达到最小大小。 如下表所示,Prsht.h 头文件包含三组属性表页面的推荐大小。

大小 说明
PROP_SM_CXDLG 小属性表页面的宽度(以对话框单位为单位)。
PROP_SM_CYDLG 小属性表页面的高度(以对话框单位为单位)。
PROP_MED_CXDLG 中等大小的属性表页面的宽度(以对话框单位为单位)。
PROP_MED_CYDLG 中等大小的属性表页面的高度(以对话框单位为单位)。
PROP_LG_CXDLG 大属性表页面的宽度(以对话框单位为单位)。
PROP_LG_CYDLG 大属性表页面的高度(以对话框单位为单位)。

使用这些推荐大小有助于确保应用程序与其他 Microsoft Windows 应用程序在视觉上保持一致。

在 Microsoft Visual Studio 资源编辑器中,可以在“添加资源”对话框中创建适当大小的页面。 展开对话框节点,选择 IDD_PROPPAGE_LARGEIDD_PROPPAGE_MEDIUMIDD_PROPPAGE_SMALL

属性表会自动调整大小,以适应最大的页面。

属性表创建

在创建属性表之前,必须定义一个或多个页面。 这需要在 PROPSHEETPAGE 结构中填入有关页面的信息,如图标、标签、对话框模板、对话框过程等,然后在调用 CreatePropertySheetPage 函数时指定该结构的地址。 该函数会返回唯一标识页面的 HPROPSHEETPAGE 类型的句柄。

要创建属性表,请在调用 PropertySheet 函数时指定 PROPSHEETHEADER 结构的地址。 该结构定义了属性表的图标和标题,还包括 HPROPSHEETPAGE 句柄数组的地址,可以使用 CreatePropertySheetPage 获得该地址。 在 PropertySheet 创建属性表时,它会包含数组中标识的页面。 页面在属性表中的显示顺序与它们在数组中的显示顺序相同。

为属性表分配页面的另一种方法是指定 PROPSHEETPAGE 结构数组,而不是 HPROPSHEETPAGE 句柄数组。 在这种情况下,PropertySheet 会在将页面添加到属性表之前为其创建句柄。

在创建页面时,其对话框过程会收到 WM_INITDIALOG 消息。 消息的 lParam 参数是指向 PROPSHEETPAGE 结构副本的指针,该结构在创建页面时定义。 具体而言,在创建页面时,结构的 lParam 成员可用于将应用程序定义的信息传递给对话框过程。 除 lParam 成员外,此结构必须被视为只读。 修改 lParam 以外的任何内容都会产生不可预知的后果。

当系统随后将页面的 PROPSHEETPAGE 结构副本传递给应用程序时,它将使用相同的指针。 结构上的任何变化都将被传递。 由于系统会忽略 lParam 成员,因此可以对其进行修改,以便向应用程序的其他部分发送信息。 例如,可以使用 lParam 将信息传递给页面的 PropSheetPageProc 回调函数。

PropertySheet 会自动设置属性表的大小和初始位置。 该位置基于所有者窗口的位置,并且大小基于创建属性表时在页面数组中指定的最大页面。 如果希望页面与属性表底部四个按钮的宽度保持一致,请将最宽页面的宽度设置为 190 对话框单位。

属性表的大小是根据资源文件中对话框模板的 widthheight 属性计算得出的。 有关更多详细信息,请参阅 DIALOG 资源DIALOGEX 资源。 但请注意,出于兼容性考虑,尺寸是相对于 MS Shell Dlg 字体而并非页面使用的字体计算得出的。 如果设计的页面使用其他字体,则可以遵循以下建议之一。

  • 调整对话框模板的尺寸,以补偿 MS Shell Dlg 字体与页面实际使用的字体之间的大小差异。 例如,如果选择的字体宽度是 MS Shell Dlg 的两倍,那么将对话框模板的宽度属性设为正常使用宽度的两倍。
  • 使用 DIALOGEX 模板并设置 DS_SHELLFONT 对话框样式。 在这种情况下,属性表管理器会根据对话框模板使用的字体来解释对话框模板的尺寸。

添加和删除页面

在创建属性表后,应用程序可通过发送 PSM_ADDPAGE 消息,在现有页面集的末尾添加一个页面。 要在现有页面之间插入页面,请发送 PropSheet_InsertPage 消息。 请注意,创建属性页后,属性页的大小将无法更改。 任何添加或插入的页面都不得大于属性表中当前最大的页面。 要删除页面,请发送 PSM_REMOVEPAGE 消息。

在定义页面时,可以指定 PropSheetPageProc 回调函数的地址,以便属性表在创建或删除页面时调用。 使用 PropSheetPageProc 可以对单个页面执行初始化和清理操作。

注意

在属性表操作页面列表时,会出现一些消息和一个函数调用。 在此操作过程中,尝试修改页面列表将产生不可预知的结果。 在实现 PropSheetPageProc 时,或在处理以下通知和 Windows 消息时,请勿添加、插入或删除页面。

如果在处理这些消息或 PropSheetPageProc 正在运行时需要修改属性表页,请发布专用 Windows 消息。 直到属性表管理器完成其任务后,应用程序才会收到该消息,此时就可以安全地修改页面列表了。

当销毁一个属性表时,添加到该属性表中的所有页面会被自动销毁。 销毁页面的顺序与创建页面时使用的数组中指定的顺序相反。 要销毁由 CreatePropertySheetPage 函数创建但未添加到属性表的页面,请使用 DestroyPropertySheetPage 函数。

属性表标题和页面标签

可以在用于创建属性表的 PROPSHEETHEADER 结构中指定属性表的标题。 如果 dwFlags 成员包含 PSH_PROPTITLE 值,属性表就会根据版本添加后缀 “Properties”或前缀“Properties for”。 在创建属性表后,可以使用 PSM_SETTITLE 消息来更改标题。 在 Aero 向导中,此消息可用于动态更改内部页面的标题。

默认情况下,属性表会使用对话框模板中指定的名称字符串作为页面标签。 可以在定义页面的 PROPSHEETPAGE 结构的 dwFlags 成员中包含 PSP_USETITLE 值,从而覆盖名称字符串。 在指定 PSP_USETITLE 时,pszTitle 成员必须包含页面的标签字符串的地址。

页面激活

一个属性表一次只能激活一个页面。 激活的页面位于重叠页面堆栈的前景。 用户通过选择页面的选项卡来激活该页面;而应用程序通过使用 PSM_SETCURSEL 消息来激活页面。

属性表会向即将取消激活的页面发送 PSN_KILLACTIVE 通知代码。 作为响应,页面必须验证用户对页面所做的任何更改。 如果页面在取消激活之前需要用户输入更多信息,请使用 SetWindowLong 函数将页面的 DWL_MSGRESULT 值设置为 TRUE。 此外,页面必须显示一个信息框,用于描述问题并提供建议的操作。 在可以取消激活时,将 DWL_MSGRESULT 设置为 FALSE

在获取激活的页面可见之前,属性表会向页面发送 PSN_SETACTIVE 通知代码。 页面必须通过初始化其控制窗口来进行响应。

“帮助”按钮

属性表可以显示两个帮助按钮:一个是显示在框架底部确定/取消/应用按钮旁边的属性表帮助按钮,另一个是提供上下文相关帮助的标准描述文字栏按钮。

属性表“帮助”按钮为可选,可以逐页启用。 要显示一个或多个页面的属性表“帮助”按钮,请执行以下操作:

  • 在属性表的 PROPSHEETHEADER 结构的 dwFlags 成员中设置 PSH_HASHELP 标志。
  • 对于将显示“帮助”按钮的每个页面,请在页面 PROPSHEETPAGE 结构的 dwFlags 成员中设置 PSP_HASHELP 标志。

当用户单击“帮助”按钮时,活动页面将收到一个 PSN_HELP 通知代码。 页面必须通过显示“帮助”信息来进行响应(通常是通过调用 WinHelp 函数)。

删除描述文字栏帮助按钮

默认情况下会显示描述文字栏“帮助”按钮,这样就可以随时为“确定/取消/应用”按钮提供上下文相关的帮助。 但是,必要时可以删除此按钮。 要删除属性表的描述文字栏“帮助”按钮,请执行以下操作:

  • 对于版本 5.80 之前的常用控件版本,必须实现属性表回调函数
  • 对于版本 5.80 以及更高版本的常用控件,只需在属性表 PROPSHEETHEADER 结构的 dwFlags 成员中设置 PSH_NOCONTEXTHELP 标志即可。 但是,如果需要与早期的常用控件版本向后兼容,则必须实现回调函数。

要实现一个属性表回调函数,用于移除描述文字栏“帮助”按钮:

  • 在属性表的 PROPSHEETHEADER 结构的 dwFlags 成员中设置 PSH_USECALLBACK 标志。
  • 设置 PROPSHEETHEADER 结构的 pfnCallBack 成员以指向回调函数。
  • 实现回调函数。 当此函数接收到 PSCB_PRECREATE 消息时,它还将接收到指向属性表对话框模板的指针。 删除此模板中的 DS_CONTEXTHELP 样式。

以下示例说明了如何实现此类调函数:

int CALLBACK RemoveContextHelpProc(HWND hwnd, UINT message, LPARAM lParam)
{
    switch (message) 
    {
    case PSCB_PRECREATE:
        // Remove the DS_CONTEXTHELP style from the
        // dialog box template
        if (((LPDLGTEMPLATEEX)lParam)->signature ==    
           0xFFFF)
           {
            ((LPDLGTEMPLATEEX)lParam)->style 
            &= ~DS_CONTEXTHELP;
        }
        else {
            ((LPDLGTEMPLATE)lParam)->style 
            &= ~DS_CONTEXTHELP;
        }
        return TRUE;
    }
    return TRUE;
}

如果未定义 DLGTEMPLATEEX 结构,请包含以下声明:

#include <pshpack1.h>

typedef struct DLGTEMPLATEEX
{
    WORD dlgVer;
    WORD signature;
    DWORD helpID;
    DWORD exStyle;
    DWORD style;
    WORD cDlgItems;
    short x;
    short y;
    short cx;
    short cy;
} DLGTEMPLATEEX, *LPDLGTEMPLATEEX;

#include <poppack.h>

确定、取消和应用按钮

确定应用按钮与此类似;它们都指示属性表的页面验证和应用用户所做的属性更改。 唯一的区别在于,单击确定按钮会导致属性表在应用更改后被销毁。

当用户单击确定应用按钮时,属性表会向活动页面发送 PSN_KILLACTIVE 通知,使其有机会验证用户的更改。 如果更改有效,页面必须调用 SetWindowLong 函数,并将 DWL_MSGRESULT 值设置为 FALSE。 如果用户的更改无效,页面必须将 DWL_MSGRESULT 设置为 TRUE,并显示一个对话框告知用户问题所在。 页面将保持活动状态,直到它响应 PSN_KILLACTIVE 消息而将 DWL_MSGRESULT 设置为 FALSE

在页面通过将 DWL_MSGRESULT 设置为 FALSE 来响应 PSN_KILLACTIVE 通知后,属性表将向每个页面发送 PSN_APPLY 通知。 在页面收到此通知后,必须将新属性应用到相应的项目中。 要向属性表表明更改对页面有效,请调用 SetWindowLong 并将 DWL_MSGRESULT 设置为 PSNRET_NOERROR。 如果更改对页面无效,则返回错误。 这样做可以防止属性表被销毁,并将焦点返回到收到 PSN_APPLY 通知的页面或按下应用按钮时拥有焦点的页面。 要返回错误并指明哪个页面将接收焦点,请将 DWL_MSGRESULT 设置为以下值之一。

  • PSNRET_INVALID。 属性表将不会被销毁,而焦点将返回此页面。
  • PSNRET_INVALID_NOCHANGEPAGE。 属性表不会被销毁,而焦点会返回到按下按钮时拥有焦点的页面。

应用程序可以使用 PSM_APPLY 消息来模拟选择应用按钮。

当页面处于活动状态时,应用按钮最初会被禁用,从而表明还没有任何属性更改需要应用。 当页面通过其中一个控件接收到用户编辑了一个属性的输入时,页面必须向属性表发送 PSM_CHANGED 消息。 该消息会使属性表启用应用按钮。 如果用户随后单击应用取消按钮,则页面必须重新初始化其控件,然后发送 PSM_UNCHANGED 消息,以便再次禁用应用按钮。

有时,应用按钮会导致页面对属性表进行更改,而且更改无法撤销。 发生这种情况时,页面必须向属性表发送 PSM_CANCELTOCLOSE 消息。 该消息会让属性表将确定按钮的文本更改为“关闭”,从而表示已应用的更改无法取消。

有时,页面对系统配置的更改需要重启 Windows 或重启系统后才能生效。 进行此类更改后,页面必须向属性表发送 PSM_RESTARTWINDOWSPSM_REBOOTSYSTEM 消息。 这些消息会导致 PropertySheet 函数在属性表被销毁后返回 ID_PSRESTARTWINDOWSID_PSREBOOTSYSTEM 值。

当用户单击取消按钮时,属性表会向所有页面发送 PSN_RESET 通知代码,从而表明属性表单即将被销毁。 页面必须使用通知来执行清理操作。

向导

向导是一种特殊属性表。 向导的设计目的是按照应用程序控制的顺序逐个显示页面。 用户不是通过单击选项卡从一组页面中进行选择,而是通过单击按钮在序列中逐页向前或向后浏览。 例如,以下屏幕截图显示了“添加硬件”向导的欢迎页面:

screen shot of the welcome page of a wizard

以下屏幕截图显示了 Aero 向导的第一页,这是 Windows Vista 引入的新样式。

screen shot of the first page of an aero wizard

有关向导的完整讨论,请参阅创建向导