📄 wtl for mfc programmers, part viii - property sheets and wizard.htm
字号:
<P>这就是向导的效果:</P>
<P><IMG height=348 alt=" [Wizard on intro page - 6K] "
src="images/wizintro8.png"
width=441 align=bottom border=0></P>
<H3><A name=morepages></A><font color="#FFFF66">添加更多的属性页,使用DDV</font></H3>
<P>为了使这个向导能够有点用处,我们要为其添加一个设置视图背景颜色的页面。这个页面还将有一个checkbox演示如何处理DDV验证失败并阻止向导进行到下一页。下面就是新的页面,ID是IDD_WIZARD_BKCOLOR:</P>
<P><IMG height=304 alt=" [Color selection wizard page - 4K] "
src="images/colorpage8.png"
width=348 align=bottom border=0></P>
<P>这个类的实现代码在CWizBkColorPage类中,下面是相关的部分代码</P>
<PRE><SPAN class=cpp-keyword><font color="#0000FF">class</font></SPAN><font color="#0000FF"> CWizBkColorPage :
<SPAN class=cpp-keyword>public</SPAN> CPropertyPageImpl<CWizBkColorPage>,
<SPAN class=cpp-keyword>public</SPAN> CWinDataExchange<CWizBkColorPage>
{
<SPAN class=cpp-keyword>public</SPAN>:
<SPAN class=cpp-comment>// some stuff removed for brevity...</SPAN>
BEGIN_DDX_MAP(CWizBkColorPage)
DDX_RADIO(IDC_BLUE, m_nColor)
DDX_CHECK(IDC_FAIL_DDV, m_bFailDDV)
END_DDX_MAP()
<SPAN class=cpp-comment>// Notification handlers</SPAN>
<SPAN class=cpp-keyword>int</SPAN> OnSetActive();
BOOL OnKillActive();
<SPAN class=cpp-comment>// DDX vars</SPAN>
<SPAN class=cpp-keyword>int</SPAN> m_nColor;
<SPAN class=cpp-keyword>protected</SPAN>:
<SPAN class=cpp-keyword>int</SPAN> m_bFailDDV;
};</font></PRE>
<P>OnSetActive()的工作和前面的介绍页面相同,它使“上一步”和“下一步”按钮可用。OnKillActive()是个新的处理函数,它触发DDV,然后检查m_bFailDDV的值,如果是TRUE就表示checkbox处于选中状态,OnKillActive()将阻止向导进行到下一页。</P>
<PRE><SPAN class=cpp-keyword><font color="#0000FF">int</font></SPAN><font color="#0000FF"> CWizBkColorPage::OnSetActive()
{
SetWizardButtons ( PSWIZB_BACK | PSWIZB_NEXT );
<SPAN class=cpp-keyword>return</SPAN> <SPAN class=cpp-literal>0</SPAN>;
}
<SPAN class=cpp-keyword>int</SPAN> CWizBkColorPage::OnKillActive()
{
<SPAN class=cpp-keyword>if</SPAN> ( !DoDataExchange(<SPAN class=cpp-keyword>true</SPAN>) )
<SPAN class=cpp-keyword>return</SPAN> TRUE; <SPAN class=cpp-comment>// prevent deactivation</SPAN>
<SPAN class=cpp-keyword>if</SPAN> ( m_bFailDDV )
{
MessageBox (
_T(<SPAN class=cpp-string>"Error box checked, wizard will stay on this page."</SPAN>),
_T(<SPAN class=cpp-string>"PSheets"</SPAN>), MB_ICONERROR );
<SPAN class=cpp-keyword>return</SPAN> TRUE; <SPAN class=cpp-comment>// prevent deactivation</SPAN>
}
<SPAN class=cpp-keyword>return</SPAN> FALSE; <SPAN class=cpp-comment>// allow deactivation</SPAN>
}</font></PRE>
<P>需要注意的是OnKillActive()中做的事情也可以在OnWizardNext()中完成,因为这两个处理函数都可以使向导维持在当前页面。它们的不同之处在于OnKillActive()在用户单击“上一步”和“下一步”按钮时被调用,而OnWizardNext()只是在用户单击“下一步”按钮时被调用。OnWizardNext()还被用来完成其它目的,比如,它可以直接将向导引导到指定的页面而不是按顺序的下一页。</P>
<p>例子工程的向导还有另外两个页面,CWizBkPicturePage 和 CWizFinishPage,由于它们和前面的两个页面相似,我就不再详细介绍它们,想了解它们的细节可以查看源代码。</p>
<H2><A name=otherui></A><font color="#FFFF66">其他的界面考虑</font></H2>
<H3><A name=centersheet></A><font color="#FFFF66">置中一个属性表</font></H3>
<P>属性页和向导的默认位置是出现在父窗口的左上角:</P>
<P><IMG height=326 alt=" [sheet position - 8K] "
src="images/sheetpos8.png"
width=410 align=bottom border=0></P>
<P>这看起来有点不爽,还好有方法可以补救。第一种方法是重载CPropertySheetImpl::PropSheetCallback()函数,在这个函数中将属性表置中。PropSheetCallback()是MSDN中介绍的PropSheetProc()的回调函数,操作系统在属性表创建时调用这个函数,WTL也是利用这个时间子类化属性表窗口的。所以我们的第一种尝试是:</P>
<PRE><SPAN class=cpp-keyword><font color="#0000FF">class</font></SPAN><font color="#0000FF"> CAppPropertySheet : <SPAN class=cpp-keyword>public</SPAN> CPropertySheetImpl<CAppPropertySheet>
{
<SPAN class=cpp-comment>//...</SPAN>
<SPAN class=cpp-keyword>static</SPAN> <SPAN class=cpp-keyword>int</SPAN> CALLBACK PropSheetCallback(HWND hWnd, UINT uMsg, LPARAM lParam)
{
<SPAN class=cpp-keyword>int</SPAN> nRet = CPropertySheetImpl<CAppPropertySheet>::PropSheetCallback (
hWnd, uMsg, lParam );
<SPAN class=cpp-keyword>if</SPAN> ( PSCB_INITIALIZED == uMsg )
{
<SPAN class=cpp-comment>// center sheet... somehow?</SPAN>
}
<SPAN class=cpp-keyword>return</SPAN> nRet;
}
};</font></PRE>
<P>正如你看到的,我们遇到了棘手的问题。PropSheetCallback()是一个静态方法,不能使用this指针访问属性表窗口。那将这些代码从CPropertySheetImpl::PropSheetCallback()中拷贝出来,然后添加我们自己的方法行不行呢?撇开刚才将代码和特定版本的WTL联系在一起的方法(这已经被证明不是各好方法),现在代码应该是这样的:</P>
<PRE><SPAN class=cpp-keyword><font color="#0000FF">class</font></SPAN><font color="#0000FF"> CAppPropertySheet : <SPAN class=cpp-keyword>public</SPAN> CPropertySheetImpl<CAppPropertySheet>
{
<SPAN class=cpp-comment>//...</SPAN>
<SPAN class=cpp-keyword>static</SPAN> <SPAN class=cpp-keyword>int</SPAN> CALLBACK PropSheetCallback(HWND hWnd, UINT uMsg, LPARAM)
{
<SPAN class=cpp-keyword>if</SPAN>(uMsg == PSCB_INITIALIZED)
{
<B><SPAN class=cpp-comment>// Code copied from WTL and tweaked to use CAppPropertySheet</SPAN>
<SPAN class=cpp-comment>// instead of T:</span></B>
ATLASSERT(hWnd != NULL);
CAppPropertySheet* pT = (CAppPropertySheet*)
_Module.ExtractCreateWndData();
<SPAN class=cpp-comment>// subclass the sheet window</SPAN>
pT->SubclassWindow(hWnd);
<SPAN class=cpp-comment>// remove page handles array</SPAN>
pT->_CleanUpPages();
<B><SPAN class=cpp-comment>// Our own code follows:</SPAN>
pT->CenterWindow ( pT->m_psh.hwndParent );</B>
}
<SPAN class=cpp-keyword>return</SPAN> <SPAN class=cpp-literal>0</SPAN>;
}
};</font></PRE>
<P>这从理论上讲很完美,但是我试过,属性表的位置并未改变。显然,通用控件的代码在我们调用CenterWindow()之后又改变了属性表窗口的位置。</P>
<p>必须放弃这个将代码封装到属性表类的方法,尽管它是个好的解决方案。我又回到原来的方案,即使用属性页窗口和属性表窗口相互协作是属性表窗口置中。我添加了一个用户定义消息UWM_CENTER_SHEET:</p>
<PRE><SPAN class=cpp-preprocessor><font color="#0000FF"> #define UWM_CENTER_SHEET WM_APP</font></SPAN></PRE>
<P>CAppPropertySheet 在它的消息映射链中处理这个消息:</P>
<PRE><SPAN class=cpp-keyword><font color="#0000FF">class</font></SPAN><font color="#0000FF"> CAppPropertySheet : <SPAN class=cpp-keyword>public</SPAN> CPropertySheetImpl<CAppPropertySheet>
{
<SPAN class=cpp-comment>//...</SPAN>
BEGIN_MSG_MAP(CAppPropertySheet)
MESSAGE_HANDLER_EX(UWM_CENTER_SHEET, OnPageInit)
CHAIN_MSG_MAP(CPropertySheetImpl<CAppPropertySheet>)
END_MSG_MAP()
<SPAN class=cpp-comment>// Message handlers</SPAN>
LRESULT OnPageInit ( UINT, WPARAM, LPARAM );
<SPAN class=cpp-keyword>protected</SPAN>:
<SPAN class=cpp-keyword>bool</SPAN> m_bCentered; <SPAN class=cpp-comment>// set to false in the ctor</SPAN>
};
LRESULT CAppPropertySheet::OnPageInit ( UINT, WPARAM, LPARAM )
{
<SPAN class=cpp-keyword>if</SPAN> ( !m_bCentered )
{
m_bCentered = <SPAN class=cpp-keyword>true</SPAN>;
CenterWindow ( m_psh.hwndParent );
}
<SPAN class=cpp-keyword>return</SPAN> <SPAN class=cpp-literal>0</SPAN>;
}</font></PRE>
<P>然后,每个属性页的OnInitDialog() 方法发送这个消息到属性表窗口:</P>
<PRE><font color="#0000FF">BOOL CBackgroundOptsPage::OnInitDialog ( HWND hwndFocus, LPARAM lParam )
{
<B>GetPropertySheet().SendMessage ( UWM_CENTER_SHEET );</B>
DoDataExchange(<SPAN class=cpp-keyword>false</SPAN>);
<SPAN class=cpp-keyword>return</SPAN> TRUE;
}</font></PRE>
<P>添加m_bCentered标志确保属性表窗口只响应收到的第一个UWM_CENTER_SHEET消息。</P>
<H3><A name=addicons></A><font color="#FFFF66">在属性页中添加图标</font> </H3>
<P>如果要使用属性表和属性页的未被成员函数封装的特性,就需要直接访问相关的数据结构:CPropertySheetImpl类中的PROPSHEETHEADER类型(结构)成员m_psh和CPropertyPageImpl类中的PROPSHEETPAGE类型(结构)成员m_psp。</P>
<p>例如:为例子中Option属性表中的Background页面添加一个图标,就需要添加一个标志并设置属性页的PROPSHEETPAGE结构中的几个成员:</p>
<PRE><font color="#0000FF">CBackgroundOptsPage::CBackgroundOptsPage()
{
m_psp.dwFlags |= PSP_USEICONID;
m_psp.pszIcon = MAKEINTRESOURCE(IDI_TABICON);
m_psp.hInstance = _Module.GetResourceInstance();
}</font></PRE>
<P>下面是这些代码的效果:</P>
<P><IMG height=67 alt=" [Tab icon - 2K] "
src="images/tabicon8.png"
width=163 align=bottom border=0></P>
<H2><A name=upnext></A><font color="#FFFF66">继续</font></H2>
<P>我将在第九章介绍WTL的一些工具类,还有GDI对象和通用对话框的包装类。</P>
<H2><A name=revisionhistory></A><font color="#FFFF66">修改记录</font></H2>
<P>September 13, 2003: 文章第一次发布。
<P>
</body>
</html>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -