65.9K
CodeProject 正在变化。 阅读更多。
Home

WTL for MFC Programmers, Part VIII - 属性表和向导

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.94/5 (66投票s)

2003 年 9 月 13 日

15分钟阅读

viewsIcon

429025

downloadIcon

5526

创建 WTL 中属性表和向导的指南

目录

引言

在 Windows 95 引入属性表作为通用控件之前,属性表就已经是一种流行的呈现选项的方式了。向导通常用于指导用户安装软件或其他复杂任务。WTL 为创建这两种类型的属性表提供了良好的支持,并且允许你使用我们之前介绍的所有与对话框相关的功能,如 DDX 和 DDV。在本文中,我将演示如何创建基本的属性表和向导,以及如何处理属性表发送的事件和通知消息。

WTL 属性表类

有两个类结合起来实现了属性表:CPropertySheetWindowCPropertySheetImpl。两者都在 atldlgs.h 头文件中定义。CPropertySheetWindow 是一个窗口接口类(即它派生自 CWindow),而 CPropertySheetImpl 具有消息映射并实际实现窗口。这与基本的 ATL 窗口类类似,其中 CWindowCWindowImpl 一起使用。

CPropertySheetWindow 包含各种 PSM_* 消息的包装器,例如 SetActivePageByID(),它包装了 PSM_SETCURSELIDCPropertySheetImpl 管理一个 PROPSHEETHEADER 结构和一个 HPROPSHEETPAGE 数组。CPropertySheetImpl 还具有设置一些 PROPSHEETHEADER 字段以及添加和删除页面的方法。你可以通过访问 m_psh 成员变量来直接访问 PROPSHEETHEADER

最后,CPropertySheetCPropertySheetImpl 的一个特例,如果你不需要完全自定义属性表,可以使用它。

CPropertySheetImpl 方法

以下是 CPropertySheetImpl 的一些重要方法。由于许多方法只是窗口消息的包装器,因此我不会在此提供详尽的列表,但你可以查看 atldlgs.h 来查看方法的完整列表。

CPropertySheetImpl(_U_STRINGorID title = (LPCTSTR) NULL,
                   UINT uStartPage = 0, HWND hWndParent = NULL)

CPropertySheetImpl 构造函数允许你立即指定一些通用属性,这样就不必稍后调用其他方法来设置它们。title 指定属性表标题栏中使用的文本。_U_STRINGorID 是一个 WTL 工具类,它允许你传递 LPCTSTR 或字符串资源 ID。例如,以下两行都将工作

  CPropertySheet mySheet ( IDS_SHEET_TITLE );
  CPropertySheet mySheet ( _T("My prop sheet") );

如果 IDS_SHEET_TITLE 是字符串表中字符串的 ID。uStartPage 是属性表首次可见时应活动的页面的零基索引。hWndParent 设置属性表的父窗口。

BOOL AddPage(HPROPSHEETPAGE hPage)
BOOL AddPage(LPCPROPSHEETPAGE pPage)

将属性页添加到属性表中。如果页面已创建,你可以将其句柄(一个 HPROPSHEETPAGE)传递给第一个重载。更常见的方法是使用第二个重载。使用该版本,你可以设置一个 PROPSHEETPAGE 结构(你可以使用 CPropertyPageImpl 来完成,稍后介绍),CPropertySheetImpl 将为你创建和管理该页面。

BOOL RemovePage(HPROPSHEETPAGE hPage)
BOOL RemovePage(int nPageIndex)

从属性表中删除页面。你可以传递页面的句柄或其零基索引。

BOOL SetActivePage(HPROPSHEETPAGE hPage)
BOOL SetActivePage(int nPageIndex)

设置属性表中的活动页面。你可以传递要变为活动页面的页面的句柄或零基索引。你可以在显示属性表之前调用此方法来设置当属性表首次可见时哪个页面将是活动的。

void SetTitle(LPCTSTR lpszText, UINT nStyle = 0)

