第一部分
AppWizard即应用程序向导,它是Visual Studio开发环境中强大的编程工具之一,用它可以创建各种不同类型的程序。比如Win32应用、ATL、MFC应用等等。在Windows的术语中,向导(wizard)一词指得是一个应用程序,它的一个主要特点就是提供一系列对话框引导用户进行必要的选择来完成给定的任务。VC中的应用程序向导——AppWizard提供一系列特定工程类型对话框来让程序员定义各种类型的新工程。其中每一个对话框都显示一些用来指定工程类型的选项。例如,用AppWizard创建Windows DLL的时候,第一个对话框让程序员指定诸如要创建什么类型的DLL,是常规类型还是MFC扩展类型,是否要包括自动化支持,以及要不要源代码注释等等。
根据程序员所填充的对话框, AppWizard会自动创建构造工程所需的框架文件,它们包括:工程文件、工作间文件、源代码文件、头文件、资源文件等等。AppWizard是Visual Studio 开发环境中使用最多的工具之一 。尽管如此,AppWizard也有它的不足之处。那就是常用的工程类型都是内建在Visual Studio 中,无法创建自己的 AppWizard。自从有了Custom AppWizard (Visual C++ 4.0 )以后,这个问题得到了解决。Custom AppWizard 也就是定制的AppWizard 。在创建类似的多个工程时,Custom AppWizards 显得特别有用。例如你创建的工程都是SDI ,并且都支持自动化(automation ),那么你就可以创建一个自己定制的 AppWizard ,将SDI 自动化设为默认选项。这样可以提高工作效率。此外,利用Custom AppWizard 也可以创建具有个性化的工程。例如你想要所有工程都有一个“关于”对话框,并且在这个对话框中显示个人信息或者公司的标徽及其它专有信息,每个源代码文件中都加上自己的专门注释。那么通过创建一个Custom AppWizard 很容易实现这个需求。你甚至可以定义并显示自己定制的对话框来收集工程类型所需的信息和选项。本文的第一部分我们将讨论AppWizard 的工作原理,然后在后续部分中循序渐进地学习如何创建Custom AppWizard 。最终我们将创建一个在实际编程中非常实用的Custom AppWizard 。并提供全部源代码。
在学会使用Custom AppWizard 之前,首先必须了解AppWizard 的工作原理,理解 AppWizard 是如何根据不同的用户选择来创建工程的。
AppWizard 有一个管理装置(manager ),它不是一个单独的应用程序。Custom AppWizard 运行于Visual Studio 框架之中。AppWizard 的这个所谓的“管理器”,实际上就是MFCAPWZ.DLL ,它控制不同的AppWizard 执行。在创建新工程的对话框中,“Project ”标签是默认的选项,列表框中显示出内建的工程类型。此外,这个列表框中还列出用户定制的AppWizard ,如图一所示。
111000101.gif
图一
这些定制AppWizard 文件扩展名为*.awx ,它们存放在一个特定的目录中。如果安装VC6.0 时是按照默认的路径安装的,则定制的AppWizard 文件在成功编译后都会被存放到\Program Files\Microsoft Visual Studio\Common\MSDev98\Template 文件夹中。注意列表框中此新的列表项“MFC AppWizard (exe) – VC 知识库”,这就是我们后面要定制的AppWizard 。从这里可以看出,只要产生了*.awx 文件,那么它就会与标准的(或者说内建的)Visual C++ AppWizard 一起自动显示在这个列表框中。
——CCustomAppWiz 类和Dictionary 字典
CCustomAppWiz 基类提供了MFCAPWZ.DLL 和Custom AppWizard 之间的通讯服务。CCustomAppWiz() 成员函数的实现就在MFCAPWZ.DLL 中。为了实现特定应用的行为,你只要从CCustomAppWiz 派生一个类,改写相应的虚拟函数,然后在MFCAPWZ.DLL 运行时调用SetCustomAppWizClass() 函数注册派生类即可。
通常,AppWizard 显示一系列对话框获取创建新工程所需的设置。每一个步进对话框显示不同的选项。AppWizard 将这些选项的值存储在一个串映射中。这个串映射就叫做Dictionary 字典。Dictionary 字典实际上是一个CCustomAppWiz 类的成员变量(m_Dictionary ),其类型为CMapStringToString 。Dictionary 将AppWizard 宏映射到相关联的值。这里所说的宏是指工程选项或设置的名称。例如,在创建MFC 的时候,你可以选择应用程序为SDI ,那么,Dictionary 中就会有一个名为PROJTYPE_SDI 的宏。Dictionary 中这个项目的值就是1 ,否则这个与这个宏关联的值为0 。
m_Dictionary 成员变量可以被用于创建宏,删除宏或者更新宏的值。因为m_Dictionary 是一个CMapStringToString 对象,肯定有相应的成员函数存取不同宏的值。下面的代码返回PROJTYPE_SDI 宏的值,它被用于判断这个工程是不是一个SDI 应用。
m_Dictionary.Lookup("PROJTYPE_SDI", m_strProjType);
if (_T("1") == m_strProjType)
{
// SDI 类型应用
}
else // 其它类型的程序
{
}
MFCAPWZ.DLL 提供了一些标准宏,任何其它定制AppWizard 所需要的宏都可以用SetAt 函数添加到Dictionary 字典中。你从在线文档中可以找到六十多个标准宏
当你创建Custom AppWizard 并按下Finish 按钮后,MFCAPWZ.DLL 用Dictionary 创建新的工程文件。每一个AppWizard (不论是标准的还是定制的)都有一套模板文件用于创建AppWizard 生成的工程源文件。Dictionary 中的值被用于与模板文件相连接来创建最终的输出(新的工程文件)。下面是一个例子,中文的基于对话框程序的模板资源文件名叫DlgLoc_chs.rc 。下面是从中摘录出的一段:
...
IDD_ABOUTBOX DIALOGEX 0, 0, 160, 129
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION " 关于$$ROOT$$"
...
注意这里 $$ROOT$$ 串的用法。当MFCAPWZ.DLL 创建新工程文件时,它首先扫描每一个模板,查找以$$ 开始并以$$ 结尾的串。在这两个前缀和后缀之间的文本串叫做占位苻。每一个占位符是Dictionary 中一个宏的名字。MFCAPWZ.DLL 在Dictionary 中查询占位符的值并用这个值替换占位符。当所有的占位符都被Dictionary 中相应的值替换之后,工程文件也就产生了。
——用MFCAPWZ.DLL 替代占为符
为了理解MFCAPWZ.DLL 用Dictionary 中宏的值替换模板文件的占位符。我们来做一个实验:
1、 用AppWizard 创建一个基于对话框的应用程序,将工程取名为MyTestDlgApp 。
2、 创建完工程之后,以文本方式打开MyTestDlgApp.rc 文件。
3、 找到IDD_ABOUT 的对话框模板资源。
4、 你应该看到原来模板文件中$$ROOT$$ 占位符已经被工程的名字(MyTestDlgApp )替换掉了。这是因为Dictionary 有一个ROOT 宏,其缺省值被设置为工程的名字。
宏即可被用于定义模板文件中指定的占位符的替换值,有时AppWizard 也用宏来协助步进对话框的显示,或者确定用哪个模板来创建新的工程文件。例如,如果Dictionary 中PROJTYPE_SDI 宏的值为1 ,则创建的应用程序是SDI 类型。但是,如果PROJTYPE_DLG 宏的值为1 的话,创建的应用程序是基于对话框的。根据宏的值是否为1 ,MFCAPWZ.DLL 使用不同的模板文件来创建工程文件。
大多数AppWizard 都由一系列固定的对话框组成。其中后一个对话框的显示完全依赖于前一个对话框所选择的选项来决定。这种多步进对话框形式称为轨迹。创建MFC 应用程序的AppWizard 是一个多轨迹的AppWizard 。
——多轨迹AppWizard
多轨迹AppWizard 提供了更为复杂的应用程序设置。为了理解多轨迹AppWizard 概念,请做一个如下实验:
1、 按下Ctrl+N 创建新工程
2、 单击“Project ”标签,然后选中“MFC AppWizard (exe) ”。
3、 注意对话框的标题条内容为“MFC AppWizard - Step 1 ”,没有指明总共有几步,这是因为总共的步进数在你决定要创建的MFC 工程类型前时未知的。
4、 看一下不同类型工程的选项有何差别:single document (SDI) ,multiple document (MDI) ,和 dialog-based 。选择multiple document (MDI) 类型,然后单击Next 按钮。
5、 注意标题条的内容中指定了总共的步进数——“MFC AppWizard - Step 2 of 6 ”。由于你选择了MDI 类型,这个类型总共有六个步进对话框,每一个步进对话框包含特定的基于文档的MFC 应用程序选项。
6、 单击Back 按钮,选择基于对话框的工程类型,然后单击Next 按钮。这一次标题条的内容指定的步进总数是——“MFC AppWizard - Step 2 of 4 ”。这说明创建基于对话框的应用程序共有四个步进对话框。
以上是对AppWizards 及其工作原理的讨论。在下一部分我们将尝试创建一个简单的Custom AppWizard 。
第二部分
在第一部分中我们介绍了Custom AppWizard 的概念及其工作原理。在这一部分,我们将尝试用Custom AppWizards 来创建一个最简单的Custom AppWizard 。
首先,我们先创建一个什么事情也不做的Custom AppWizard ,主要是了解它的创建过程和步骤。按Ctrl+N 打开New 对话框,新建一个Custom AppWizards 工程。填入工程名字后单击OK ,从步进对话框的标题中,我们可以了解到总共有两个步骤,在第一个步的对话框中包含三个输入域。
第一个输入域是新Custom AppWizards 的起点。它有三个单选按钮:
l         Existing project ——这个选项是以一个现存的工程作为蓝本来创建Custom AppWizards 。使用这个选项有两个缺点。第一,AppWizards 创建的是一个已经存在的工程。第二,AppWizards 创建的工程文件名和类名必须与现存工程的文件名和类名一致。
l         Standard MFC AppWizard steps ——这个选项是最常用的选项,它创建的AppWizard 模板可用于每一种MFC 支持的工程类型。从修改各种MFC 工程模板文件的灵活性方面,这个选项也是最灵活的。因为这是最通用的一种定制AppWizard 类型,所以我们将以它为例。
l         Your own customized steps ——这个选项全新定制一组步进对话框和选项。例如,假设你需要一个定制的AppWizard 来自动创建一个非MFC 应用程序。这时你就得用这个选项创建所有自己定制的对话框。
第二个输入域让你命名新建的定制AppWizard 。这个名字将被用于显示在New Project List 对话框中.
最后一个输入域用来指定定制步进的数目或者对话框的数目,它将被添加到新的定制AppWizard 中。有时候我们不需要额外的步进对话框,比如我们即将创建的简单AppWizard 就不用任何步进。但在第三部分中,我们将会学习如何定制步进对话框。
接下来,按Next 按钮继续到定制AppWizard 的第二步(对话框),也是最后一步。这个对话框中有两个域都是自解释的。第一个域定义新定制的AppWizard 是个可执行程序还是一个DLL 。第二个域指定语言支持选项。
设置工程的缺省选项
前面我们讲过用AppWizard 创建工程时可以有多种类型可以选择。这一部分我们创建的AppWizard 名字叫SDIAutomationWiz ,在默认情况下,用这个AppWizard 创建的工程类型是支持自动化的SDI 工程。
打开工程的New 对话框,在Project List 中选择Custom AppWizard ,在Project Name 编辑框中输入SDIAutomationWiz ,单击OK 进入第一个步进对话框,选择“Standard MFC AppWizard steps ”,然后指定一个它在Project List 中显示的名字。因为这个AppWizard 没有额外的步进对话框,因此步进数编辑框中填写0 ,单击Next 按钮到下一步。选择“MFC AppWizard Executable ”,语言支持为中文,单击Finish 按钮,出现确认对话框。单击OK 后便开始创建新的AppWizard 工程。
——定义CCustomAppWiz 类
虽然编译后的Custom AppWizard 文件扩展名都是.awx ,但是它实际上就是一个通常我们使用的Windows 动态链接库(DLL )文件。如果你打开SDIAutomationWiz.cpp 文件,你就会看到如下的DLLMain() 函数代码:
// Defining the DLLMain() Function
extern "C" int APIENTRY
DllMain(HINSTANCE hInstance, DWORD dwReason,
LPVOID lpReserved)
{
 if (dwReason == DLL_PROCESS_ATTACH)
 {
  TRACE0("SDIAUTOMATIONWIZ.AWX Initializing!\n");
 
  // Extension DLL one-time initialization
  AfxInitExtensionModule(SDIAutomationWizDLL, hInstance);
 
  // Insert this DLL into the resource chain
  new CDynLinkLibrary(SDIAutomationWizDLL);
 
  // Register this Custom AppWizard with MFCAPWZ.DLL
  SetCustomAppWizClass(&SDIAutomationWizaw);
 }
 else if (dwReason == DLL_PROCESS_DETACH)
 {
  TRACE0("SDIAUTOMATIONWIZ.AWX Terminating!\n");
 
  // Terminate the library before destructors are called
  AfxTermExtensionModule(SDIAutomationWizDLL);
 }
 return 1;   // ok
}
除了常规的CDynLinkLibrary MFC 扩展动态链接库例程之外,还有一个对SetCustomAppWizClass() 函数的调用。这个函数是从MFCAPWZ.DLL 输出的,用于传递定制AppWizard 中CCustomAppWiz 派生类的指针。因为MFCAPWZ.DLL 通过调用CCustomAppWiz 的成员函数来控制所有AppWizard 的执行,因此它必须用这个指针来调用CCustomAppWiz 派生类中重载的成员函数。
有一点必须牢记在心,那就是尽管你创建了定制的AppWizard ,但MFCAPWZ.DLL 仍然控制着一切。换句话说,你定制的AppWizard 只是用于显示步进对话框,确定对话框以什么顺序显示,以及设置新工程的模人选项。一些重要的工作,诸如解析模板文件,合并Dictionary 中的宏和模板文件中的占为符,创建工程文件等等还是要MFCAPWZ.DLL 来做。
前面我们讲过,CCustomAppWiz 类负责AppWizard 与MFCAPWZ.DLL 之间的通讯。实际上这种通讯是单边的。MFCAPWZ.DLL 告诉你的CCustomAppWiz 对象什么时候需要调用相应的虚拟成员函数。
CCustomAppWiz 类中大约有十来个函数,其中只有五个函数是可以看到并使用的常用例程。通过这些函数的命名你基本上就能了解其主要作用。例如,InitCustomAppWiz() 函数是进行初始化,包括初始化宏。除此之外,ExitCustomAppWiz() 函数的作用是卸载AppWizard 。
另外,还有两个函数用来控制步进对话框的显示顺序。Next() 和Back() 。不说肯定你也知道,这两个函数与AppWizard 对话框中的Next 和Back 按钮是关联的。
最后一个很重要的函数是CustomizeProject() ,一旦程序员完成工程选项的设置,AppWizard 便创建工程的make 文件并定义工程缺省的debug 和release 配置。然后AppWizard 调用CustomizeProject() 函数,以便定制的AppWizard 能在存储工程之前修改make 文件设置。
——宏指令的处理
我们已经知道了宏的初始化是在InitCustomAppWiz() 中进行的,我们也知道了CCustomAppWiz 类有一个成员变量m_Dictionary ,它存储宏名及其值。因为这个成员变量是CMapStringToString 类型的,用标准的MFC 映射函数就能get 或者set 不同的宏,请看下列代码:
// retrieve value for Automation
CString strValue;
m_Dictionary.Lookup(_T("AUTOMATION"), strValue);
 
