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

为 Visual Studio .NET 创建项目项代码模板

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.86/5 (41投票s)

2005 年 3 月 15 日

17分钟阅读

viewsIcon

192701

downloadIcon

2477

本文描述了 Visual Studio .NET 2003(和 2005)中的新项目项代码模板的工作方式,并介绍了一个可以轻松编写新模板的组件。

目录

Template Manager

引言

本文描述了在 Visual Studio .NET 中添加新项目项时使用的代码模板的工作方式。CodeProject 上还有几篇触及此主题的文章(其中几篇列在底部“参考文献”部分),本文旨在更详细地描述其工作原理。

此外,还介绍了一个名为 Krevera Template Manager 的组件,它可以(相当)轻松地创建自定义模板,并对其功能进行了描述。该组件随附了用于 C#、VB 和 C++ 的强类型集合和强类型字典的模板。如果需要,可以扩展此组件以创建更强大的模板。所有代码示例均用 C# 编写。

该组件有两个版本,一个用于 Visual Studio .NET 2003,另一个用于 Visual Studio .NET 2005。后者已在 Beta 2 上进行测试,但可能也适用于更高版本。

背景

Visual Studio 中有多种模板和向导,其中最值得注意的是项目模板项目项模板。项目模板包含不同类型项目所需的基本文件,而项目项模板是单个文件模板(如果项有代码隐藏文件,则为多个文件),可以包含在项目中。为准确起见,实际上并非所有项目项都需要实际的模板文件,因为它们也可以完全即时创建,但通常为每个项目项设置一个模板文件,然后在包含到项目中时对其进行微调是最容易的。

本文讨论的是项目项模板,项目模板请读者参考其他资料。

当向项目中添加新项目项时,会显示“添加新项”对话框

Add New Item dialog

我们将自定义模板添加到此对话框中。

它是如何工作的?

上面对话框中的项由 Visual Studio 通过读取当前项目类型的项目项目录中的配置文件来生成。例如,如果当前项目是 Visual Basic 项目,则此目录中的项

C:\Program Files\Microsoft Visual Studio .NET 2003\Vb7\VBProjectItems

会被分析(当然,这取决于我的系统,确切位置取决于安装位置)。

项目项目录中有两种重要的文件类型

  • .vsdir 文件 - 列出要显示的项,并提供对话框中显示的所有属性,如名称、描述、图标、默认项文件名等。对于每个项,还会命名其对应的 .vsz 文件。
  • .vsz 文件 - 每个项目项都有一个 .vsz 文件,用于告诉 Visual Studio 如何创建该类型的项。更准确地说,它命名了一个 Visual Studio 可以用来创建该项的 COM 组件,以及该组件应接收的参数(例如,模板文件名)。

这两种文件类型和一个创建实例的 COM 组件就是创建项目项所需的一切。这是所有连接在一起的总体图景

How .vsdir and .vsz files are connected

在介绍我们创建灵活项目项模板的解决方案(如您可能猜到的,它是上述 COM 组件的实现)之前,将更详细地描述这些文件的格式。

请注意,通过将我们的 .vsdir.vsz 文件存储在子目录中,我们可以创建项目项类别。在上图中,我们有例如“本地项目项”、“UI”、“代码”等子目录。

项目项目录

在详细介绍文件格式之前,了解文件的位置可能很有用。一如既往,当然有多种方法。最简单的方法可能是使用资源管理器导航到 Visual Studio 的安装目录,并查找不同 .NET 语言的子目录。例如,VB7 显然包含 Visual Basic 的相关内容。在该目录中,查找名称中包含“projectitem”的目录。很快,我们就会找到包含文件的 VBProjectItems 目录。查找其他项目类型的项目项目录同样容易。

但是,如果我们想以编程方式进行,我们会使用一种更可靠的方法,即从注册表中读取,位于此键下

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\7.1\
                                Projects\{[PROJECT_TYPE_GUID]}

我们感兴趣的值称为 ItemTemplatesDir,它包含正确目录的绝对路径。唯一的复杂之处在于我们必须知道项目类型的 GUID。对于 Visual Basic 和 C#,该 GUID 在 Visual Studio Automation 接口中以这些常量形式提供(需要对 VSLangProj.dll 的引用)