设置用于属性表标题栏的文本。nStyle 可以是 0 或 PSH_PROPTITLE。如果它是 PSH_PROPTITLE,则将该样式位添加到属性表中,这会导致在传入 lpszText 的文本前加上“Properties for”字样。

void SetWizardMode()

设置 PSH_WIZARD 样式,这会将属性表转换为向导。你必须在显示属性表之前调用此方法。

void EnableHelp()

设置 PSH_HASHELP 样式,这会在属性表中添加一个“帮助”按钮。请注意,你还需要为提供帮助的每个页面启用帮助才能生效。

INT_PTR DoModal(HWND hWndParent = ::GetActiveWindow())

创建并显示模态属性表。返回值大于 0 表示成功,有关返回值的完整说明,请参阅 PropertySheet() API 的文档。如果发生错误且无法创建属性表,DoModal() 返回 -1。

HWND Create(HWND hWndParent = NULL)

创建并显示非模态属性表,并返回其窗口句柄。如果发生错误且无法创建属性表,Create() 返回 NULL。

WTL 属性页类

封装属性页的 WTL 类的工作方式类似于属性表类。有一个窗口接口类 CPropertyPageWindow 和一个实现类 CPropertyPageImplCPropertyPageWindow 非常小,主要包含调用父属性表方法的实用函数。

CPropertyPageImpl 派生自 ATL 类 CDialogImplBaseT,因为页面是基于对话框资源构建的。这意味着我们在对话框中使用的所有 WTL 功能也可用于属性表,如 DDX 和 DDV。CPropertyPageImpl 主要有两个用途:它管理一个 PROPSHEETPAGE 结构(存储在成员变量 m_psp 中),并处理 PSN_* 通知消息。对于非常简单的属性页,你可以使用 CPropertyPage 类。这仅适用于根本不与用户交互的页面,例如“关于”页面,或向导中的介绍页面。

你还可以创建托管 ActiveX 控件的页面。首先在 stdafx.h 中包含 atlhost.h。对于页面,你使用 CAxPropertyPageImpl 而不是 CPropertyPageImpl。对于托管 ActiveX 控件的简单页面,你可以使用 CAxPropertyPage 而不是 CPropertyPage

CPropertyPageWindow 方法

CPropertyPageWindow 最重要的方法是 GetPropertySheet()

CPropertySheetWindow GetPropertySheet()

此方法获取页面父窗口(属性表)的 HWND,并将 CPropertySheetWindow 附加到该 HWND。然后将新的 CPropertySheetWindow 返回给调用者。请注意,这仅创建一个临时对象;它 **不** 返回实际用于创建属性表的 CPropertySheetCPropertySheetImpl 对象的指针或引用。如果你正在使用自己的 CPropertySheetImpl 派生类并且需要访问属性表对象中的数据成员,这一点很重要。

其余成员只是调用 CPropertySheetWindow 函数,这些函数包装了 PSM_* 消息。

BOOL Apply()
void CancelToClose()
void SetModified(BOOL bChanged = TRUE)
LRESULT QuerySiblings(WPARAM wParam, LPARAM lParam)
void RebootSystem()
void RestartWindows()
void SetWizardButtons(DWORD dwFlags)

例如,在 CPropertyPageImpl 派生类中,你可以调用

  SetWizardButtons ( PSWIZB_BACK | PSWIZB_FINISH );

而不是

CPropertySheetWindow wndSheet;
 
  wndSheet = GetPropertySheet();
  wndSheet.SetWizardButtons ( PSWIZB_BACK | PSWIZB_FINISH );

CPropertyPageImpl 方法

CPropertyPageImpl 管理一个 PROPSHEETPAGE 结构,即公共成员 m_pspCPropertyPageImpl 还有一个 operator PROPSHEETPAGE* 转换器,因此你可以将 CPropertyPageImpl 传递给接受 LPPROPSHEETPAGELPCPROPSHEETPAGE 的方法,例如 CPropertySheetImpl::AddPage()

CPropertyPageImpl 构造函数允许你设置页面标题,即显示在页面选项卡上的文本。