// Include support for context sensitive help
m_Dictionary.SetAt(_T("HELP"), strValue.Compare("1"));
现在打开SDIAutomationWizAW.cpp 文件,在InitCustomAppWiz() 函数末尾敲入:
m_Dictionary.SetAt(_T("PROJTYPE_SDI"), _T("1"));
m_Dictionary.SetAt(_T("PROJTYPE_MDI"), _T("0"));
m_Dictionary.SetAt(_T("AUTOMATION"), _T("1"));
然后构造(build )定制的AppWizard 工程。如果没有出错的话,AppWizard 的.awx 文件会被自动拷贝到专门的目录中,以便MFCAPWZ.DLL 能找到它。
现在按下Ctrl+N ,新定制的AppWizard 应该出现在New 对话框的Project 清单中。如果你使用新的AppWizard ,你会看到工程的默认选项是SDI 程序并支持自动化。通过这个简单的Demo ,我们基本上了解了如何通过定制AppWizard 来设置默认的工程选项。
如果要分发你创建的AppWizard ,只要分发.awx 文件就可以了,把它拷到Visual Studio 的模板文件目录即可。
在这一部分,我们定制了一个简单的AppWizard ,通过一个例子示范了如何处理宏字典。在第三部分中,我们将涉及更多定制AppWizard 的内容,并且还要制作一个实用价值很高的AppWizard 。包括新增加一个步进对话框,获得新步进对话框中的输入信息。用这个定制的AppWizard 创建的所有工程都会在其“关于”对话框中显示在步进对话框输入的信息,并通过静态控制和图像建立URL 链接。此外,用这个AppWizard 创建的每一个源文件都会自动建立程序员自己的专用注释。
第三部分
 
  我们在第二部分中示范的AppWizard例子很简单,没有任何实用性。在这一部分我们将讨论几个关于制作AppWizard的高级话题。然后利用VC提供的Custom AppWizard来创建一个在编程中非常实用的AppWizard。与MFC AppWizard(exe) 产生的常规应用程序相比,用这个定制的AppWizard所创建的工程构造出来的应用程序有两个定制特点:
