MFC 程序员的 WTL 教程 ( 8 ) — 属性表和向导
|
原文地址链接 WTL for MFC Programmers, Part VIII – Property Sheets and Wizards 内容
简介甚至于在 Windows 95 把属性表引入为公用控件之前,它就已经成为了呈现选项的一种颇为流行的方法了。向导通常用于指导用户通历软件的安装过程或者其他的复杂工作。WTL 对创建这两种类型的属性表都提供了良好的支持,并允许你使用前 面介绍过的所有的那些对话框相关的特性,比如说 DDX 和 DDV。在本章里,我会演示创建一个基本的属性表和向导,以及如何处理由表发送出的事件和通知消息。
WTL 属性表类有两个类,
最后要说明的是, CPropertySheetImpl 的方法下面是 CPropertySheetImpl(_U_STRINGorID title = (LPCTSTR) NULL,
UINT uStartPage = 0, HWND hWndParent = NULL)
CPropertySheetImpl mySheet ( IDS_SHEET_TITLE );
CPropertySheetImpl mySheet ( _T("My prop sheet") );
BOOL AddPage(HPROPSHEETPAGE hPage) BOOL AddPage(LPCPROPSHEETPAGE pPage) 向属性表中添加一个属性页。如果该页已经创建的话,你可以将其句柄(一个 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 或者 void SetWizardMode()
设置 void EnableHelp()
设置 INT_PTR DoModal(HWND hWndParent = ::GetActiveWindow()) 创建并显示一个模态属性表。正的返回值表示成功,对于返回值的完整描述可以参看 HWND Create(HWND hWndParent = NULL) 创建并显示一个非模态的属性表,并返回其窗口句柄。如果发生了错误,属性表就创建不出来, WTL 属性页类和属性表类相似,此 WTL 类封装了属性页的相关工作,也是既有一个窗口接口类,
你还可以创建掌控 ActiveX 控件的页。首先,你要把 atlhost.h 包含到 stdafx.h 中去,而对于该页,你要使用 CPropertyPageWindow 的方法
CPropertySheetWindow GetPropertySheet() 此方法获取属性页的父窗口(也即属性表)的 剩下的成员仅仅是调用封装了 BOOL Apply() void CancelToClose() void SetModified(BOOL bChanged = TRUE) LRESULT QuerySiblings(WPARAM wParam, LPARAM lParam) void RebootSystem() void RestartWindows() void SetWizardButtons(DWORD dwFlags) 例如,在 SetWizardButtons ( PSWIZB_BACK | PSWIZB_FINISH ); 用以代替: CPropertySheetWindow wndSheet; wndSheet = GetPropertySheet(); wndSheet.SetWizardButtons ( PSWIZB_BACK | PSWIZB_FINISH ); CPropertyPageImpl 的方法
CPropertyPageImpl(_U_STRINGorID title = (LPCTSTR) NULL) 如果你需要手动创建一个页,而不是让属性表来干这件事的话,你可以调用 HPROPSHEETPAGE Create()
还有三个方法,可以设置页面内的几处标题文字: void SetTitle(_U_STRINGorID title) void SetHeaderTitle(LPCTSTR lpstrHeaderTitle) void SetHeaderSubTitle(LPCTSTR lpstrHeaderSubTitle) 第一个函数用于改变该页的标签上的文字。其他的两个用在 Wizard97 风格的向导中,用于设置属性页上方的题头区域内的文字。 void EnableHelp()
在 处理通知消息
共有两组通知处理器,这是由于 WTL 3 和 7 之间出现了设计上的变化。在 WTL 3 里,通知处理器会返回一个不同于 case PSN_WIZFINISH: lResult = !pT->OnWizardFinish(); break;
在 WTL 7 里, #define _WTL_NEW_PAGE_NOTIFY_HANDLERS
在写新代码的时候,显然没有什么理由不使用 WTL 7 的处理器,所以在这就不介绍 WTL 3 的处理器了。
创建一个属性表现在,我们有关类的介绍就结束了,我们需要有一个程序来演示如何使用它们。本章的示例工程是一个简单的 SDI 应用,它在其客户区要显示一个图片,并使用颜色填充背景。图片和颜色可以通过选项对话框(一个属性表)以及一个向导(后文叙述)来更改。 永远最简单的属性表使用 WTL AppWizard 生成一个 SDI 工程后,我们就可以开始创建用于 About 框的属性表了。我们首先从向导为我们生成的 about 对话框开始,要改变其风格才能使它像一个属性页一样工作。 第一步是移除 OK 按钮,在属性表里它没有任何意义。在对话框的属性里,将 Style 改为 Child,将 Border 改为 Thin,并选中 Disabled。 第二步,也是最后一步,是在 void CMainFrame::OnAppAbout(...)
{
CPropertySheet sheet ( _T("About PSheets") );
CPropertyPage<IDD_ABOUTBOX> pgAbout;
sheet.AddPage ( pgAbout );
sheet.DoModal ( *this );
}
结果看起来是这样的: 创建一个有用的属性页因为并不是每个属性表的属性页都和 About 框一样简单,所以大部分的页都会需要是一个 此对话框和 About 页的风格一样。对于此页,我们需要一个新类,将其命名为 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; }; 此类中需要注意的有:
int CBackgroundOptsPage::OnApply() { return DoDataExchange(true) ? PSNRET_NOERROR : PSNRET_INVALID; } 添加一个 Tools|Options 菜单项,让它来把属性表搬出来,我们把此命令的处理器放到视图类中。此处理器像前面一样创建属性表,不过要把新的 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 ); }
如果用户点击了 OK,
创建一个更好的属性表类
#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::CAppPropertySheet (
_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 );
}
这样就使 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 的属性页里那样,你要覆盖 留意一下,此页没有标题文字。由于向导中的每个页通常都具有相同的标题,我建议在 CPropertySheetImpl 的构造函数里设置标题,并让每一页都使用相同的字符串资源。这样,就可以只改动一个字符串而使每一页都能反映出来。 此页的实现都在 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 )
{
}
在本页成为当前页的时候,字符串 int CWizIntroPage::OnSetActive() { SetWizardButtons ( PSWIZB_NEXT ); return 0; } 为了实现此向导,我们需要创建一个 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 为 此页的实现是在 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; };
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 } 注意,在 示例工程里的向导还有另外的两个页 其他的 UI 考虑居中属性表属性页和向导的缺省行为会显示到接近其父窗口的左上角的位置: 这看起来很随意,幸好我们还有补救的办法。感谢那些在论坛里提供做这件事情的代码的人们,本文的前一个版本使用了一种更为复杂的方法。 属性表类或者向导类可以处理 下面就是我们可以加入到 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 里的 例如,要为选项属性表的 Background 页添加图标的话,我们就需要添加一个标志,并在该页的 CBackgroundOptsPage::CBackgroundOptsPage()
{
m_psp.dwFlags |= PSP_USEICONID;
m_psp.pszIcon = MAKEINTRESOURCE(IDI_TABICON);
m_psp.hInstance = _Module.GetResourceInstance();
}
下面是结果: 延伸阅读: WTL CPropertySheet as a Resizable View 以及修改后的源代码 PropViewDemo 下一步在第九部分里,我会介绍 WTL 的辅助类,以及 GDI 对象和公用对话框的封装类。 修订历史2003 年 9 月 13 日:首次发布 |

近期评论