CPropertyPageImpl(_U_STRINGorID title = (LPCTSTR) NULL)

如果你需要手动创建页面,而不是让属性表来创建,你可以调用 Create()

HPROPSHEETPAGE Create()

Create() 只是使用 m_psp 作为参数调用 Win32 API CreatePropertySheetPage()。只有当你向已创建的属性表添加页面时,或者当你创建要传递给其他不受你控制的属性表的页面时(例如,属性表处理程序 Shell 扩展),才需要调用 Create()

有三种方法可以设置页面上的各种标题文本。

void SetTitle(_U_STRINGorID title)
void SetHeaderTitle(LPCTSTR lpstrHeaderTitle)
void SetHeaderSubTitle(LPCTSTR lpstrHeaderSubTitle)

第一个更改页面选项卡上的文本。另外两个在 Wizard97 风格的向导中用于设置属性页区域上方的标题文本。

void EnableHelp()

m_psp 中设置 PSP_HASHELP 标志,以便在页面处于活动状态时启用“帮助”按钮。

处理通知消息

CPropertyPageImpl 有一个处理 WM_NOTIFY 的消息映射。如果通知代码是 PSN_* 值,OnNotify() 将调用特定通知的处理程序。这是通过编译时虚函数技术完成的,因此处理程序可以在派生类中轻松覆盖。

由于 WTL 3 和 7 之间的设计更改,存在两组通知处理程序。在 WTL 3 中,通知处理程序的返回值与 PSN_* 消息的返回值不同。例如,WTL 3 中 PSN_WIZFINISH 的处理程序是

  case PSN_WIZFINISH:
    lResult = !pT->OnWizardFinish();
  break;

OnWizardFinish() 预计返回 TRUE 以允许向导完成,或返回 FALSE 以阻止向导关闭。当 IE 5 通用控件添加了从 PSN_WIZFINISH 处理程序返回窗口句柄以将焦点移至该窗口的功能时,此代码中断了。WTL 3 应用程序无法使用此功能,因为所有非零值都被视为相同。

在 WTL 7 中,OnNotify() 不会更改从 PSN_* 处理程序返回的任何值。处理程序可以返回任何文档规定的合法值,并且行为将是正确的。但是,为了向后兼容,WTL 3 处理程序仍然存在,并且默认情况下会被使用。要使用 WTL 7 处理程序,你必须在包含 atldlgs.h 之前将此行添加到 stdafx.h

#define _WTL_NEW_PAGE_NOTIFY_HANDLERS

编写新代码时,没有理由不使用 WTL 7 处理程序,因此此处不涵盖 WTL 3 处理程序。

CPropertyPageImpl 为所有通知提供默认处理程序,因此你只需覆盖程序相关的处理程序。默认处理程序及其操作是

int OnSetActive() - 允许页面变为活动状态

BOOL OnKillActive() - 允许页面变为不活动状态

int OnApply() - 返回 PSNRET_NOERROR 表示应用操作成功

void OnReset() - 无操作

BOOL OnQueryCancel() - 允许取消操作

int OnWizardBack() - 转到上一页

int OnWizardNext() - 转到下一页

INT_PTR OnWizardFinish() - 允许向导完成

void OnHelp() - 无操作

BOOL OnGetObject(LPNMOBJECTNOTIFY lpObjectNotify) - 无操作

int OnTranslateAccelerator(LPMSG lpMsg) - 返回 PSNRET_NOERROR 表示消息未被处理

HWND OnQueryInitialFocus(HWND hWndFocus) - 返回 NULL 以将焦点设置到选项卡顺序中的第一个控件

创建属性表

我们的类之旅完成后,我们需要一个程序来演示如何使用它们。本文的示例项目是一个简单的 SDI 应用程序,它在客户端区域显示图像,并用颜色填充背景。可以通过选项对话框(属性表)和向导(我稍后将介绍)更改图像和颜色。

最简单的属性表,永远