VSLangProj.PrjKind.prjKindCSharpProject
VSLangProj.PrjKind.prjKindVBProject

不幸的是,VSLangProj 没有为所有项目类型定义常量,因此,例如,对于 C++,我们必须硬编码。幸运的是,使用 regedit 找到正确的 GUID 非常容易。导航到上述键,然后在每个项目子键中查找 DefaultProjectExtension 值(对于 C++ 是 vcproj)。找到后,我们就可以确定我们处于正确的项目类型中。使用此方法,我们发现 C++ 项目的 GUID 是 {8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}。坏消息是,我们不能绝对确定 Microsoft 不会在下一个 Visual Studio 版本中更改它,但到时候再说……

这是一段检索 C# 项目项目录的代码

const string sVisualStudioVersion = "7.1";
RegistryKey rk;

// C#
rk = Registry.LocalMachine.OpenSubKey("SOFTWARE")
  .OpenSubKey("Microsoft")
  .OpenSubKey("VisualStudio")
  .OpenSubKey(sVisualStudioVersion)
  .OpenSubKey("Projects")
  .OpenSubKey(VSLangProj.PrjKind.prjKindCSharpProject);
_sCSProjItemPath = (string)rk.GetValue(@"ItemTemplatesDir");

注意:对于 Visual Studio .NET 2005,我们将 sVisualStudioVersion 设置为 "8.0"

.vsdir 文件格式

.vsdir 文件列出了一组项及其属性。同一个目录中可以有多个 .vsdir 文件。

MSDN 中描述了文件格式(使用索引搜索查找“.VSDir files”),但这里是简要总结

  • 文件包含每行一个项。
  • 每行是按以下顺序排列的 | 分隔的属性列表
    1. RelPathName - 项的 .vsz 文件的相对路径。
    2. {clsidPackage} - 可选的组件 GUID,包含本地化资源(例如字符串)。
    3. LocalizedName - 要在“添加新项”对话框中显示的项目的名称。
    4. SortPriority - 决定“添加新项”对话框中项相对顺序的数字。数字较小的项排在数字较大的项之前。
    5. Description - 项目的描述,显示在“添加新项”对话框的状态字段中。
    6. DLLPath{clsidPackage} - 包含要为该项使用的图标资源的 DLL 的路径或 GUID。
    7. IconResourceId - 第 6 列中组件包含的图标的资源 ID。
    8. Flags - 要使用的标志的组合值。
    9. SuggestedBaseName - 要为该项使用的基文件名。请注意,“添加新项”对话框会在该名称的文件扩展名之前插入一个数字。例如,基文件名“Class.cs”将向用户建议为“Class1.cs”,或者如果项目已包含此类项,“Class2.cs”。

    在我看来最有趣的是粗体标记的字段。我通常将所有其他字段设置为 0,除了 SortPriority,设置为 1 是一个合适的数字。如果要添加图标,将图标文件(扩展名为 .ico)复制到与 .vsz 文件相同的目录中,并将其命名与 .vsz 文件相同(当然,除了扩展名),这比创建资源组件更容易。

以下是一个 .vsdir 文件可能样子的示例(为便于阅读,行已换行)

Strongly Typed Collection.vsz|0|Typed Collection|1|
 Sub-class of CollectionBase with elements of a given type|
 0|0|0|TypedCollection.cs
Strongly Typed Dictionary.vsz|0|Typed Dictionary|1|
 Sub-class of DictionaryBase with keys and values of the selected types|
 0|0|0|TypedDictionary.cs 

如果我们将该文件存储在子目录“Krevera Template Manager”中,则以下项将显示在“添加新项”对话框中(图像中给出了一些列号,以显示 .vsdir 文件中的列字符串是如何使用的)

Add Item dialog

当用户选择一个项并单击“打开”按钮时,Visual Studio 会打开该项的 .vsz 文件,因此现在是时候描述它的格式了。

.vsz 文件格式