一是所有程序都会有一个定制的“关于”对话框,在这个对话框中显示自己或公司的有关信息,对话框中还有一个将用户定向到Web站点的静态文字控制或图像(icon和bmp)。
二是工程中每一个源代码文件(*.h和*.cpp)的最上面都会有程序编写着的名字及程序创建日期以及简单的程序说明和注释。
这一部分要介绍的主要技术包括:
1、 如何定义和添加AppWizard要用到步进对话框。
2、 如何将Custom AppWizard的专用宏添加到字典中。
3、 如何修改定制AppWizard要用到的模板文件,包括inf文件,资源模板文件等。
4、 将输入信息存储到注册表中,使得每一个工程的公共信息都不用重复输入。

下面我们就开始吧: 进入Visual C++开发环境,如图一:
111000301.gif
图一

选择“Project”标签,工程名字可以随便取。这里我取的名字是“VckbaseWiz”,其它选项都默认。
然后单击OK。进入下一个对话框。如图二:
111000302.gif
图二

因为我们要建一个标准的MFC AppWizard,所以选择“Standard MFC AppWizard steps”单选按钮。AppWizard的命名最好规范一些,这样便于记忆和辨认。与AppWizard的工程名不同,这个名字要在Project类型清单中列出。我们把它命名为“MFC AppWizard(exe)——VC知识库”。因为在我们创建的这个Custom AppWizard中有一个额外的对话框,所以在设置步进步骤的数目时输入1。单击“Next”进入下一个对话框。如图三:
111000303.gif
图三