使用 WTL AppWizard 创建 SDI 项目后,我们可以开始创建一个用于“关于”框的属性表。让我们从向导为我们创建的 About 对话框开始,并更改样式,使其可以作为属性页工作。

第一步是删除“确定”按钮,因为它在属性表中没有意义。在对话框的属性中,将 Style 改为 Child,将 Border 改为 Thin,并将 Disabled 设置为选中。

第二步(也是最后一步)是在 OnAppAbout() 处理程序中创建一个属性表。我们可以使用 CPropertySheetCPropertyPage 来实现。

void CMainFrame::OnAppAbout(...)
{
CPropertySheet sheet ( _T("About PSheets") );
CPropertyPage<IDD_ABOUTBOX> pgAbout;
 
  sheet.AddPage ( pgAbout );
  sheet.DoModal ( *this );
}

结果如下:

 [Simple sheet - 27K]

创建有用的属性页

由于并非所有属性表中的所有页面都像“关于”框一样简单,大多数页面都需要一个 CPropertyPageImpl 派生类,因此我们现在来看一个这样的类。我们将创建一个新的属性页,其中包含客户端区域背景图形的设置。这是对话框

 [Background options - 4K]

此对话框具有与 About 页面相同的样式。我们需要一个与之配套的新类,称为 CBackgroundOptsPage。该类派生自 CPropertyPageImpl,因为它是一个属性页,并且派生自 CWinDataExchange 以启用 DDX。

class CBackgroundOptsPage :
  public CPropertyPageImpl<CBackgroundOptsPage>,
  public CWinDataExchange<CBackgroundOptsPage>
{
public:
  enum { IDD = IDD_BACKGROUND_OPTS };
 
  // Construction
  CBackgroundOptsPage();
  ~CBackgroundOptsPage();
 
  // Maps
  BEGIN_MSG_MAP(CBackgroundOptsPage)
    MSG_WM_INITDIALOG(OnInitDialog)
    CHAIN_MSG_MAP(CPropertyPageImpl<CBackgroundOptsPage>)
  END_MSG_MAP()
 
  BEGIN_DDX_MAP(CBackgroundOptsPage)
    DDX_RADIO(IDC_BLUE, m_nColor)
    DDX_RADIO(IDC_ALYSON, m_nPicture)
  END_DDX_MAP()
 
  // Message handlers
  BOOL OnInitDialog ( HWND hwndFocus, LPARAM lParam );
 
  // Property page notification handlers
  int OnApply();
 
  // DDX variables
  int m_nColor, m_nPicture;
};

此类中需要注意的事项

  • 有一个名为 IDD 的公共成员,其中包含关联的对话框资源 ID。
  • 消息映射类似于 CDialogImpl 类。
  • 消息映射将消息链接到 CPropertyPageImpl,以便处理与属性表相关的通知消息。
  • 有一个 OnApply() 处理程序,用于在用户在属性表中单击“确定”时保存用户的选择。

OnApply() 非常简单,它调用 DoDataExchange() 来更新 DDX 变量,然后返回一个代码,指示属性表是应该关闭还是不关闭。

int CBackgroundOptsPage::OnApply()
{
  return DoDataExchange(true) ? PSNRET_NOERROR : PSNRET_INVALID;
}

我们将添加一个 Tools|Options 菜单项来调出属性表,并为该命令添加到视图类的处理程序。该处理程序像以前一样创建属性表,但将新的 CBackgroundOptsPage 添加到属性表中。

void CPSheetsView::OnOptions ( UINT uCode, int nID, HWND hwndCtrl )
{
CPropertySheet sheet ( _T("PSheets Options"), 0 );
CBackgroundOptsPage pgBackground;
CPropertyPage<IDD_ABOUTBOX> pgAbout;
 
  pgBackground.m_nColor = m_nColor;
  pgBackground.m_nPicture = m_nPicture;
 
  sheet.m_psh.dwFlags |= PSH_NOAPPLYNOW|PSH_NOCONTEXTHELP;
 
  sheet.AddPage ( pgBackground );
  sheet.AddPage ( pgAbout );
 
  if ( IDOK == sheet.DoModal() )
    SetBackgroundOptions ( pgBackground.m_nColor,
                           pgBackground.m_nPicture );
}