.vsz 文件的唯一目的是告诉 Visual Studio 调用哪个组件来创建项目项以及它所需的参数。文件格式非常简单,这是一个示例(即 C# 的标准“Class”项目项)

VSWIZARD 7.0
Wizard=VsWizard.VsWizardEngine.7.1
Param="WIZARD_NAME = CSharpAddClassWiz"
Param="WIZARD_UI = FALSE"
Param="PROJECT_TYPE = CSPROJ"

描述

  • 第一行应始终为“VSWIZARD 7.0”,以便 Visual Studio 知道预期的版本。
  • 第二行告诉 Visual Studio,应使用哪个 COM 组件来创建项目项。此处必须给出组件的 ProgID 或 CLSID。
  • 从第三行开始,我们可以根据需要向组件传递一些额外的参数。在上面的示例中,项模板的创建者希望传递三个参数。请注意,引号中的所有内容都将原样传递给组件。Visual Studio 不知道“WIZARD_NAME = CSharpAddClassWiz”的含义,这取决于组件来决定。

关于 .vsz 文件格式,确实没有太多要说的了,所以我们继续描述实际创建项目项的逻辑。

COM 组件

如前所述,实际的项目项是由 .vsz 文件指向的组件创建的。该组件必须满足几个要求

  1. 它必须是 COM 组件。要使用 .NET 组件,我们必须为其注册 COM 互操作。
  2. COM 类必须实现 EnvDTE.IDTWizard 接口(该接口定义在 EnvDTE.dll 库中,顺便说一句,该库还包含 Visual Studio 大部分自动化接口)。该接口只有一个方法,Execute,其原型如下
    void Execute(
       object Application,
       int hwndOwner,
       object[] ContextParams,
       object[] CustomParams,
       ref wizardResult retval
    );

Execute 函数的参数如下

  1. Application - 一个 DTE 对象(Visual Studio 自动化模型的顶层对象),引用正在运行的 Visual Studio 实例。
  2. hwndOwner - 用于显示我们想要显示的对话框的父窗口。
  3. ContextParams - 关于当前项目属性的列表。确切列表取决于向导的调用方式,但对于新项目项,列表如下
    • WizardType - 向导的类型,对于我们关心的项目项,它总是等于 Constants.vsWizardAddItem
    • ProjectName - 当前项目的名称。
    • ProjectItems - 当前项目的 ProjectItem 对象集合。完成后,我们将在该集合中创建一个新项。
    • LocalDirectory - 以ManagerPortal 文件形式存储当前项目的本地目录。
    • ItemName - 新项的请求名称(此名称来自“添加新项”对话框)。
    • InstallationDirectory - Visual Studio 的安装目录。
    • Silent - bool 值,如果为 true,则表示组件不应显示用户界面。
  4. CustomParams - .vsz 文件中提供的额外参数列表。
  5. retval - 一个输出参数,Execute 方法用它来告知 Visual Studio 是否成功创建了新项目项。

就是这样。当要创建项目项时,Visual Studio 会创建一个类的实例并调用其 Execute 方法,在该方法中,该类创建新的项目项并将其插入到 ProjectItems 集合中。创建项目项通常涉及复制现有的模板文件,并根据“添加新项”对话框中指定的名称、当前项目的默认命名空间和其他信息对其进行修改。

Microsoft 以这种方式创建了数量相当多的项目项模板。实际的模板文件不与 .vsdir.vsz 文件存储在一起,而是分开存储。完全可以调整它们,使用 Code Project 文章“Customizing Visual Studio's Code Generation Templates”中描述的方法,但可能进行的修改仅限于布局更改、标准文件头等。无法添加动态内容(当前日期、用户名等),因为用于调整项目模板文件的组件仅限于执行 Visual Studio 开发人员认为可行的操作。另外,据我所知,没有关于标准组件执行的变量替换的文档。另一个问题是,我们无法构建新逻辑,例如向用户询问信息。

因此,为了允许创建更复杂的模板,我们必须创建一个新的 COM IDTWizard 组件,而这正是现在要介绍的。

Krevera 模板管理器

Krevera Template Manager 本质上是一个用于处理创建新项目项请求的 COM 组件。它用 C# 编写,完整的源代码可在本文顶部下载。还有一个二进制下载,其中包含组件的安装程序以及一些用于在 C#、VB 和 C++ 中创建强类型集合和字典的示例模板。

模板是普通文本文件,其中包含变量,Krevera Template Manager 在创建项目项时会替换这些变量。变量以 XML 格式编写,包括以下内容(type 属性此处仅为清晰起见,在这种情况下可以忽略,因为其默认值为“standard”)

<variable type="standard" name= "[variablename]"/>

可以使用 13 种不同的变量

变量名 描述 示例值
fullfilename 新项目项的绝对路径。 C:\Documents and Settings\Emil\My Documents\Visual Studio Projects\Template Manager Test\CS_Test\FoobarCollection.cs
curdate 当前日期。 2005-03-14
directory 创建新项目项所在目录的绝对路径。 C:\Documents and Settings\Emil\My Documents\Visual Studio Projects\Template Manager Test\CS_Test
domainname 计算机连接到的域的名称。 FRODO
domainusername 域限定的用户名。 FRODO\Emil
文件名 新项目项的文件名。 FoobarCollection.cs
filenamebase 不含扩展名的文件名。这通常用作新项的名称。例如,名为 MyClass.cs 的新文件中的类可以假定为名为 MyClass FoobarCollection
fullusername 当前用户的全名。为此,计算机必须连接到域。 Emil Åström
installdir Krevera Template Manager 的安装目录。在 .vsz 文件中引用相对于安装目录的模板文件时很有用。 C:\Program Files\Krevera Software\Krevera Template Manager\
machinename 计算机的名称。 FRODO
命名空间 当前项目的默认命名空间。 CS_Test
projectname 当前项目的名称。 CS_Test
用户名 当前用户的用户名,不包括域名称。 Emil

我们还可以通过将 type 属性设置为“environment”并在 name 属性中给出环境变量的名称来访问环境变量

<variable type="environment" name="TEMP"/>

最后,我们还可以使用自定义变量,这些变量的值通过在类似向导的界面中向用户询问来检索。以下是一个示例

<wizard>
  <page>
    <title>Select data type</title>
    <description>Select data type for which to create 
            a strongly typed collection</description>
    <variable name="datatype"/>
  </page>    
</wizard>

创建包含上述向导片段的项目项时,会显示以下对话框

Krevera Template manager Wizard example

当用户输入自定义变量(在示例中名为“datatype”)的期望值后,我们可以在模板中使用它,方法是使用 type 属性设置为“custom”的变量

<variable type="custom" name="datatype"/>

为确保模板的变量声明不会与模板文本中的代码语法冲突,所有变量都必须用 [%%] 包围。

总而言之,现在我们展示一个真实模板的顶部部分,即随代码分发的创建强类型集合的模板

[%
<wizard>
  <page>
    <title>Select data type</title>
    <description>Select data type for which to create a 
       strongly typed collection</description>
    <variable name="datatype"/>
  </page>    
</wizard>
%]using System;
using System.Collections;

namespace [%<variable name="namespace"/>%] {
    public class [%<variable type="standard"
       name="filenamebase"/>%] : CollectionBase
    {
         public [%<variable type="custom"
               name="datatype"/>%] this[ int index ] {
            get
            {
                return( ([%<variable type="custom"
                    name="datatype"/>%]) List[index] );
            }
            set
            {
                List[index] = value;
            }
        }
        ...

如果我们将此类型的项目项插入到默认命名空间为 CS_Test 的项目中,将项命名为 FoobarCollection.cs,并回答向导问题 Foobar,我们将得到包含以下内容的如下文件

using System;
using System.Collections;

namespace CS_Test
{
    public class FoobarCollection : CollectionBase
    {
        public Foobar this[ int index ]
        {
            get
            {
                return( (Foobar) List[index] );
            }
            set
            {
                List[index] = value;
            }
        }
        ...

希望大家都同意,与手动创建相比,这是一种非常简单的创建强类型集合的方法。

模板的其他常见用途是强制执行有关文件头的组织范围规则。使用此组件,可以轻松地自动将文件名、创建日期、用户名等包含在文件头中。如果需要,创建新变量也很容易,只需修改代码中的 Wizard.EvaluateVariable 函数即可实现新变量的评估。

实现概述

此处不详细描述代码;直接阅读代码及其注释可能更容易。但是,有一个代码工作原理的概述可能会有所帮助,所以我们开始吧

  • 该组件是用 C# 编写的 .NET 组件,启用了 COM 互操作(项目属性“Register for COM Interop”设置为“true”)。为了使其正常工作,我们必须为该组件关联一个强名称(一个 .snk 文件),该文件可以从命令行生成
    sn -k TemplateManager.snk

    密钥文件还必须在 AssemblyInfo.cs 中引用(有关详细信息,请参阅该文件)。

    更新:这不适用于 Visual Studio .NET 2005 版本。我们仍然需要设置Register for COM Interop,但不必担心强名称等。

  • 我们的 .vsz 文件必须将 Krevera.TemplateManager.Wizard 指定为要使用的组件,以及指定要使用的模板文件的强制参数 TEMPLATE_FILE。这是一个示例(为便于阅读,已换行)
    VSWIZARD 7.0
    Wizard=Krevera.TemplateManager.Wizard
    Param="TEMPLATE_FILE = [%<variable name="installdir"/>%]
           Templates\CSharp\Project Items\StronglyTypedCollection.cs"

    请注意,如果需要,我们可以在参数中使用变量。

  • 使用以下代码将模板文件复制到项目中(以及 ProjectItems 集合)
    ProjectItem newfile = _projitems.AddFromTemplate(
      _sTemplateFile, _sNewItemName);
  • 包含的文件将被打开,并且所有 [% ... %] 块的内容都将使用 XML 架构进行验证。然后,如果块是变量,它将被替换为变量的值。如果块是向导片段,则会显示该块中的所有页面,用户提供的值将存储在具有自定义变量值的字典中。
  • 源代码中还包含一个安装程序项目。该项目基本简单,除了它使用所谓的自定义操作将实际的 .vsdir.vsz 文件复制到 Visual Studio 子目录中的正确项目项位置。执行复制操作的函数位于组件项目中,名为 TemplateManagerInstaller.OnAfterInstall。如果项目项不应放置在“Krevera Template Manager”子目录中(这是默认设置),则应修改此处。

安装

运行安装程序(如果在安装期间 Visual Studio 打开,则重启 Visual Studio)或手动执行

  1. 构建组件项目。这应该也会注册 COM 互操作组件。
  2. 确保存在注册表项 HKEY_LOCAL_MACHINE\SOFTWARE\Krevera Software\Template Manager(对于 Visual Studio .NET 2005 版本,附加“ VS2005”),其中有一个名为 InstallDir 的值,该值给出 Krevera Template Manager 的安装目录位置(用于查找模板文件)。值的示例是 C:\Program Files\Krevera Software\Krevera Template Manager\
  3. 将 .vsdir.vsz 文件复制到 Visual Studio 的项目项目录中。

这些步骤足以使用模板。

许可证

您可以使用此软件的任何方式,但不能用于营利。您可以自由地私下或在组织中使用它(原始或修改状态),但不得将其包含在商业产品中等。至少在获得作者(即我!)的明确批准之前,不得这样做!

参考文献

图书

  • Inside Microsoft Visual Studio .NET,作者 Brian Johnson、Craig Skibo 和 Marc Young。

    这是一本内容广泛的书,但不幸的是,内容看起来不太有条理,而且存在一些错误。尽管如此,它并没有太多竞争对手,所以对于有兴趣为 Visual Studio 创建宏和外接程序的读者来说,它仍然值得推荐。

其他 Code Project 文章

历史

  • 2005-07-24
    • 错误修复:在项目中创建的子文件夹中的文件未正确打开。
    • 对文章文本进行了一些小的更改,最值得注意的是修正了 LocalDirectory 的描述,之前错误地声称该值为新项的路径,而实际上它是项目文件夹的路径(如果项在子文件夹中创建,则不相同)。
    • 添加了适用于 Visual Studio .NET 2005 Beta 2(可能还有更高版本)的模板管理器版本。感谢 Marcin Celej 指出所需修改的简单性。

      注释

      • 编译时有 3 个警告,但运行正常。
      • 示例模板与以前相同(强类型集合和字典),尽管现在有了泛型,它们的作用要小得多。它们仍然能说明模板机制,所以我保持原样。
  • 2005-04-24

    修复了一些错误(布局和语法)。

  • 2005-03-15

    首次发布版本。

© . All rights reserved.