单选按钮部分选择“MFC AppWizard Executable”,语言支持部分选择 “中文[中国](APPWZCHS.DLL)”。然后单击“Finish”进入确认对话框。单击“OK”开始产生定制AppWizard的程序代码。

添加定制的对话框

因为我们的Custom AppWizard有一个额外的对话框。所以我们首先要定制这个对话框的模板资源,以便它能收集输入信息,今后用此定制AppWizard创建的所有应用程序的“关于”对话框中都会显示这些信息。选择“ResourceView”标签,打开工程资源表中的“Dialog”。你会发现有一个原始对话框,其ID是IDD_CUSTOM1。定制后的对话框应该如图四:
111000304.gif
图四

表一中是对话框中编译框控制的ID,注意这里的“程序介绍”和“代码注释”编辑框控制的风格属性都要设置成“Multiline”。
控制控制ID
程序员编辑框IDC_EDT_PROGRAMMER
Web 站点编辑框IDC_EDT_WEB_PAGE
程序介绍编辑框IDC_EDT_GENERAL_INFO
代码注释编辑框IDC_EDT_COMMENT_INFO
表一 对话框中的控制的资源IDs

添加完对话框的资源,我们还要为对话框控制定义成员变量。进入菜单“View|ClassWizard”,选择“Member Variables”标签,程序变量的类型都是CString类型,名称分别为:m_strProgrammer、m_strWebPage、m_strGeneralInfo、m_strCommentInfo。 接下来是实现CCustom1Dlg对话框类初始化成员函数OnInitDialog()。在OnInitDialog()的return语句前面添加如下代码
//
VckbaseWizaw.m_Dictionary.Lookup("PROGRAMMER", m_strProgrammer);
VckbaseWizaw.m_Dictionary.Lookup("WEB_PAGE", m_strWebPage);
VckbaseWizaw.m_Dictionary.Lookup("GENERAL_INFO", m_strGeneralInfo);
VckbaseWizaw.m_Dictionary.Lookup("COMMENT_INFO", m_strConmmentInfo);
UpdateData(FALSE);
//
此段代码的作用是从Dictionary字典中获取定制AppWizard宏的值。VckbaseWizaw是一个CVckbaseWizAppWiz(派生于CCustomAppWiz)类型的全局对象,它在VckbaseWizaw.h中定义。接下来是从CCustom1Dlg的构造函数中删除初始化代码,因为它们的值将在CVckbaseWizAppWiz::InitCustomAppWiz()函数中初始化。
我们还要做一件事情就是存储输入对话框中的数据,也就是说要用创建新工程时输入的数据更新Dictionary字典。这件事情要在CCustom1Dlg::OnDismiss()函数中完成。在CCustom1Dlg::OnDismiss()的return语句前面加入以下代码:
//
VckbaseWizaw.m_Dictionary.SetAt("PROGRAMMER", m_strProgrammer);
VckbaseWizaw.m_Dictionary.SetAt("WEB_PAGE", m_strWebPage);
VckbaseWizaw.m_Dictionary.SetAt("GENERAL_INFO", m_strGeneralInfo);
VckbaseWizaw.m_Dictionary.SetAt("COMMENT_INFO", m_strCommentInfo);
CTime date = CTime::GetCurrentTime();
CString szDate = date.Format( "%A, %B %d, %Y" );
VckbaseWizaw.m_Dictionary["DATE_INFO"] = szDate;
return TRUE; 
//

如果你现在构造这个定制的AppWizard并用它来创建新的应用程序的话,你可以看到刚才创建的对话框,但是还有问题,那就是如何将输入对话框的值作为宏存储在AppWizard的字典中,以便今后在新的工程中使用?答案是使用模板文件中的占位符,AppWizard正是用这些包含有占位符的模板文件来构造新的工程文件。在下面的主题中,我们将讨论如何创建新的模板文件。

创建自己的模板文件

对于一个用AppWizard创建的默认的MFC程序来说,用于定义“关于”对话框对象和App对象的文件是相同的。我们在本文中定制的AppWizard除了要产生常规的新工程文件模板以外,还要创建一个全新的模板文件——About.h。这个文件必须存放在AppWizard工程的Template文件夹中。下面是About.h的代码:
/
// Project:$$ROOT$$
// Author:$$PROGRAMMER$$
// Date:$$DATE_INFO$$
// Description:$$COMMENT_INFO$$
//
/
#pragma once
#include "StatLink.h"
/
// CAboutDlg dialog used for App $$Root$$

