65.9K
CodeProject 正在变化。 阅读更多。
Home

征服Wizard97

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (32投票s)

2002年10月10日

CPOL

7分钟阅读

viewsIcon

219184

downloadIcon

3356

本文描述了使用MFC和Wizard 97时可能遇到的问题以及解决方法。

引言

每个人都知道向导(Wizard)是什么。但是,为了让不知道的人也能明白,下面会提供一个简短的介绍,回答“什么是向导?”这个神秘的问题。那些觉得自己已经知道答案的人可以跳到下一部分。

向导是已知的 GUI 元素之一,它可以一步一步地引导用户完成整个过程。它由一系列对话框组成,这些对话框依次在框架窗口中运行。向导有向前和向后导航页面按钮,以及允许用户提交或取消过程的按钮。

从 Win32 API 编程的角度来看,向导是 PropertySheet 控件,其中包含多个页面,而这些页面实际上是 PropertyPage 控件。所以,本质上,向导与 PropertySheet 控件非常相似,但它只允许用户一次访问一个页面。这就是为什么它在 Win32 API 中的实现与 PropertySheet 控件相同。唯一需要做的是在创建 PropertySheet 控件期间定义一个 PSH_WIZARD 标志。

图 1 显示了一个典型的向导示例。

The typical view of Wizard page

图 1。 向导页面的典型视图

随着 Internet Explorer 新样式的推出,微软也为向导带来了一种新的设计风格——所谓的 Wizard 97。从 5.80 版本开始,通用控件库为向导提供了新的风格化元素,例如带有标题和副标题的头部,以及水印(见图 2)。要启用这些功能,程序员只需在创建 PropertySheet 控件时提供 PSH_WIZARD97 标志而不是 PSH_WIZARD 标志。

An example of the Wizard 97 page with header

图 2。 带头部的 Wizard 97 页面示例

如此好的新通用控件库特性,如果仅仅因为实现简单就忽略了,那将是一种遗憾。

研究

首先,请注意,如果某件事已经有人做过并且已经存在,那么你不应该去尝试实现它。生命太短暂,不值得重复同样的错误和重写代码。你不能“重新发明轮子”。所以要做的第一步是研究现有的使用 Wizard 97 的示例。

在这种情况下,主要来源是

如果你曾经尝试寻找使用 Wizard 97 的示例,你会对搜索结果感到惊讶。出乎意料的少!最有用的链接是

对这些示例的分析可以得出一些结论:

  • 来自 MSDN 的微软示例使用了纯粹的 Win32 API,并伴随所有隐含的后果。例如,要创建对话框并实现其行为,你必须编写一个对话框处理程序而不是使用 MFC 的方式——使用事件等。
  • 来自 Codeproject 网站的示例充斥着可调整大小的功能,而且已经过时,因为它使用了已弃用的 MFC 7.0 之前的类 CPropertySheetExCPropertyPageEx。MSDN 上写着,现在这些类的所有功能都已包含在之前的基类中——分别是 CPropertySheetCPropertyPage
  • 来自 Codeguru 网站的示例根本没有使用 CPropertySheetCPropertyPage。它基于 CDialog 类,并且作者自己实现了所有的向导功能。

所以,看起来我们还是得“重新发明轮子”,尽管它几乎可以肯定已经被别人做过了。证据就在我们身边。我们开始吧!让我们使用 MFC 7 类库创建一个自己的 Wizard 97 示例。

热身

天空晴朗,道路畅通。MSDN 中写道,Wizard 97 的所有功能都包含在两个漂亮的包装器类 CPropertySheetCPropertyPage 中。所以我们只需要写几行代码。

创建一个新项目,比如 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();
}

那么,我们准备好面对第一批错误了。编译,运行,第一个结果出来了。

The first page with watermark

The second page with header

图 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)。

The first page with watermark

The second page with header

图 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)。

The first page with watermark

The second page with header

图 5。 最终结果

你可以在以下链接找到关于我们修改的常量的更明确的信息:http://msdn.microsoft.com/library/en-us/sdkintro/sdkintro/using_the_sdk_headers.asp

所以,总结我们刚刚完成的整个过程,你会发现使用 MFC 类实现 Wizard 97 真的很容易,它们是相当不错的包装器。但要利用 MFC 的强大功能,你必须意识到这种方法的某些陷阱。

我希望这篇文章对您有所帮助,并且您不必重复我的错误。感谢所有有足够热情坚持到最后的人。

特别感谢 Parland Johnstone 的支持。
© . All rights reserved.