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






4.94/5 (66投票s)
2003 年 9 月 13 日
15分钟阅读

429025

5526
创建 WTL 中属性表和向导的指南
目录
引言
在 Windows 95 引入属性表作为通用控件之前,属性表就已经是一种流行的呈现选项的方式了。向导通常用于指导用户安装软件或其他复杂任务。WTL 为创建这两种类型的属性表提供了良好的支持,并且允许你使用我们之前介绍的所有与对话框相关的功能,如 DDX 和 DDV。在本文中,我将演示如何创建基本的属性表和向导,以及如何处理属性表发送的事件和通知消息。
WTL 属性表类
有两个类结合起来实现了属性表:CPropertySheetWindow
和 CPropertySheetImpl
。两者都在 atldlgs.h 头文件中定义。CPropertySheetWindow
是一个窗口接口类(即它派生自 CWindow
),而 CPropertySheetImpl
具有消息映射并实际实现窗口。这与基本的 ATL 窗口类类似,其中 CWindow
和 CWindowImpl
一起使用。
CPropertySheetWindow
包含各种 PSM_*
消息的包装器,例如 SetActivePageByID()
,它包装了 PSM_SETCURSELID
。CPropertySheetImpl
管理一个 PROPSHEETHEADER
结构和一个 HPROPSHEETPAGE
数组。CPropertySheetImpl
还具有设置一些 PROPSHEETHEADER
字段以及添加和删除页面的方法。你可以通过访问 m_psh
成员变量来直接访问 PROPSHEETHEADER
。
最后,CPropertySheet
是 CPropertySheetImpl
的一个特例,如果你不需要完全自定义属性表,可以使用它。
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
和一个实现类 CPropertyPageImpl
。CPropertyPageWindow
非常小,主要包含调用父属性表方法的实用函数。
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
返回给调用者。请注意,这仅创建一个临时对象;它 **不** 返回实际用于创建属性表的 CPropertySheet
或 CPropertySheetImpl
对象的指针或引用。如果你正在使用自己的 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_psp
。CPropertyPageImpl
还有一个 operator PROPSHEETPAGE*
转换器,因此你可以将 CPropertyPageImpl
传递给接受 LPPROPSHEETPAGE
或 LPCPROPSHEETPAGE
的方法,例如 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()
处理程序中创建一个属性表。我们可以使用 CPropertySheet
和 CPropertyPage
来实现。
void CMainFrame::OnAppAbout(...) { CPropertySheet sheet ( _T("About PSheets") ); CPropertyPage<IDD_ABOUTBOX> pgAbout; sheet.AddPage ( pgAbout ); sheet.DoModal ( *this ); }
结果如下:
创建有用的属性页
由于并非所有属性表中的所有页面都像“关于”框一样简单,大多数页面都需要一个 CPropertyPageImpl
派生类,因此我们现在来看一个这样的类。我们将创建一个新的属性页,其中包含客户端区域背景图形的设置。这是对话框
此对话框具有与 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
,视图将使用新图像和颜色重绘自身。以下是一些屏幕截图,展示了不同的视图。
创建更好的属性表类
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 ); }
创建向导
创建向导,不出所料,与创建属性表类似。需要做更多工作来启用 Back 和 Next 按钮;就像 MFC 属性页一样,你需要覆盖 OnSetActive()
并调用 SetWizardButtons()
来启用适当的按钮。我们将从一个简单的介绍页面开始,其 ID 为 IDD_WIZARD_INTRO
。
请注意,该页面没有标题文本。由于向导中的每个页面通常都有相同的标题,我更喜欢在 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() ); }
以下是向导的实际运行截图。
添加更多页面,处理 DDV
为了使这个向导更有用,我们将添加一个新页面来设置视图的背景颜色。此页面还将包含一个复选框,用于演示如何处理 DDV 失败并阻止用户继续向导。这是新页面,其 ID 为 IDD_WIZARD_BKCOLOR
。
此页面的实现位于 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()
的工作方式与介绍页面类似,它同时启用 Back 和 Next 按钮。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()
在用户单击 Back 或 Next 时被调用,而 OnWizardNext()
仅在用户单击 Next 时被调用(顾名思义)。OnWizardNext()
也用于其他目的;如果需要跳过某些页面,它可以将向导定向到顺序中的下一页而不是下一页。
示例项目中的向导还有另外两个页面:CWizBkPicturePage
和 CWizFinishPage
。由于它们与上面两个页面相似,因此我在这里不详细介绍,但你可以查看示例代码以获取所有详细信息。
其他 UI 考虑事项
居中显示属性表
属性表和向导的默认行为是出现在其父窗口的左上角附近。
这看起来相当草率,但幸运的是我们可以纠正它。感谢论坛上的朋友们提供了代码来完成此操作;该文章的早期版本使用了更复杂的方法。
属性表或向导类可以处理 WM_SHOWWINDOW
消息。随 WM_SHOWWINDOW
发送的 wParam
是一个布尔值,指示窗口是否正在显示。如果 wParam
为 true
,并且这是窗口第一次显示,那么它可以调用 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(); }
这是结果:
接下来
在第 9 部分,我将介绍 WTL 的实用工具类及其 GDI 对象和通用对话框的包装器。
版权和许可
本文是受版权保护的材料,(c) 2003-2006 Michael Dunn。我知道这不会阻止人们在网上随意复制它,但我还是得说。如果你有兴趣翻译本文,请给我发电子邮件告知。我不太可能拒绝任何人翻译的许可,我只是希望了解翻译情况,以便在此发布链接。
本文的演示代码已发布到公共领域。我以这种方式发布它,以便代码可以使所有人受益。(我没有将文章本身设为公共领域,因为仅在 CodeProject 上提供文章有助于我个人的知名度和 CodeProject 网站。)如果你在自己的应用程序中使用演示代码,发电子邮件告知我将不胜感激(只是为了满足我对人们是否受益于我的代码的好奇心),但这不是必需的。在你的源代码中注明作者身份也表示赞赏,但不是必需的。
修订历史
2003 年 9 月 13 日:文章首次发布。
2006 年 1 月 13 日:将选项属性表/向导代码移至视图类。更新了关于居中属性表的章节。
系列导航:« 第 VII 部分 (分隔符窗口)