sheet 构造函数现在有一个第二个参数 0,表示索引为 0 的页面最初是可见的。你可以将其更改为 1,以便在属性表首次出现时,“关于”页面可见。由于这只是演示代码,我将偷懒,让 CBackgroundOptsPage 变量连接到公共单选按钮。视图存储这些变量中的当前选项,如果用户在属性表中单击“确定”,则保存新值。

如果用户单击“确定”,DoModal() 返回 IDOK,视图将使用新图像和颜色重绘自身。以下是一些屏幕截图,展示了不同的视图。

 [Alyson background - 35K]  [Strong Bad background - 9K]

创建更好的属性表类

OnOptions() 处理程序可以正常创建属性表,但其中有很多设置和初始化代码,这实际上不应该是 CMainFrame 的职责。更好的方法是创建一个派生自 CPropertySheetImpl 的类来处理这些任务。

#include "BackgroundOptsPage.h"
 
class COptionsSheet : public CPropertySheetImpl<COptionsSheet>
{
public:
  // Construction
  COptionsSheet(_U_STRINGorID title = (LPCTSTR) NULL, 
                UINT uStartPage = 0, HWND hWndParent = NULL);
 
  // Maps
  BEGIN_MSG_MAP(COptionsSheet)
    CHAIN_MSG_MAP(CPropertySheetImpl<COptionsSheet>)
  END_MSG_MAP()
 
  // Property pages
  CBackgroundOptsPage         m_pgBackground;
  CPropertyPage<IDD_ABOUTBOX> m_pgAbout;
};

有了这个类,我们就封装了属性表中包含哪些页面的详细信息,并将它们移到了属性表类本身中。构造函数负责将页面添加到属性表中,并设置任何其他必要的标志。

COptionsSheet::COptionsSheet (
  _U_STRINGorID title, UINT uStartPage, HWND hWndParent ) :
    CPropertySheetImpl<COptionsSheet>(title, uStartPage, hWndParent)
{
  m_psh.dwFlags |= PSH_NOAPPLYNOW|PSH_NOCONTEXTHELP;
 
  AddPage ( m_pgBackground );
  AddPage ( m_pgAbout );
}

因此,OnOptions() 处理程序变得更简单一些。

void CPSheetsView::OnOptions ( UINT uCode, int nID, HWND hwndCtrl )
{
COptionsSheet sheet ( _T("PSheets Options"), 0 );
 
  sheet.m_pgBackground.m_nColor = m_nColor;
  sheet.m_pgBackground.m_nPicture = m_nPicture;
 
  if ( IDOK == sheet.DoModal() )
    SetBackgroundOptions ( sheet.m_pgBackground.m_nColor,
                           sheet.m_pgBackground.m_nPicture );
}

创建向导

创建向导,不出所料,与创建属性表类似。需要做更多工作来启用 BackNext 按钮;就像 MFC 属性页一样,你需要覆盖 OnSetActive() 并调用 SetWizardButtons() 来启用适当的按钮。我们将从一个简单的介绍页面开始,其 ID 为 IDD_WIZARD_INTRO

 [Intro page - 3K]

请注意,该页面没有标题文本。由于向导中的每个页面通常都有相同的标题,我更喜欢在 CPropertySheetImpl 构造函数中设置文本,并让每个页面使用相同的字符串资源。这样,我只需更改一个字符串,所有页面都会反映出更改。

此页面的实现由 CWizIntroPage 类完成。

class CWizIntroPage : public CPropertyPageImpl<CWizIntroPage>
{
public:
  enum { IDD = IDD_WIZARD_INTRO };
 
  // Construction
  CWizIntroPage();
 
  // Maps
  BEGIN_MSG_MAP(COptionsWizard)
    CHAIN_MSG_MAP(CPropertyPageImpl<CWizIntroPage>)
  END_MSG_MAP()
 
