创建基本项目系统,第 1 部分

在 Visual Studio 中,项目是开发人员用来组织源代码文件和其他资产的容器。 项目在解决方案资源管理器显示为解决方案的子级。 项目允许你组织、生成、调试和部署源代码,并创建对 Web 服务、数据库和其他资源的引用。

项目在项目文件中定义,例如 Visual C# 项目的 .csproj 文件。 可以创建自己的项目类型,该类型具有自己的项目文件扩展名。 有关项目类型的详细信息,请参阅 “项目类型”。

注意

如果需要使用自定义项目类型扩展 Visual Studio,强烈建议利用 Visual Studio 项目系统(VSPS),该系统在从头开始生成项目系统 方面具有许多优势:

  • 更易于载入。 即使是基本项目系统也需要数万行代码。 利用 VSPS 可以降低载入成本,只需单击几下鼠标,即可根据需要对其进行自定义。

  • 更易于维护。 利用 VSPS,只需维护自己的方案。 我们处理所有项目系统基础结构的维护。

    如果需要面向 Visual Studio 2013 之前的 Visual Studio 版本,将无法在 Visual Studio 扩展中使用 VSPS。 如果是这种情况,本演练是入门的好位置。

本演练演示如何创建具有项目文件扩展名 .myproj 的项目类型。 本演练从现有的 Visual C# 项目系统借用。

注意

有关扩展项目的更多示例,请参阅 VSSDK 示例

本演练介绍如何完成以下任务:

  • 创建基本项目类型。

  • 创建基本项目模板。

  • 向 Visual Studio 注册项目模板。

  • 打开 “新建项目 ”对话框,然后使用模板创建项目实例。

  • 为项目系统创建项目工厂。

  • 为项目系统创建项目节点。

  • 为项目系统添加自定义图标。

  • 实现基本模板参数替换。

先决条件

下载项目的托管包框架的源代码。 将文件解压缩到要创建的解决方案可访问的位置。

创建基本项目类型

创建名为 SimpleProjectC# VSIX 项目。 (文件>新建>项目,然后 Visual C#>Extensibility>VSIX Project)。 添加 Visual Studio 包项目项模板(在解决方案资源管理器上,右键单击项目节点并选择“添加新>”,然后转到“扩展性>Visual Studio 包”。 将文件 命名为 SimpleProjectPackage

创建基本项目模板

现在,可以修改此基本 VSPackage 来实现新的 .myproj 项目类型。 若要创建基于 .myproj 项目类型的项目,Visual Studio 必须知道要添加到新项目的文件、资源和引用。 若要提供此信息,请将项目文件放在项目模板文件夹中。 当用户使用 .myproj 项目创建项目时,文件将复制到新项目。