class CAboutDlg : public CDialog { public: CAboutDlg(); protected: CStaticLink m_wndLink1; CStaticLink m_wndLink2; CStaticLink m_wndLink3; CStaticLink m_wndLink4; // Dialog Data //{{AFX_DATA(CAboutDlg) enum { IDD = IDD_ABOUTBOX }; //}}AFX_DATA
// ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CAboutDlg) protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support //}}AFX_VIRTUAL // Implementation protected: //{{AFX_MSG(CAboutDlg) virtual BOOL OnInitDialog(); //}}AFX_MSG DECLARE_MESSAGE_MAP() }; CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD) { //{{AFX_DATA_INIT(CAboutDlg) //}}AFX_DATA_INIT } void CAboutDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CAboutDlg) //}}AFX_DATA_MAP } BEGIN_MESSAGE_MAP(CAboutDlg, CDialog) //{{AFX_MSG_MAP(CAboutDlg) //}}AFX_MSG_MAP END_MESSAGE_MAP() BOOL CAboutDlg::OnInitDialog() { CDialog::OnInitDialog(); m_wndLink1.m_link = _T("http://www.vckbase.com"); m_wndLink2.m_link = _T("http://www.vckbase.com"); m_wndLink3.m_link = _T("mailto:vckbase@publik.hk.hi.cn"); m_wndLink4.m_link = _T("http://www.vckbase.com"); m_wndLink1.SubclassDlgItem(IDC_STATIC_ICON, this); m_wndLink2.SubclassDlgItem(IDC_STATIC_TEXT, this); m_wndLink3.SubclassDlgItem(IDC_STATIC_MAIL, this); m_wndLink4.SubclassDlgItem(IDB_STATIC_IMG, this); return TRUE; // return TRUE unless you set the focus to a control // EXCEPTION: OCX Property Pages should return FALSE }
IDC_STATIC_ICON、IDC_STATIC_TEXT、IDC_STATIC_MAIL、IDB_STATIC_IMG是“关于”对话框中要用到的四个控制,它们是在Dlgres.h和resource.h中定义的,稍候我们在修改资源模板文件时会定义这四个控制ID。
About.h的代码中包含了OnInitDialog()处理。当它被调用时,做一些控制的初始化。接下来我们要做的事情是把模板添加到工程中。
前面我们讲过,Custom AppWizard创建新工程的时候,由MFCAPWZ.DLL负责用模板文件来生成新工程的源文件。那么如何告诉AppWizard除了要产生默认的源文件以外,你还要求创建一个新模板文件呢?实际上,要解决这个问题需要两个步骤。第一步,你必须更新资源文件,在创建新工程时,将自定义资源插入到每一个Custom AppWizard创建的文件中。为此,必须以文本模式打开.rc文件,定位到下面注释的位置:
//TEMPLATE
你应该看到这个注释行的后面列出了所有模板文件,它们都是定制AppWizard要用来生成新工程源文件的模板。我们要在其中加入一个新的模板文件——About.h,将下面这几行代码加到模板文件清单中:
//
ABOUT.H                TEMPLATE DISCARDABLE    "template\\about.h"
VCKBASELOGO.BMP        TEMPLATE DISCARDABLE    "template\\vckbaselogo.bmp"
HYPRLINK.H             TEMPLATE DISCARDABLE    "template\\HyprLink.h"
STATLINK.H             TEMPLATE DISCARDABLE    "template\\StatLink.h"
STATLINK.CPP           TEMPLATE DISCARDABLE    "template\\StatLink.cpp"
//
这里hyprlink.h、statlink.h、statlink.cpp三个文件是超链接类,它是由MSDN 专栏作家Paul Dilascia 编写的可重用类,很多读者一定熟悉Paul 在MSDN的专栏——《C++ Q&A》,他的大多数文章的示例代码都用到这个类在“关于”对话框中创建静态超链接。笔者深受启发,在VC知识库中也多次使用这个类来做Demo程序的“关于”对话框。但每次做都要去重复定制“关于”对话框岂不是很累。所以才决定做一个自己的AppWizard。 下一步,我们的任务是修改newproj.inf文件

修改newproj.inf文件

这个文件乍一看有点怪模怪样,它位于AppWizard工程的Template目录。在创建新的工程文件时,MFCAPWZ.DLL需要用到这个文件。前面我们提到过占位符,它以“$$”作为前缀和后缀,并且它与AppWizard宏的名字相关联,宏名所对应的宏的值存储在Dictionary字典中。除此之外,“$$”也被用于表示某种命令——这些命令被称为AppWizard指令,它们被用于处理模板文件。 Newproj.inf中的第一行指令是注释行,用“$$//”表示,这四个字符后的任何文本都被MFCAPWZ.DLL忽略,不予解析。打开newproj.inf文件,其第一行就是:
      $$// newproj.inf = template for list of template files 
通常在一个模板文件被转换为新工程中的源文件时,宏的作用是命名目的文件。但是,某些标准文件的名字与所建工程类型无关,不管创建什么样的工程,其名字都是一样的。例如:stdafx.h和stdafx..cpp。下面的代码行告诉MFCAPWZ.DLL将stdafx.h和stdafx.cpp文件拷到新工程文件夹,文件名不变。这里要注意两个文件名(模板文件和目的文件)之间使用一个Tab键分开,这一点很重要,而且经常被忽略。如果你是手工编写此代码行,必须保证两个文件之间只能是一个Tab,不能是别的任何字符。 stdafx.h StdAfx.h stdafx.cpp StdAfx.cpp newproj.inf文件中的下一条指令是$$IF。它检查括弧中宏的布尔状态。注意这里宏的使用方法,$$IF中用的不是宏本身,换句话说,如果你想在代码中引用宏的话,必须加上前缀和后缀$$,如$$PROGRAMMER$$。 参见下面的代码段,其前两行表示的意义是:如果此工程不是DLL并且不是基于对话框的程序,则将模板文件Frame.h和Frame.cpp拷贝成名字为frame_hfile 和frame_ifile宏所表示的文件名。你可能还记得用AppWizard创建MDI程序时最后一个对话框可以让你根据不同的类命名文件,因为程序员可以改变这些类的名字,其对应的值存储在宏中。这个对话框就象我们在前面定制对话框那样,使用输入对话框的文件名并更新Dictionary字典中的宏。请看一下代码如何命名目的文件:
$$IF(!PROJTYPE_DLL)
$$IF(!PROJTYPE_DLG)
frame.h    $$frame_hfile$$.h
frame.cpp  $$frame_ifile$$.cpp
下面的$$IF指令和前面的两个$$IF的作用一样,唯一不同的是根据这个宏判断的结果导致另一个从模板到文件的拷贝。
$$IF(MDICHILD)
childfrm.h      $$child_frame_hfile$$.h
childfrm.cpp    $$child_frame_ifile$$.cpp
最后,每一个$$IF指令都必须有匹配的$$ENDIF指令。此外,指令行末尾可以加入类似C++的注释,如:
$$ENDIF //MDICHILD
$$ENDIF //!PROJTYPE_DLG
现在你应该很容易理解newproj.inf文件了,我们来加入自己的代码,首先,在文件最上面(在注释的后面)加入以下代码,记住在模板文件和目的文件之间是一个Tab键。
//
$$IF(PROJTYPE_DLG)
$$IF(ABOUT)
about.h     about.h
HYPRLINK.H  HyprLink.h
STATLINK.CPP     StatLink.cpp
STATLINK.H  StatLink.h
$$ENDIF //ABOUT
$$ELIF(PROJTYPE_MDI)
about.h     about.h
HYPRLINK.H  HyprLink.h
STATLINK.CPP     StatLink.cpp
STATLINK.H  StatLink.h
$$ELIF(PROJTYPE_SDI)
about.h     about.h
HYPRLINK.H  HyprLink.h
STATLINK.CPP     StatLink.cpp
STATLINK.H  StatLink.h
$$ENDIF //PROJTYPE_DLG
//
这段代码的主要作用是保证about.h、hyprlink.h、statlink.cpp、statlink.h四个文件在三种情况下都被拷贝:
  • 基于对话框的工程类型,程序员不用取消“About Box”复选框。
  • SDI类型的工程
  • MDI类型的工程
其次,不要忘了拷贝我们在“关于”对话框中要用到的图像资源文件,定位到newproj.inf的/res行,然后找到这一段的如下代码行
$$IF(!PROJTYPE_DLL)
=:root.ico              res\$$root$$.ico
在这行代码后加上:
=:root.ico              res\APP.ico
=:VCKBASELOGO.BMP       res\VCKBASELOGO.BMP
newproj.inf文件的修改就OK了。

修改AppWizard模板

前面我们已经知道了如何创建新的模板文件并将它添加到新的工程中(在newproj.inf文件中操作)。现在我们来学习如何修改创建Custom AppWizard时由AppWizard建立的一般模板。

——修改模板资源定义文件

由于我们在AppWizard所创建之新工程的“关于”对话框中加入了几个静态控制,那么我们就必须在模板文件中将这些控制的资源ID定义好。以便AppWizard在生成新工程的源文件时也能在目标源文件中定义这些资源ID。这里要涉及两个文件,一个是AppWizard工程Template目录中的Dlgres.h,另一个是同目录中的resource.h。前者用于基于对话框的程序,后者用于SDI和MDI。在Dlgres.h文件的最前面(注释之后)加上如下代码:
//
$$IF(PROJTYPE_DLG)
$$IF(ABOUT)
#define IDC_STATIC_ICON                 1000
#define IDC_STATIC_TEXT                 1001
#define IDC_STATIC_MAIL                 1002
#define IDB_STATIC_IMG                  129
$$ENDIF //ABOUT
$$ELIF(PROJTYPE_MDI)
#define IDC_STATIC_ICON                 1000
#define IDC_STATIC_TEXT                 1001
#define IDC_STATIC_MAIL                 1002
#define IDB_STATIC_IMG                  129
$$ELIF(PROJTYPE_SDI)
#define IDC_STATIC_ICON                 1000
#define IDC_STATIC_TEXT                 1001
#define IDC_STATIC_MAIL                 1002
#define IDB_STATIC_IMG                  129
$$ENDIF //PROJTYPE_DLG
//
找到_APS_NEXT_RESOURCE_VALUE和_APS_NEXT_CONTROL_VALUE,并降下一个值的定义改为:
#define _APS_NEXT_RESOURCE_VALUE        130
#define _APS_NEXT_CONTROL_VALUE         1003
如法炮制resource.h文件,不同的是IDB_STATIC_IMG的值为:
#define IDB_STATIC_IMG  130
而#define _APS_NEXT_RESOURCE_VALUE定义的下一个值是131。

——修改模板资源文件

因为我们创建的定制AppWizard包含了一个新对话框,所以你必须在资源模板文件中插入一个新的对话框模板资源。你在创建定制AppWizard时,你规定了起始点是“Standard MFC AppWizard steps”。所以在Template目录中,你会看到用这个定制AppWizard创建每种类型的MFC可执行文件所需的全部模板文件。这意味着你可以按自己的要求任意修改这些模板文件。但是我们要确定如何改,以及改什么!这个目录中的模板资源文件(.rc)不止一个,到底应该改哪一个呢?,其实细想一下,很容易确定要改哪一个rc文件。
主要的资源文件名中都包含有三个字母的后缀,它表示语言支持(English=enu,Chinese=chs等等)。此外还有每个资源文件的本地化Macintosh版本。因此,对于一个面向运行Windows的Intel PC的中文版应用程序来说,资源文件的数目无外乎四个:all.rc、dlgall.rc、dlgloc_chs.rc、loc_chs.rc。
打开all.rc和dlgall.rc文件,你会发现其中的指令和宏都是用于支持语言和平台的基本资源文件。进而可以断定这两个文件包含特定语言和平台所需的资源定义,由串表和对话框这样的资源使用。排除了这两个文件,那么我们必须修改的两个对话框资源文件非dlgloc_chs.rc和loc_chs.rc莫属。每种语言之所以有两个资源文件是因为其中一个文件(loc_chs.rc)用于基于文档/视图的应用程序(SDI和MDI),另一个文件(dlgloc_chs.rc)用于基于对话框的程序。
既然知道了要修改哪一个资源文件,那么就把对话框资源添加进去吧,遗憾的是你不能象往常创建对话框模板资源那样用资源编辑器来做这件事情。这是因为在你试图打开模板资源文件的时候,资源编辑器会去编译文件的资源。而现在这些模板文件含有AppWizard指令,文件编译将不会成功,因此Visual Studio会强行让你用文本方式(Edit code)打开它。也可以在“File Open Dialog”对话框中,将“Open As”设为“Text”。 按照上面所讲的方法打开loc_chs.rc文件,用下面的代码替换IDD_ABOUT对话框模板资源定义:
$$IF(ABOUT)
IDD_ABOUTBOX DIALOG DISCARDABLE  34, 22, 313, 159
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "关于 111"
FONT 9, "宋体"
BEGIN
    ICON            IDR_MAINFRAME,IDC_STATIC_ICON,11,10,21,21
    LTEXT           "$$GENERAL_INFO$$",IDC_STATIC,46,14,237,45
    CONTROL         129,IDB_STATIC_IMG,"Static",SS_BITMAP | 
                    WS_BORDER,46,72,110,36
    LTEXT           "@@VCKBASE 版权所有 (C) $$YEAR$$@@",IDC_STATIC,187,83,106,8
    LTEXT           "VC 知识库\n$$WEB_PAGE$$",IDC_STATIC_TEXT,187,97,73,
                    16
    DEFPUSHBUTTON   "@@确定@@",IDOK,56,117,84,14,WS_GROUP
    LTEXT           "@@与我们联系@@",IDC_STATIC_MAIL,187,119,58,8
END
$$ENDIF //ABOUT
接下来用相同的方法打开dlgloc_chs.rc文件,如法炮制。注意对话框定义中宏的使用方法。它说明了用户在Custom AppWizard对话框中输入的信息如何最终反映在资源文件中。现在如果你构造AppWizard工程,则它可以创建基于对话框的应用程序,填写完“程序员”、“Web 站点”等信息,并构造新创建的工程就可以欣赏定制的“关于”对话框了。但是如果对话框中没有系统菜单怎么办呢?从哪里访问“关于”对话框呢?为了解决这个问题,我们还要对资源模板文件(dlgloc_chs.rc)进行修改。在它定义的主对话框中加一个“关于”按钮。这样的话,定制的AppWizard创建每一个基于对话框的程序时都会自动在对话框上加一个“关于”按钮。打开dlgloc_chs.rc文件,将主对话框的资源定义替换为以下代码,注意“帮助”按钮的位置取决于是否创建“关于”按钮。
IDD_$$SAFE_ROOT$$_DIALOG DIALOGEX  0, 0, 320, 200 
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
EXSTYLE WS_EX_APPWINDOW
CAPTION "$$TITLE$$"
FONT @@9@@, "@@宋体@@"
BEGIN
    DEFPUSHBUTTON   "@@确定@@",IDOK,260,7,50,14
    PUSHBUTTON      "@@取消@@",IDCANCEL,260,23,50,14
$$IF(ABOUT)
              PUSHBUTTON                    "@@关于(&A)@@",ID_APP_ABOUT,260,45,50,14
$$IF(HELP)
    PUSHBUTTON      "@@帮助(&H)@@",ID_HELP,260,40,50,14
$$ENDIF
$$ELIF(HELP)
    PUSHBUTTON      "@@帮助(&H)@@",ID_HELP,260,45,50,14
$$ENDIF
       LTEXT           "@@TODO: 在这里设置对话控制。@@",IDC_STATIC,50,90,200,8
END
——修改模版头文件和模板实现文件

为了在AppWizard所创建的每一个工程源文件中加上作者自己的专门注释(比如,作者姓名、代码许可声明、创建日期等),我们必须修改AppWizard工程Template目录中所有的.h文件和.cpp文件,在每个文件最上面添加如下代码段:
/
// Project:$$ROOT$$
// Author:$$PROGRAMMER$$
// Date:$$DATE_INFO$$
// Description:$$COMMENT_INFO$$
//
/
——修改文档/视图模板文件和对话框模板文件

这一部分我们将修改文档/视图类和对话框类的头文件和实现文件,之所以要改这些文件是因为缺省的视图实现文件和对话框实现文件通常都要声明和实现默认的CAboutDlg类,而我们在前面创建的about.h文件中已经包含了CAboutDlg的声明。
如果你用AppWizard创建一个名叫MyApp的SDI或者MDI程序,主程序类的声明将会在MyApp.h中,实现将会在MyApp.cpp中。这是因为名为$$ROOT$$的宏其值被设置为工程的名字,并且被用来命名包含主程序类定义和声明的文件。newproj.inf中的两行代码证明了这一点:
root.h   $$root$$.h
root.cpp $$root$$.cpp
1、 打开root.cpp文件(在Template目录中),按照如下的步骤进行修改:
2、 删除文件尾部的CAboutDlg类声明。
3、 删除所有的CAboutDlg成员函数。添加如下代码:
// App command to run the About dialog
void $$APP_CLASS$$::OnAppAbout()
{
       CAboutDlg().DoModal();
}
4、 到文件顶部,在#include "$$root$$.h"语句之后加上,#include "about.h"
现在基于文档/视图的模板文件已经修改完成,下面要修改基于对话框的模板文件。分析一下root.h和root.cpp以及dlgroot.h和dlgroot.cpp模板文件的作用,不难得出root.h和root.cpp模板用于创建所有基于文档/视图程序的主程序类源文件,dlgroot.h和dlgroot.cpp模板用于创建所有基于对话框程序的主程序类源文件。与基于文档/视图的应用程序不同,基于对话框的程序其“关于”对话框的定义与主程序类的定义不在同一个文件当中。而是定义在主对话框的实现文件中,其模板文件是dialog..cpp。打开dialog.h文件(位于Template目录),定位到以下代码处:
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
插入下面的函数声明:
afx_msg void OnAbout();
打开dialog.cpp,按照以下步骤进行修改:
1、 删除$$IF(ABOUT) 到 $$ENDIF之间的所有代码行。
2、 在$$IF(ABOUT) 和 $$ENDIF //ABOUT之间加上语句 #include "about.h":
$$IF(ABOUT)
#include "about.h"
$$ENDIF //ABOUT
3、 定位到MESSAGE_MAP 部分中的ON_WM_SYSCOMMAND()位置,添加下列代码行:
ON_BN_CLICKED(ID_APP_ABOUT, OnAbout)
4、 在文件末尾添加下列函数定义:
$$IF(ABOUT)
void $$DLG_CLASS$$::OnAbout()
{
       CAboutDlg().DoModal();
}
$$ENDIF //ABOUT
到此,定制AppWizard要做的主要工作以及要编写的主要代码已经完成,在构造和测试它之前,让我们再做一些锦上添花的工作。每次用AppWizard产生工程的时候,其最后一个对话框是一个确认对话框,其中总结性地显示在前面一系列对话框中做出的选择或者选项。下面我们将学习如何轻松确认输入信息,将我们在AppWizard定制对话框中输入的信息也显示在这个确认对话框里。

修改confirm.inf文件

打开confirm.inf文件,你会马上感觉到这个文件与newproj.inf太相似了,其中充斥着大量的AppWizard指令和宏。我们对它的修改很简单,把自己定义的宏加入这个文件即可,如果你想显示一下在定制对话框中输入的内容,那么在confirm.inf文件的顶部加入下面的代码就可以了:
$$IF(PROJTYPE_DLG)
$$IF(ABOUT)
定制对话框信息:
     作者:$$PROGRAMMER$$
        网站:  $$WEB_PAGE$$
     程序说明:$$GENERAL_INFO$$
     程序注释:$$COMMETN_INFO$$
$$ENDIF //ABOUT
$$ELIF(PROJTYPE_MDI)
定制对话框信息:
     作者:$$PROGRAMMER$$
        网站:$$WEB_PAGE$$
     程序说明:$$GENERAL_INFO$$
     程序注释:$$COMMETN_INFO$$
$$ELIF(PROJTYPE_SDI)
定制对话框信息:
     作者:$$PROGRAMMER$$
        网站:$$WEB_PAGE$$
     程序说明:$$GENERAL_INFO$$
     程序注释:$$COMMETN_INFO$$
$$ENDIF //PROJTYPE_DLG
在这个文件中,$$IF/$$ELIF/$$ENDIF的结构和语法完全与newproj.inf一样。$$IF/$$ENDIF指令之间的语句是按原样显示的对话框输入信息。

在注册表中存储宏

通过前面的努力,我们已经创建了一个自己定制的AppWizard,用它创建的每一个MFC应用程序,不论是SDI还是MDI,或是基于对话框,都包含一个特制的“关于”对话框,在这个对话框中可以显示关于作者的信息,程序说明,以及静态超链接。另外这个AppWizard还会在每个源代码文件中加上专门定制的注释说明。但美中不足的是每次创建新工程的时候都要程序员重新输入信息。而这些信息对于每一个新的应用程序都是一样的。为了在可用性方面使我们的程序更加完美,下面将针对这个问题对我们定制的AppWizard进行改进,将工程的公共信息存储在注册表中,当创建新的工程时,AppWizard会首先从注册表中读取这些公共信息,不需要重新输入,除非你确实要改变这些公共信息。

——在CCustomAppWiz派生类中引入注册表的操作


为了存取注册表信息,我们在定制的AppWizard工程中使用了一个封装类CRegistry。这个类封装了针对注册表的常用操作。我们要对CVckbaseWizAppWiz实现进行修改。以便能在它的实现中使用CRegistry类存取注册表。方法如下:
1、 打开VckbaseWizAw.cpp,在“#include "chooser.h"” 包含语句后面加上“#include "registry.h"”
2、 在InitCustomAppWiz成员函数前面加上如下注册表键值定义:
#define VCKBASEWIZ_KEY "Software\\VCKBASE\\VckbaseWiz"
3、 在VCKBASEWIZ_KEY #define指令之后,定义下列静态结构,其中包含:宏名、注册表值名、宏的缺省值。注册表值名被用来在注册表中查找VCKBASEWIZ_KEY指定的键。如果没有找到键值(例如第一次运行程序时),则会使用宏的缺省值:
static struct
{
       char szMacroName[50];
       char szRegistryValueName[50]; 
       char szMacroDefaultValue[1024];//512个汉字
} macroPairs[] = {
       "PROGRAMMER", "Programmer", "程序员",
       "WEB_PAGE", "Web Page", "网站",
       "GENERAL_INFO", "General Info", "程序描述",
       "COMMENT_INFO", "Comment Info", "程序注释"
};
4、 将下列代码添加到CVckbaseWizAppWiz::InitCustomAppWiz()函数末尾,其作用是在堆栈创建CRegistry对象,然后遍历上一步定义的静态结构。对于这个宏结构数组每一个元素,程序都会在注册表中找对应值,如果找到则取注册表中的值,否则取缺省值。不论哪一种情况,宏的值一旦建立,Dictionary字典的值就被更新为当前宏的值:
CRegistry registry(HKEY_LOCAL_MACHINE, VCKBASEWIZ_KEY);
CString strValue;
       for (int i = 0; i < sizeof macroPairs / sizeof macroPairs[0]; i++)
       {
              if (registry.ReadString(macroPairs[i].szRegistryValueName, strValue.GetBuffer(strValue.GetLength())))
              {
                     m_Dictionary.SetAt(macroPairs[i].szMacroName, strValue);
              }
              else
              {
                     m_Dictionary.SetAt(macroPairs[i].szMacroName, macroPairs[i].szMacroDefaultValue);
                     if (m_Dictionary.Lookup(macroPairs[i].szMacroName, strValue))
                     {
                            registry.WriteString(macroPairs[i].szRegistryValueName, strValue.GetBuffer(strValue.GetLength()));
                     }
              }
       }
5、 将下列代码添加到CVckbaseWizAppWiz::ExitCustomAppWiz()函数末尾,其作用是当卸载定制的AppWizard DLL时程序会调用这个函数存储数据。
CRegistry registry(HKEY_LOCAL_MACHINE, VCKBASEWIZ_KEY);
CString strValue;
       for (int i = 0; i < sizeof macroPairs / sizeof macroPairs[0]; i++)
       {
              if (m_Dictionary.Lookup(macroPairs[i].szMacroName, strValue))
              {
                     registry.WriteString(macroPairs[i].szRegistryValueName, strValue.GetBuffer(strValue.GetLength()));
              }
       }
大功告成,现在构造定制的AppWizard。如果没有出错的话,则编译生成的.awx文件会被自动拷贝到Visual Studio的Template目录。 接下来测试一下我们定制的AppWizard,New一个新工程,在工程类型列表中选择“MFC AppWizard (exe) – VC知识库”,创建一个基于对话框的应用程序,最后一个对话框是我们在Custom AppWizard工程中定制的对话框,如图五:
111000305.gif
图五

输入相应的信息后,单击“Finish”按钮,显示确认对话框,你在定制对话框中输入的信息也应该在此确认对话框中显示。单击“OK”按钮创建工程。然后编译并运行。对话框中可以见到三个按钮:“确定”、“取消”、“关于”。单击“关于”按钮,弹出对话框如图六:
111000306.gif
图六
这就是我们定制的“关于”对话框。这个对话框中有带URL链接的静态文字、icon和Bitmap图像。
我真的觉得它很酷!