  // Notification handlers
  int OnSetActive();
};

构造函数通过引用字符串资源 ID 设置页面的文本。

CWizIntroPage::CWizIntroPage() :
  CPropertyPageImpl<CWizIntroPage>(IDS_WIZARD_TITLE)
{
}

字符串 IDS_WIZARD_TITLE(“PSheets Options Wizard”)将显示在向导的标题栏中,当此页面是当前页面时。OnSetActive() 仅启用 Next 按钮。

int CWizIntroPage::OnSetActive()
{
  SetWizardButtons ( PSWIZB_NEXT );
  return 0;
}

为了实现向导,我们将创建一个类 COptionsWizard,并在应用程序菜单中添加一个 Tools|Wizard 菜单项。COptionsWizard 构造函数与 COptionsSheet 非常相似,它设置任何必要的样式位或标志,并将页面添加到属性表中。

class COptionsWizard : public CPropertySheetImpl<COptionsWizard>
{
public:
  // Construction
  COptionsWizard ( HWND hWndParent = NULL );
 
  // Maps
  BEGIN_MSG_MAP(COptionsWizard)
    CHAIN_MSG_MAP(CPropertySheetImpl<COptionsWizard>)
  END_MSG_MAP()
 
  // Property pages
  CWizIntroPage m_pgIntro;
};
 
COptionsWizard::COptionsWizard ( HWND hWndParent ) :
  CPropertySheetImpl<COptionsWizard> ( 0U, 0, hWndParent )
{
  SetWizardMode();
  AddPage ( m_pgIntro );
}

然后,Tools|Wizard 菜单的处理程序如下所示。

void CPSheetsView::OnOptionsWizard ( UINT uCode, int nID, HWND hwndCtrl )
{
COptionsWizard wizard;
 
  wizard.DoModal ( GetTopLevelParent() );
}

以下是向导的实际运行截图。

 [Wizard on intro page - 10K]

添加更多页面,处理 DDV

为了使这个向导更有用,我们将添加一个新页面来设置视图的背景颜色。此页面还将包含一个复选框,用于演示如何处理 DDV 失败并阻止用户继续向导。这是新页面,其 ID 为 IDD_WIZARD_BKCOLOR

 [Color selection wizard page - 4K]

此页面的实现位于 CWizBkColorPage 类中。以下是与此相关的部分。

class CWizBkColorPage :
  public CPropertyPageImpl<CWizBkColorPage>,
  public CWinDataExchange<CWizBkColorPage>
{
public:
    //...
  BEGIN_DDX_MAP(CWizBkColorPage)
    DDX_RADIO(IDC_BLUE, m_nColor)
    DDX_CHECK(IDC_FAIL_DDV, m_bFailDDV)
  END_DDX_MAP()
 
  // Notification handlers
  int OnSetActive();
  BOOL OnKillActive();
 
  // DDX vars
  int m_nColor;
 
protected:
  bool m_bFailDDV;
};

OnSetActive() 的工作方式与介绍页面类似,它同时启用 BackNext 按钮。OnKillActive() 是一个新处理程序,它调用 DDX 然后检查 m_bFailDDV 的值。如果该变量为 true,表示复选框已选中,OnKillActive() 将阻止向导转到下一页。

int CWizBkColorPage::OnSetActive()
{
  SetWizardButtons ( PSWIZB_BACK | PSWIZB_NEXT );
  return 0;
}
 
int CWizBkColorPage::OnKillActive()
{
  if ( !DoDataExchange(true) )
    return TRUE;    // prevent deactivation
 
  if ( m_bFailDDV )
    {
    MessageBox (
        _T("Error box checked, wizard will stay on this page."),
        _T("PSheets"), MB_ICONERROR );
 
    return TRUE;    // prevent deactivation
    }
 
  return FALSE;       // allow deactivation
}