创建基本项目模板

  1. 向项目添加三个文件夹,另 一个文件夹:Templates\Projects\SimpleProject。 (In 解决方案资源管理器,右键单击 SimpleProject 项目节点,指向“添加”,然后单击“新建文件夹”。将文件夹命名为“模板”。在“模板”文件夹中,添加名为 Projects 的文件夹。在 Projects 文件夹中,添加名为 SimpleProject 的文件夹。

  2. Templates\Projects\SimpleProject 文件夹中,添加位图图像文件以用作名为 SimpleProject.ico 的图标。 单击“添加时,图标编辑器将打开。

  3. 使图标与众不同。 本图标将在演练后面的“新建项目”对话框中显示

    Simple Project Icon

  4. 保存图标并关闭图标编辑器。

  5. Templates\Projects\SimpleProject 文件夹中,添加名为 Program.cs项。

  6. 将现有代码替换为以下行。

    using System;
    using System.Collections.Generic;
    using System.Text;
    
    namespace $nameSpace$
    {
        public class $className$
        {
            static void Main(string[] args)
            {
                Console.WriteLine("Hello VSX!!!");
                Console.ReadKey();
            }
        }
    }
    

    重要

    这不是 Program.cs 代码的最终形式;将在后面的步骤中处理替换参数。 你可能会看到编译错误,但只要文件的 BuildActionContent,你应该能够像往常一样生成和运行项目。

  7. 保存文件。

  8. AssemblyInfo.cs 文件从 Properties 文件夹复制到 Projects\SimpleProject 文件夹。

  9. Projects\SimpleProject 文件夹中,添加名为 SimpleProject.myproj 的 XML 文件。

    注意

    此类型的所有项目的文件扩展名为 .myproj。 如果要更改它,则必须在演练中提及到处更改它。

  10. 将现有内容替换为以下行。

    <?xml version="1.0" encoding="utf-8" ?>
    <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
      <PropertyGroup>
        <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
        <SchemaVersion>2.0</SchemaVersion>
        <ProjectGuid></ProjectGuid>
        <OutputType>Exe</OutputType>
        <RootNamespace>MyRootNamespace</RootNamespace>
        <AssemblyName>MyAssemblyName</AssemblyName>
        <EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
      </PropertyGroup>
      <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
        <DebugSymbols>true</DebugSymbols>
        <OutputPath>bin\Debug\</OutputPath>
      </PropertyGroup>
      <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
        <DebugSymbols>false</DebugSymbols>
        <OutputPath>bin\Release\</OutputPath>
      </PropertyGroup>
      <ItemGroup>
        <Reference Include="mscorlib" />
        <Reference Include="System" />
        <Reference Include="System.Data" />
        <Reference Include="System.Xml" />
      </ItemGroup>
      <ItemGroup>
        <Compile Include="AssemblyInfo.cs">
          <SubType>Code</SubType>
        </Compile>
        <Compile Include="Program.cs">
          <SubType>Code</SubType>
        </Compile>
      </ItemGroup>
      <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
    </Project>
    
  11. 保存文件。

  12. “属性”窗口中,将 AssemblyInfo.cs、Program.csSimpleProject.icoSimpleProject.myproj 设置为 Content,并将其在 VSIX 属性中的 Include 设置为 True。

    此项目模板介绍一个基本 Visual C# 项目,该项目同时具有调试配置和发布配置。 该项目包括两个源文件: AssemblyInfo.csProgram.cs 以及多个程序集引用。 从模板创建项目时,ProjectGuid 值将自动替换为新的 GUID。

    解决方案资源管理器中,展开的 Templates 文件夹应如下所示:

Templates
   Projects
      SimpleProject
         AssemblyInfo.cs
         Program.cs
         SimpleProject.ico
         SimpleProject.myproj

创建基本项目工厂

必须告知 Visual Studio 项目模板文件夹的位置。 为此,请将属性添加到实现项目工厂的 VSPackage 类,以便在生成 VSPackage 时将模板位置写入系统注册表。 首先创建由项目工厂 GUID 标识的基本项目工厂。 使用 ProvideProjectFactoryAttribute 特性将项目工厂连接到 SimpleProjectPackage 类。

创建基本项目工厂

  1. 为项目工厂创建 GUID(在 “工具 ”菜单上,单击“创建 GUID” ),或使用以下示例中的 GUID。 将 GUID 添加到 SimpleProjectPackage 已定义的 PackageGuidString节附近的类。 GUID 必须同时采用 GUID 窗体和字符串形式。 生成的代码应类似于以下示例。

        public sealed class SimpleProjectPackage : Package
        {
            ...
            public const string SimpleProjectPkgString = "96bf4c26-d94e-43bf-a56a-f8500b52bfad";
            public const string SimpleProjectFactoryString = "471EC4BB-E47E-4229-A789-D1F5F83B52D4";
    
            public static readonly Guid guidSimpleProjectFactory = new Guid(SimpleProjectFactoryString);
        }
    
  2. 将类添加到名为 SimpleProjectFactory.cs 的顶部 SimpleProject 文件夹。

  3. 添加以下 using 指令:

    using System.Runtime.InteropServices;
    using Microsoft.VisualStudio.Shell;
    
  4. 向类添加 GUID 属性 SimpleProjectFactory 。 属性的值是新的项目工厂 GUID。

    [Guid(SimpleProjectPackage.SimpleProjectFactoryString)]
    class SimpleProjectFactory
    {
    }
    

    现在可以注册项目模板。

注册项目模板

  1. SimpleProjectPackage.cs 中,将属性添加到ProvideProjectFactoryAttributeSimpleProjectPackage类,如下所示。

    [ProvideProjectFactory(    typeof(SimpleProjectFactory),     "Simple Project",
        "Simple Project Files (*.myproj);*.myproj", "myproj", "myproj",
        @"Templates\Projects\SimpleProject",     LanguageVsTemplate = "SimpleProject")]
    [Guid(SimpleProjectPackage.PackageGuidString)]
    public sealed class SimpleProjectPackage : Package
    
  2. 重新生成解决方案并验证它是否生成时没有错误。

    重新生成注册项目模板。

    参数 defaultProjectExtensionpossibleProjectExtensions 设置为项目文件扩展名 (.myproj)。 参数projectTemplatesDirectory设置为 Templates 文件夹的相对路径。 在生成过程中,此路径将转换为完整生成,并添加到注册表以注册项目系统。

测试模板注册

模板注册告知 Visual Studio 项目模板文件夹的位置,以便 Visual Studio 可以在“新建项目”对话框中显示模板名称和图标

测试模板注册

  1. F5 开始调试 Visual Studio 的实验实例。

  2. 在实验实例中,创建新创建项目类型的新项目。 在“新建项目”对话框中,应在“已安装”模板看到 SimpleProject

    现在,你有一个已注册的项目工厂。 但是,它尚无法创建项目。 项目包和项目工厂协同工作来创建和初始化项目。

添加托管包框架代码

实现项目包与项目工厂之间的连接。

  • 导入托管包框架的源代码文件。

    1. 卸载 SimpleProject 项目(在 解决方案资源管理器 中,选择项目节点,然后在上下文菜单上单击“卸载项目”。)并在 XML 编辑器中打开项目文件。

    2. 将以下块添加到项目文件(就在导入>块上方<)。 设置为 ProjectBasePath 刚下载的 托管包框架代码中的 ProjectBase.files 文件的位置。 可能需要向 pathname 添加反斜杠。 如果没有,项目可能无法找到托管包框架源代码。

      <PropertyGroup>
           <ProjectBasePath>your path here\</ProjectBasePath>
           <RegisterWithCodebase>true</RegisterWithCodebase>
        </PropertyGroup>
        <Import Project="$(ProjectBasePath)\ProjectBase.Files" />
      

      重要

      不要忘记路径末尾的反斜杠。

    3. 重新加载 项目。

    4. 添加对下列程序集的引用:

      • Microsoft.VisualStudio.Designer.Interfaces (in <VSSDK install>\VisualStudioIntegration\Common\Assemblies\v2.0

      • WindowsBase

      • Microsoft.Build.Tasks.v4.0

初始化项目工厂

  1. SimpleProjectPackage.cs 文件中,添加以下 using 指令。

    using Microsoft.VisualStudio.Project;
    
  2. SimpleProjectPackageMicrosoft.VisualStudio.Package.ProjectPackage中派生类。

    public sealed class SimpleProjectPackage : ProjectPackage
    
  3. 注册项目工厂。 将以下行添加到 SimpleProjectPackage.Initialize 方法,紧接着 base.Initialize

    base.Initialize();
    this.RegisterProjectFactory(new SimpleProjectFactory(this));
    
  4. 实现抽象属性 ProductUserContext

    public override string ProductUserContext
        {
            get { return ""; }
    }
    
  5. SimpleProjectFactory.cs 中,在现有using指令后面添加以下using指令。

    using Microsoft.VisualStudio.Project;
    
  6. SimpleProjectFactoryProjectFactory中派生类。

    class SimpleProjectFactory : ProjectFactory
    
  7. 将以下虚拟方法添加到 SimpleProjectFactory 类。 将在后面的部分中实现此方法。

    protected override ProjectNode CreateProject()
    {
        return null;
    }
    
  8. 将以下字段和构造函数添加到 SimpleProjectFactory 类。 此 SimpleProjectPackage 引用缓存在专用字段中,以便可用于设置服务提供商站点。

    private SimpleProjectPackage package;
    
    public SimpleProjectFactory(SimpleProjectPackage package)
        : base(package)
    {
        this.package = package;
    }
    
  9. 重新生成解决方案并验证它是否生成时没有错误。

测试项目工厂实现

测试是否调用项目工厂实现的构造函数。

测试项目工厂实现

  1. SimpleProjectFactory.cs 文件中,在 SimpleProjectFactory 构造函数的以下行上设置断点。

    this.package = package;
    
  2. F5 启动 Visual Studio 的实验实例。

  3. 在实验实例中,开始创建新项目。 在 “新建项目 ”对话框中,选择 SimpleProject 项目类型,然后单击“ 确定”。 执行在断点处停止。

  4. 清除断点并停止调试。 由于尚未创建项目节点,因此项目创建代码仍会引发异常。

扩展 ProjectNode 类

现在,你可以实现 SimpleProjectNode 派生自该类的 ProjectNode 类。 基 ProjectNode 类处理以下项目创建任务:

  • 将项目模板文件 SimpleProject.myproj 复制到新的项目文件夹。 副本根据“新建项目”对话框中输入的名称重命名。 属性值 ProjectGuid 由新的 GUID 替换。

  • 遍历项目模板文件 SimpleProject.myproj 的 MSBuild 元素,并查找 Compile 元素。 对于每个 Compile 目标文件,将该文件复制到新项目文件夹。

    派生 SimpleProjectNode 类处理这些任务:

  • 为要创建或选择解决方案资源管理器中的项目和文件节点启用图标。

  • 允许指定其他项目模板参数替换。

扩展 ProjectNode 类

  1. 添加名为的 SimpleProjectNode.cs 的类。

  2. 用下面的代码替换现有代码。

    using System;
    using System.Collections.Generic;
    using Microsoft.VisualStudio.Project;
    
    namespace SimpleProject
    {
        public class SimpleProjectNode : ProjectNode
        {
            private SimpleProjectPackage package;
    
            public SimpleProjectNode(SimpleProjectPackage package)
            {
                this.package = package;
            }
            public override Guid ProjectGuid
            {
                get { return SimpleProjectPackage.guidSimpleProjectFactory; }
            }
            public override string ProjectType
            {
                get { return "SimpleProjectType"; }
            }
    
            public override void AddFileFromTemplate(
                string source, string target)
            {
                this.FileTemplateProcessor.UntokenFile(source, target);
                this.FileTemplateProcessor.Reset();
            }
        }
    }
    

    此类 SimpleProjectNode 实现具有以下重写的方法:

  • ProjectGuid,返回项目工厂 GUID。

  • ProjectType,返回项目类型的本地化名称。

  • AddFileFromTemplate,它将所选文件从模板文件夹复制到目标项目。 此方法将在后面的部分中进一步实现。

    构造 SimpleProjectNode 函数(如 SimpleProjectFactory 构造函数)在专用字段中缓存 SimpleProjectPackage 引用以供以后使用。

    若要将SimpleProjectFactory类连接到SimpleProjectNode该类,必须在方法中SimpleProjectFactory.CreateProject实例化一个新SimpleProjectNode项,并将其缓存在专用字段中供以后使用。

连接项目工厂类和节点类

  1. SimpleProjectFactory.cs 文件中,添加以下 using 指令:

    using IOleServiceProvider =    Microsoft.VisualStudio.OLE.Interop.IServiceProvider;
    
  2. SimpleProjectFactory.CreateProject使用以下代码替换该方法。

    protected override ProjectNode CreateProject()
    {
        SimpleProjectNode project = new SimpleProjectNode(this.package);
    
        project.SetSite((IOleServiceProvider)        ((IServiceProvider)this.package).GetService(            typeof(IOleServiceProvider)));
        return project;
    }
    
  3. 重新生成解决方案并验证它是否生成时没有错误。

测试 ProjectNode 类

测试项目工厂,以查看它是否创建项目层次结构。

测试 ProjectNode 类

  1. F5 启动调试。 在实验实例中,创建新的 SimpleProject。

  2. Visual Studio 应调用项目工厂来创建项目。

  3. 关闭 Visual Studio 的实验实例。

添加自定义项目节点图标

上一部分的项目节点图标是默认图标。 你可以将其更改为自定义图标。

添加自定义项目节点图标

  1. Resources 文件夹中,添加名为 SimpleProjectNode.bmp 的位图文件。

  2. “属性” 窗口中,将位图减少到 16 x 16 像素。 使位图与众不同。

    Simple Project Comm

  3. “属性” 窗口中,将 位图的“生成”操作 更改为 “嵌入资源”。

  4. SimpleProjectNode.cs 中,添加以下 using 指令:

    using System.Drawing;
    using System.Windows.Forms;
    
  5. 将以下静态字段和构造函数添加到 SimpleProjectNode 类。

    private static ImageList imageList;
    
    static SimpleProjectNode()
    {
        imageList =        Utilities.GetImageList(            typeof(SimpleProjectNode).Assembly.GetManifestResourceStream(                "SimpleProject.Resources.SimpleProjectNode.bmp"));
    }
    
  6. 将以下属性添加到类的 SimpleProjectNode 开头。

    internal static int imageIndex;
       public override int ImageIndex
       {
           get { return imageIndex; }
       }
    
  7. 将实例构造函数替换为以下代码。

    public SimpleProjectNode(SimpleProjectPackage package)
    {
        this.package = package;
    
        imageIndex = this.ImageHandler.ImageList.Images.Count;
    
        foreach (Image img in imageList.Images)
        {
            this.ImageHandler.AddImage(img);
        }
    }
    

    在静态构造过程中, SimpleProjectNode 从程序集清单资源检索项目节点位图,并将其缓存在专用字段中供以后使用。 请注意图像路径的 GetManifestResourceStream 语法。 若要查看程序集中嵌入的清单资源的名称,请使用 GetManifestResourceNames 该方法。 当此方法应用于程序集时 SimpleProject ,结果应如下所示:

  • SimpleProject.Resources.resources

  • VisualStudio.Project.resources

  • SimpleProject.VSPackage.resources

  • Resources.imagelis.bmp

  • Microsoft.VisualStudio.Project.DontShowAgainDialog.resources

  • Microsoft.VisualStudio.Project.SecurityWarningDialog.resources

  • SimpleProject.Resources.SimpleProjectNode.bmp

    在实例构造期间, ProjectNode 基类加载 Resources.imagelis.bmp,其中嵌入的通常来自 Resources\imagelis.bmp 的 16 x 16 位图。 此位图列表可用作 SimpleProjectNode ImageHandler.ImageList. SimpleProjectNode 将项目节点位图追加到列表中。 图像列表中的项目节点位图的偏移量将缓存,供以后用作公共 ImageIndex 属性的值。 Visual Studio 使用此属性来确定要显示为项目节点图标的位图。

测试自定义项目节点图标

测试项目工厂,以查看它是否创建具有自定义项目节点图标的项目层次结构。

测试自定义项目节点图标

  1. 开始调试,并在实验实例中创建新的 SimpleProject。

  2. 在新创建的项目中,请注意 SimpleProjectNode.bmp 用作项目节点图标。

    Simple Project New Project Node

  3. 在代码编辑器中打开“Program.cs”。 应会看到类似于以下代码的源代码。

    using System;
    using System.Collections.Generic;
    using System.Text;
    
    namespace $nameSpace$
    {
        public class $className$
        {
            static void Main(string[] args)
            {
                Console.WriteLine("Hello VSX!!!");
                Console.ReadKey();
            }
        }
    }
    

    请注意,$nameSpace$ 和 $className$ 的模板参数没有新值。 你将了解如何在下一部分中实现模板参数替换。

替换模板参数

在前面的部分中,你使用 ProvideProjectFactory 属性向 Visual Studio 注册了项目模板。 通过以这种方式注册模板文件夹的路径,可以通过重写和扩展 ProjectNode.AddFileFromTemplate 类来启用基本模板参数替换。 有关详细信息,请参阅 “新建项目生成:在引擎盖下”,第二部分。

现在,将 AddFileFromTemplate 替换代码添加到类。

替换模板参数

  1. SimpleProjectNode.cs 文件中,添加以下 using 指令。

    using System.IO;
    
  2. AddFileFromTemplate使用以下代码替换该方法。

    public override void AddFileFromTemplate(
        string source, string target)
    {
        string nameSpace =
            this.FileTemplateProcessor.GetFileNamespace(target, this);
        string className = Path.GetFileNameWithoutExtension(target);
    
        this.FileTemplateProcessor.AddReplace("$nameSpace$", nameSpace);
        this.FileTemplateProcessor.AddReplace("$className$", className);
    
        this.FileTemplateProcessor.UntokenFile(source, target);
        this.FileTemplateProcessor.Reset();
    }
    
  3. 在方法中设置断点,就在赋值语句之后 className

    赋值语句确定命名空间和新类名的合理值。 这两 ProjectNode.FileTemplateProcessor.AddReplace 个方法调用使用这些新值替换相应的模板参数值。

测试模板参数替换

现在可以测试模板参数替换。

测试模板参数替换

  1. 开始调试,并在实验实例中创建新的 SimpleProject。

  2. 执行在方法中的 AddFileFromTemplate 断点处停止。

  3. 检查参数的值nameSpaceclassName

    • nameSpace给定 \Templates\Projects\SimpleProject\SimpleProject\SimpleProject.myproj 项目模板文件中 RootNamespace> 元素的值<。 在本例中,该值为 MyRootNamespace

    • className 给定类源文件名称的值,而不指定文件扩展名。 在这种情况下,要复制到目标文件夹的第一个文件是 AssemblyInfo.cs;因此,className 的值为 AssemblyInfo.

  4. 删除断点并按 F5 继续执行。

    Visual Studio 应完成项目创建。

  5. 在代码编辑器中打开“Program.cs”。 应会看到类似于以下代码的源代码。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace MyRootNamespace
    {
        public class Program
        {
            static void Main(string[] args)
            {
                Console.WriteLine("Hello VSX!!!");
                Console.ReadKey();
            }
        }
    }
    

    请注意,命名空间现在 MyRootNamespace 和类名现在 Program

  6. 开始调试项目。 新项目应在控制台窗口中编译、运行和显示“Hello VSX!!”。

    Simple Project Command

    祝贺你! 你已实现基本的托管项目系统。