征服Wizard97






4.92/5 (32投票s)
本文描述了使用MFC和Wizard 97时可能遇到的问题以及解决方法。
引言
每个人都知道向导(Wizard)是什么。但是,为了让不知道的人也能明白,下面会提供一个简短的介绍,回答“什么是向导?”这个神秘的问题。那些觉得自己已经知道答案的人可以跳到下一部分。
向导是已知的 GUI 元素之一,它可以一步一步地引导用户完成整个过程。它由一系列对话框组成,这些对话框依次在框架窗口中运行。向导有向前和向后导航页面按钮,以及允许用户提交或取消过程的按钮。
从 Win32 API 编程的角度来看,向导是 PropertySheet
控件,其中包含多个页面,而这些页面实际上是 PropertyPage
控件。所以,本质上,向导与 PropertySheet
控件非常相似,但它只允许用户一次访问一个页面。这就是为什么它在 Win32 API 中的实现与 PropertySheet
控件相同。唯一需要做的是在创建 PropertySheet
控件期间定义一个 PSH_WIZARD
标志。
图 1 显示了一个典型的向导示例。
图 1。 向导页面的典型视图
随着 Internet Explorer 新样式的推出,微软也为向导带来了一种新的设计风格——所谓的 Wizard 97。从 5.80 版本开始,通用控件库为向导提供了新的风格化元素,例如带有标题和副标题的头部,以及水印(见图 2)。要启用这些功能,程序员只需在创建 PropertySheet
控件时提供 PSH_WIZARD97
标志而不是 PSH_WIZARD
标志。
图 2。 带头部的 Wizard 97 页面示例
如此好的新通用控件库特性,如果仅仅因为实现简单就忽略了,那将是一种遗憾。
研究
首先,请注意,如果某件事已经有人做过并且已经存在,那么你不应该去尝试实现它。生命太短暂,不值得重复同样的错误和重写代码。你不能“重新发明轮子”。所以要做的第一步是研究现有的使用 Wizard 97 的示例。
在这种情况下,主要来源是
- MSDN 网站 - http://msdn.microsoft.com/
- Codeproject 网站 - https://codeproject.org.cn/
- Codeguru 网站 - http://www.codeguru.com/
- 最后是 Google 搜索引擎 - http://www.google.com/
如果你曾经尝试寻找使用 Wizard 97 的示例,你会对搜索结果感到惊讶。出乎意料的少!最有用的链接是
- http://msdn.microsoft.com/library/en-us/shellcc/platform/CommCtls/PropSheet/wizards.asp
- https://codeproject.org.cn/property/resizeable_wizard97.asp
- http://www.codeguru.com/propertysheet/wiz2000.shtml
- ……就这些了!
对这些示例的分析可以得出一些结论:
- 来自 MSDN 的微软示例使用了纯粹的 Win32 API,并伴随所有隐含的后果。例如,要创建对话框并实现其行为,你必须编写一个对话框处理程序而不是使用 MFC 的方式——使用事件等。
- 来自 Codeproject 网站的示例充斥着可调整大小的功能,而且已经过时,因为它使用了已弃用的 MFC 7.0 之前的类
CPropertySheetEx
和CPropertyPageEx
。MSDN 上写着,现在这些类的所有功能都已包含在之前的基类中——分别是CPropertySheet
和CPropertyPage
。 - 来自 Codeguru 网站的示例根本没有使用
CPropertySheet
和CPropertyPage
。它基于CDialog
类,并且作者自己实现了所有的向导功能。
所以,看起来我们还是得“重新发明轮子”,尽管它几乎可以肯定已经被别人做过了。证据就在我们身边。我们开始吧!让我们使用 MFC 7 类库创建一个自己的 Wizard 97 示例。
热身
天空晴朗,道路畅通。MSDN 中写道,Wizard 97 的所有功能都包含在两个漂亮的包装器类 CPropertySheet
和 CPropertyPage
中。所以我们只需要写几行代码。
创建一个新项目,比如 Wiz97,它是一个基于对话框的应用程序,以保持简单。我们将创建的向导包含两个页面。第一个用于测试水印功能(介绍页面),第二个用于使用带有标题和副标题的头部。
资源中有两个对话框,两个类都基于 CPropertyPage
,还有一个类继承自 CPropertySheet
。另外准备两张位图——一张用于头部的图标,一张用于第一页水印的大图。
遵循 MSDN 的示例,类的构造函数应按如下方式修改:
... // Modified constructor of the first page (with watermark) CFirstPage::CFirstPage() : CPropertyPage(CFirstPage::IDD) { m_psp.dwFlags |= PSP_DEFAULT|PSP_HIDEHEADER; } ... // Modified constructor of the second page (with banner, title and subtitle) CSecondPage::CSecondPage() : CPropertyPage(CSecondPage::IDD) { m_psp.dwFlags |= PSP_DEFAULT|PSP_USEHEADERTITLE|PSP_USEHEADERSUBTITLE; m_psp.pszHeaderTitle = _T("Title"); m_psp.pszHeaderSubTitle = _T("And subtitle"); } ... // Modified constructor of the property sheet class CWiz97::CWiz97(UINT nIDCaption, CWnd* pParentWnd, UINT iSelectPage) :CPropertySheet(nIDCaption, pParentWnd, iSelectPage) { AddPage(&m_pgFirst); AddPage(&m_pgSecond); m_psh.dwFlags |= PSH_WIZARD97|PSH_WATERMARK|PSH_HEADER; m_psh.pszbmWatermark = MAKEINTRESOURCE(IDB_WATERMARK); m_psh.pszbmHeader = MAKEINTRESOURCE(IDB_BANNER_ICON); }
要启动一个向导,我们只需要添加几行代码——这就是我们期待的 MFC 的强大之处。
#include "Wiz97.h" ... void CWiz97_1Dlg::OnBnClickedButton1() { CWiz97 dlg(_T("Wizard 97")); dlg.DoModal(); }
那么,我们准备好面对第一批错误了。编译,运行,第一个结果出来了。
图 3。 第一页本应包含水印,第二页头部预期有一个图标
这里有问题!当然,我们期待看到的水印和小图片头部都没有出现。
第一次胜利
通过阅读 MSDN 示例的源代码并进行分析,我们可以解释第一次失败的原因以及解决问题的方法。解决方案隐藏在一行代码中:
// Modified constructor of the property sheet class CWiz97::CWiz97(UINT nIDCaption, CWnd* pParentWnd, UINT iSelectPage) :CPropertySheet(nIDCaption, pParentWnd, iSelectPage) { SetWizardMode(); AddPage(&m_pgFirst); AddPage(&m_pgSecond); m_psh.dwFlags |= PSH_WIZARD97|PSH_WATERMARK|PSH_HEADER; m_psh.pszbmWatermark = MAKEINTRESOURCE(IDB_WATERMARK); m_psh.pszbmHeader = MAKEINTRESOURCE(IDB_BANNER_ICON); // Next line is very important m_psh.hInstance = AfxGetInstanceHandle(); }
发现问题很容易,尽管人们可能会期望实例句柄在 MFC 类的构造函数中已经设置好了。所以,让我们再次编译并运行。
不用为新版本的程序担心。我们稍后会修复它,但现在先看看新的视图(图 4)。
图 4。 向导令人惊叹的新视图
积极的一点是我们能看到我们创建的图片。但我们向导的整体视图仍然与 MSDN 示例不同。
最后的战斗
如果你已经恢复过来,那么我们可以继续。
找出问题根源可能相当困难。我们可能做错了什么?找出并修复问题可能需要一段时间。
不要在网上搜索——别人已经做过了,你找不到任何关于这个问题的技巧或窍门。也不要检查源代码,因为我们的错误不在于遗漏了标志。我测试了各种标志、属性、函数的组合,但与 MSDN 示例相比,这些努力的最终结果导致了图 3 和图 4 之间的差异。
但在宇宙中,没有什么是不朽的。原因找到了。这个问题发生在我们用于创建应用程序的项目向导的默认设置,即 stdafx.h 文件中关于 IE 版本的设置。要让向导按预期运行,你需要进行以下更改:
#ifndef WINVER #define WINVER 0x0400 // Default value is 0x0400 #endif #ifndef _WIN32_WINNT #define _WIN32_WINNT 0x0400 // Default value is 0x0400 #endif #ifndef _WIN32_WINDOWS #define _WIN32_WINDOWS 0x0410 // Default value is 0x0410 #endif #ifndef _WIN32_IE #define _WIN32_IE 0x0500 // Default value is 0x0400 #endif
最终我们得到了最初期望的结果(见图 5)。
图 5。 最终结果
你可以在以下链接找到关于我们修改的常量的更明确的信息:http://msdn.microsoft.com/library/en-us/sdkintro/sdkintro/using_the_sdk_headers.asp。
所以,总结我们刚刚完成的整个过程,你会发现使用 MFC 类实现 Wizard 97 真的很容易,它们是相当不错的包装器。但要利用 MFC 的强大功能,你必须意识到这种方法的某些陷阱。
我希望这篇文章对您有所帮助,并且您不必重复我的错误。感谢所有有足够热情坚持到最后的人。
特别感谢 Parland Johnstone 的支持。