请注意,OnKillActive() 中的逻辑也可以放在 OnWizardNext() 中,因为两个处理程序都可以阻止向导停留在当前页面。区别在于,OnKillActive() 在用户单击 BackNext 时被调用,而 OnWizardNext() 仅在用户单击 Next 时被调用(顾名思义)。OnWizardNext() 也用于其他目的;如果需要跳过某些页面,它可以将向导定向到顺序中的下一页而不是下一页。

示例项目中的向导还有另外两个页面:CWizBkPicturePageCWizFinishPage。由于它们与上面两个页面相似,因此我在这里不详细介绍,但你可以查看示例代码以获取所有详细信息。

其他 UI 考虑事项

居中显示属性表

属性表和向导的默认行为是出现在其父窗口的左上角附近。

 [sheet position - 19K]

这看起来相当草率,但幸运的是我们可以纠正它。感谢论坛上的朋友们提供了代码来完成此操作;该文章的早期版本使用了更复杂的方法。

属性表或向导类可以处理 WM_SHOWWINDOW 消息。随 WM_SHOWWINDOW 发送的 wParam 是一个布尔值,指示窗口是否正在显示。如果 wParamtrue,并且这是窗口第一次显示,那么它可以调用 CenterWindow()

以下是我们可以在 COptionsSheet 中添加的代码来居中显示属性表。m_bCentered 成员是我们用来跟踪属性表是否已居中的方法。

class COptionsSheet : public CPropertySheetImpl<COptionsSheet>
{
//...
  BEGIN_MSG_MAP(COptionsSheet)
    MSG_WM_SHOWWINDOW(OnShowWindow)
    CHAIN_MSG_MAP(CPropertySheetImpl<COptionsSheet>)
  END_MSG_MAP()
 
  // Message handlers
  void OnShowWindow(BOOL bShowing, int nReason);
 
protected:
  bool m_bCentered;  // set to false in the ctor
};
 
void COptionsSheet::OnShowWindow(BOOL bShowing, int nReason)
{
  if ( bShowing && !m_bCentered )
    {
    m_bCentered = true;
    CenterWindow ( m_psh.hwndParent );
    }
}

为页面添加图标

要使用属性表和页面尚未被成员函数包装的其他功能,你需要直接访问相关的结构:CPropertySheetImpl 中的 PROPSHEETHEADER 成员 m_psh,或 CPropertyPageImpl 中的 PROPSHEETPAGE 成员 m_psp

例如,要在选项属性表的 Background 页面中添加图标,我们需要在页面的 PROPSHEETPAGE 结构中添加一个标志并设置另外两个成员。

CBackgroundOptsPage::CBackgroundOptsPage()
{
  m_psp.dwFlags |= PSP_USEICONID;
  m_psp.pszIcon = MAKEINTRESOURCE(IDI_TABICON);
  m_psp.hInstance = _Module.GetResourceInstance();
}

这是结果:

 [Tab icon - 5K]

接下来

在第 9 部分,我将介绍 WTL 的实用工具类及其 GDI 对象和通用对话框的包装器。

版权和许可

本文是受版权保护的材料,(c) 2003-2006 Michael Dunn。我知道这不会阻止人们在网上随意复制它,但我还是得说。如果你有兴趣翻译本文,请给我发电子邮件告知。我不太可能拒绝任何人翻译的许可,我只是希望了解翻译情况,以便在此发布链接。

本文的演示代码已发布到公共领域。我以这种方式发布它,以便代码可以使所有人受益。(我没有将文章本身设为公共领域,因为仅在 CodeProject 上提供文章有助于我个人的知名度和 CodeProject 网站。)如果你在自己的应用程序中使用演示代码,发电子邮件告知我将不胜感激(只是为了满足我对人们是否受益于我的代码的好奇心),但这不是必需的。在你的源代码中注明作者身份也表示赞赏,但不是必需的。

修订历史

2003 年 9 月 13 日:文章首次发布。
2006 年 1 月 13 日:将选项属性表/向导代码移至视图类。更新了关于居中属性表的章节。

系列导航:« 第 VII 部分 (分隔符窗口)

© . All rights reserved.