第八部分属性表和向导.docx
《第八部分属性表和向导.docx》由会员分享,可在线阅读,更多相关《第八部分属性表和向导.docx(21页珍藏版)》请在冰豆网上搜索。
第八部分属性表和向导
第八部分-属性表和向导
∙下载示例工程-272KB
内容
∙简介
∙WTL属性表类
oCPropertySheetImpl的方法
∙WTL属性页类
oCPropertyPageWindow的方法
oCPropertyPageImpl的方法
o处理通知消息
∙创建一个属性表
o永远最简单的属性表
o创建一个有用的属性页
o创建一个更好的属性表类
∙创建一个向导
o添加更多的页,处理DDV
∙其他的UI考虑
o居中属性表
o为属性页添加图标
∙下一步
∙修订历史
简介
甚至于在Windows95把属性表引入为公用控件之前,它就已经成为了呈现选项的一种颇为流行的方法了。
向导通常用于指导用户通历软件的安装过程或者其他的复杂工作。
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_STRINGorIDtitle=(LPCTSTR)NULL,
UINTuStartPage=0,HWNDhWndParent=NULL)
CPropertySheetImpl的构造函数允许你当下就指定一些常用的属性,这样你就不必在以后调用别的方法来设置它们。
title指定了在属性表的标题上用到的文字。
_U_STRINGorID是一个WTL辅助类,可以使你传递LPCTSTR或者字符串资源ID。
例如,如果IDS_SHEET_TITLE是字符串表中某个字符串的ID的话,那么下面这两行就都可以工作:
CPropertySheetImplmySheet(IDS_SHEET_TITLE);
CPropertySheetImplmySheet(_T("Mypropsheet"));
uStartPage是从零开始的页面索引值,当属性表第一次显示时,该页就会被激活。
hWndParent设置了属性表的父窗口。
BOOLAddPage(HPROPSHEETPAGEhPage)
BOOLAddPage(LPCPROPSHEETPAGEpPage)
向属性表中添加一个属性页。
如果该页已经创建的话,你可以将其句柄(一个HPROPSHEETPAGE)传递给第一个重载版本的函数。
更常见的方法是使用第二个重载版本。
使用这一版本,你可以先设置一个PROPSHEETPAGE结构(此事可以由CPropertyPageImpl来做,后文会有介绍),而CPropertySheetImpl会为你创建并管理该页。
BOOLRemovePage(HPROPSHEETPAGEhPage)
BOOLRemovePage(intnPageIndex)
从属性表中删除一个属性页。
你既可以传递属性页的句柄也可以传递基于零的索引值。
BOOLSetActivePage(HPROPSHEETPAGEhPage)
BOOLSetActivePage(intnPageIndex)
设置属性表的活动属性页。
你既可以传递要激活的页的句柄,也可以传递基于零的索引。
你也可以在创建属性表之前调用此方法,以设置属性表首次显示时要激活哪一页。
voidSetTitle(LPCTSTRlpszText,UINTnStyle=0)
设置属性表标题使用的文字。
nStyle可以是0或者PSH_PROPTITLE。
如果是PSH_PROPTITLE,则此风格会被加到属性表上,就会使“Propertiesfor”字样附加到你在lpszText参数里传入的文本之前。
voidSetWizardMode()
设置PSH_WIZARD风格,这会使得属性表变成一个向导。
你必须在显示属性表之前调用此方法。
voidEnableHelp()
设置PSH_HASHELP风格,它会为属性表加入帮助按钮。
注意,你还需要在为此按钮提供帮助的各个页中启用帮助才能有效。
INT_PTRDoModal(HWNDhWndParent=:
:
GetActiveWindow())
创建并显示一个模态属性表。
正的返回值表示成功,对于返回值的完整描述可以参看PropertySheet()API的文档。
如果有错误发生且属性表没有创建成功,DoModal()会返回-1。
HWNDCreate(HWNDhWndParent=NULL)
创建并显示一个非模态的属性表,并返回其窗口句柄。
如果发生了错误,属性表就创建不出来,Create()会返回NULL。
WTL属性页类
和属性表类相似,此WTL类封装了属性页的相关工作,也是既有一个窗口接口类,CPropertyPageWindow,又有一个实现类CPropertyPageImpl。
CPropertyPageWindow非常小,其包含的大部分辅助函数都调用了父表中的方法。
CPropertyPageImpl派生于CDialogImplBaseT,这是因为一个属性页是用一个对话框资源来构建的。
这也意味着,我们在对话框里所用到的所有WTL特性在属性表中都是可用的,比如说DDX和DDV。
CPropertyPageImpl有两个主要的目的:
管理保存在成员变量m_psp中的PROPSHEETPAGE结构,并处理PSN_*通知消息。
对于非常简单的属性页,你可以使用CPropertyPage类。
这仅仅适用于那些根本不与用户交互的页,比方说一个About页,或者是向导里的简介页面。
你还可以创建掌控ActiveX控件的页。
首先,你要把atlhost.h包含到stdafx.h中去,而对于该页,你要使用CAxPropertyPageImpl代替CPropertyPageImpl。
对于要掌控ActiveX控件的简单页,你可以使用CAxPropertyPage来代替CPropertyPage。
CPropertyPageWindow的方法
CPropertyPageWindow最重要的方法是GetPropertySheet():
CPropertySheetWindowGetPropertySheet()
此方法获取属性页的父窗口(也即属性表)的HWND并将之关联到一个CPropertySheetWindow上。
再把新的CPropertySheetWindow返回给调用者。
请注意,此处只是创建一个临时对象,它并不是返回用来创建属性表的实际的CPropertySheet或者CPropertySheetImpl对象的指针或者是引用。
如果你是使用自己的CPropertySheetImpl派生类,而又要在属性表对象中访问数据成员的话,这一点就很重要。
剩下的成员仅仅是调用封装了PSM_*消息的CPropertySheetWindow函数:
BOOLApply()
voidCancelToClose()
voidSetModified(BOOLbChanged=TRUE)
LRESULTQuerySiblings(WPARAMwParam,LPARAMlParam)
voidRebootSystem()
voidRestartWindows()
voidSetWizardButtons(DWORDdwFlags)
例如,在CPropertyPageImpl派生类里,你可以调用:
SetWizardButtons(PSWIZB_BACK|PSWIZB_FINISH);
用以代替:
CPropertySheetWindowwndSheet;
wndSheet=GetPropertySheet();
wndSheet.SetWizardButtons(PSWIZB_BACK|PSWIZB_FINISH);
CPropertyPageImpl的方法
CPropertyPageImpl管理着一个PROPSHEETPAGE结构,即其公用成员m_psp。
CPropertyPageImpl还有一个operatorPROPSHEETPAGE*转换器,因此你可以把一个CPropertyPageImpl传递到接受LPPROPSHEETPAGE或者LPCPROPSHEETPAGE参数的方法里,比如CPropertySheetImpl:
:
AddPage()。
CPropertyPageImpl的构造函数允许你设置页的标题,也即出现在页的标签上的文字:
CPropertyPageImpl(_U_STRINGorIDtitle=(LPCTSTR)NULL)
如果你需要手动创建一个页,而不是让属性表来干这件事的话,你可以调用Create():
HPROPSHEETPAGECreate()
Create()只不过是使用m_psp作为参数调用了CreatePropertySheetPage()。
你只有在以下情况下才需要调用Create(),或者是在属性表创建之后需要向其上添加一个页,或者是要把创建的页传递给其他不受你控制的属性表,例如,一个属性表处理器的外壳扩展。
还有三个方法,可以设置页面内的几处标题文字:
voidSetTitle(_U_STRINGorIDtitle)
voidSetHeaderTitle(LPCTSTRlpstrHeaderTitle)
voidSetHeaderSubTitle(LPCTSTRlpstrHeaderSubTitle)
第一个函数用于改变该页的标签上的文字。
其他的两个用在Wizard97风格的向导中,用于设置属性页上方的题头区域内的文字。
voidEnableHelp()
在m_psp中设置PSP_HASHELP标志,当页面激活时即可以启用Help按钮。
处理通知消息
CPropertyPageImpl中有一个消息映射,它处理了WM_NOTIFY。
如果通知代码是一个PSN_*值,OnNotify()会调用用于此通知的特定的处理器。
这由编译时虚函数技术完成,因而在派生类里可以很轻易地覆盖该处理器。
共有两组通知处理器,这是由于WTL3和7之间出现了设计上的变化。
在WTL3里,通知处理器会返回一个不同于PSN_*消息的返回值的值。
比如,WTL3中PSN_WIZFINISH的处理器:
casePSN_WIZFINISH:
lResult=!
pT->OnWizardFinish();
break;
OnWizardFinish()希望返回TRUE以允许向导结束,或者返回FALSE来阻止向导的关闭;但是,由于IE5的公用控件添入了可以从PSN_WIZFINISH的处理器返回一个窗口句柄的能力用以设置焦点,所以这就行不通了。
WTL3的应用不能使用此特性,因为所有的非零值都被认为是一样的。
在WTL7里,OnNotify()不会改变从PSN_*处理器返回的任何值。
处理器可以返回任何文档化的合法值,因而其行为也就完全正常了。
但是,出于后向兼容的考虑,WTL3的处理器仍然存在并且被缺省使用。
要使用WTL7的处理器,你必须把下列行添加到stdafx.h中,而且位于atldlgs.h的包含语句之前:
#define_WTL_NEW_PAGE_NOTIFY_HANDLERS
在写新代码的时候,显然没有什么理由不使用WTL7的处理器,所以在这就不介绍WTL3的处理器了。
CPropertyPageImpl对所有的通知都有缺省的处理器,所以你可以只覆盖和你的程序相关的那些处理器。
缺省处理器及其行为如下:
intOnSetActive()-允许属性页成为活动的
BOOLOnKillActive()-允许属性页成为非活动的
intOnApply()-返回表示应用操作已成功的PSNRET_NOERROR
voidOnReset()-无操作
BOOLOnQueryCancel()-允许取消操作
intOnWizardBack()-到上一页
intOnWizardNext()-到下一页
INT_PTROnWizardFinish()-允许向导结束
voidOnHelp()-无操作
BOOLOnGetObject(LPNMOBJECTNOTIFYlpObjectNotify)-无操作
intOnTranslateAccelerator(LPMSGlpMsg)-返回表示消息没有被处理的PSNRET_NOERROR
HWNDOnQueryInitialFocus(HWNDhWndFocus)-返回NULL以把焦点设置到Tab顺序里的第一个控件上
创建一个属性表
现在,我们有关类的介绍就结束了,我们需要有一个程序来演示如何使用它们。
本章的示例工程是一个简单的SDI应用,它在其客户区要显示一个图片,并使用颜色填充背景。
图片和颜色可以通过选项对话框(一个属性表)以及一个向导(后文叙述)来更改。
永远最简单的属性表
使用WTLAppWizard生成一个SDI工程后,我们就可以开始创建用于About框的属性表了。
我们首先从向导为我们生成的about对话框开始,要改变其风格才能使它像一个属性页一样工作。
第一步是移除OK按钮,在属性表里它没有任何意义。
在对话框的属性里,将Style改为Child,将Border改为Thin,并选中Disabled。
第二步,也是最后一步,是在OnAppAbout()处理器中创建属性表。
我们可以使用不可定制的CPropertySheet和CPropertyPage来做这件事:
voidCMainFrame:
:
OnAppAbout(...)
{
CPropertySheetsheet(_T("AboutPSheets"));
CPropertyPagepgAbout;
sheet.AddPage(pgAbout);
sheet.DoModal(*this);
}
结果看起来是这样的:
创建一个有用的属性页
因为并不是每个属性表的属性页都和About框一样简单,所以大部分的页都会需要是一个CPropertyPageImpl的派生类,所以我们现在就来看一下这样的一个类。
我们要创建一个新的属性页,其中包含了显示在客户区背景中的图象的设置。
对话框如下:
此对话框和About页的风格一样。
对于此页,我们需要一个新类,将其命名为CBackgroundOptsPage。
此类派生于CPropertyPageImpl,因为它毕竟是一个属性页,同时也派生于CWinDataExchange,这样可以启用DDX。
classCBackgroundOptsPage:
publicCPropertyPageImpl,
publicCWinDataExchange
{
public:
enum{IDD=IDD_BACKGROUND_OPTS};
//Construction
CBackgroundOptsPage();
~CBackgroundOptsPage();
//Maps
BEGIN_MSG_MAP(CBackgroundOptsPage)
MSG_WM_INITDIALOG(OnInitDialog)
CHAIN_MSG_MAP(CPropertyPageImpl)
END_MSG_MAP()
BEGIN_DDX_MAP(CBackgroundOptsPage)
DDX_RADIO(IDC_BLUE,m_nColor)
DDX_RADIO(IDC_ALYSON,m_nPicture)
END_DDX_MAP()
//Messagehandlers
BOOLOnInitDialog(HWNDhwndFocus,LPARAMlParam);
//Propertypagenotificationhandlers
intOnApply();
//DDXvariables
intm_nColor,m_nPicture;
};
此类中需要注意的有:
∙其中有一个名为IDD的公用成员,里面存放着相关联的对话框资源ID。
∙其消息映射与CDialogImpl类相似。
∙消息映射会把消息串联到CPropertyPageImpl,从而可以处理属性表相关的消息。
∙其中有一个OnApply()处理器,当用户点击属性表上的OK时可以保存用户的选择。
OnApply()相当简单,它调用DoDataExchange()来更新DDX变量,然后再返回一个代码,指示属性表是否可以关闭:
intCBackgroundOptsPage:
:
OnApply()
{
returnDoDataExchange(true)?
PSNRET_NOERROR:
PSNRET_INVALID;
}
添加一个Tools|Options菜单项,让它来把属性表搬出来,我们把此命令的处理器放到视图类中。
此处理器像前面一样创建属性表,不过要把新的CBackgroundOptsPage添加到属性表里。
voidCPSheetsView:
:
OnOptions(UINTuCode,intnID,HWNDhwndCtrl)
{
CPropertySheetsheet(_T("PSheetsOptions"),0);
CBackgroundOptsPagepgBackground;
CPropertyPagepgAbout;
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使得属性表出现时首先看到的是About页。
因为这仅仅是演示代码,我计划偷个懒,让CBackgroundOptsPage中连接到单选按钮的变量成为共有的。
视图会把当前的选项存放在这些变量里,如果用户点击了属性表的OK,那就把这些新的值保存起来。
如果用户点击了OK,DoModal()会返回IDOK,于是视图就会使用的新的图片和颜色重绘自己。
下面是不同视图的屏幕截图:
创建一个更好的属性表类
OnOptions()处理器创建的属性表确实不错,但是那一大堆设置和初始化代码,却不应该是CMainFrame的职责。
更好的方法是从CPropertySheetImpl派生一个类,由它来处理这些工作。
#include"BackgroundOptsPage.h"
classCOptionsSheet:
publicCPropertySheetImpl
{
public:
//Construction
COptionsSheet(_U_STRINGorIDtitle=(LPCTSTR)NULL,
UINTuStartPage=0,HWNDhWndParent=NULL);
//Maps
BEGIN_MSG_MAP(COptionsSheet)
CHAIN_MSG_MAP(CPropertySheetImpl)
END_MSG_MAP()
//Propertypages
CBackgroundOptsPagem_pgBackground;
CPropertyPagem_pgAbout;
};
有了这个类,我们就把诸如表中有哪些页之类的细节移到了属性表自身里。
构造函数处理以下事宜:
把属性页添加到属性表里,并设置其它必要的标志:
COptionsSheet:
:
CAppPropertySheet(
_U_STRINGorIDtitle,UINTuStartPage,HWNDhWndParent):
CPropertySheetImpl(title,uStartPage,hWndParent)
{
m_psh.dwFlags|=PSH_NOAPPLY