15.2 扩展模板

根据现有的项或项目构建模板限制了开发人员的操作,因为它假定每个项目或场景都需要相同的项。通过少量的用户交互操作,可以将一个模板应用于多种场景,而不是为每一个不同的场景创建一个对应的模板(例如,一个场景的主窗体为黑色背景,而另一个场景的主窗体为白色背景)。因此,本节修改前面创建的项目模板,使它可以为主窗体指定背景色。

为了给模板添加用户交互操作,需要在类库中实现IWizard接口,之后在使用模板的计算机上给该类库签名,并放在全局程序集缓存(Global Assembly Cache,GAC)中。因此,要部署使用向导的模板,还需要拥有把向导程序集部署到GAC中的权限。

15.2.1 模板项目的安装

在插入和实现IWizard接口之前,执行下面的步骤,以安装解决方案。

(1)使用本章前面创建的项目模板Project Template Example创建一个新项目,命名为Extended- ProjectTemplateExample。确保这个解决方案成功生成和运行。该解决方案出现的任何问题在以后都很难检测出来,因为使用模板时出现的错误消息很难理解。

(2)在这个解决方案中添加一个Class Library项目WizardClassLibrary,在其中放置IWizard实现代码。

(3)在WizardClassLibrary中添加一个新的空类文件MyWizard和一个名为ColorPickerForm的空白Windows Form,这些文件将在以后定制。

(4)要访问IWizard接口,需要在Classic Library项目中添加对EnvDTE.dll和Microsoft. VisualStudio.TemplateWizardInterface.dll的引用。EnvDTE.dll位于%programfiles%\Common Files\Microsoft Shared\MSEnv\PublicAssemblies中,而Microsoft.VisualStudio.TemplateWizardInterface.dll位于%programfiles%\ Microsoft Visual Studio 14.0\Common7\IDE\PublicAssemblies\中。

15.2.2 IWizard

WizardClassLibrary的目的、使用IWizard接口的实际原因是给模板创建过程添加编程关联。在项目中,有一个窗体(ColorPickerForm)和一个类(MyWizard)。前者是一个简单的窗体,用于指定主窗体的背景色。对于这个窗体,需要添加一个Color Dialog控件ColorDialog1、一个Panel控件ColorPanel、一个文本为Pick Color的Button控件PickColorButton,以及一个文本为Accept Color的Button控件AcceptColorButton。完成后,ColorPickerForm应该如图15-9所示。

图15-9

在该窗体上添加如下代码。这个窗体的主要逻辑在Pick Color按钮的事件处理程序中,该按钮会打开用于选择颜色的ColorDialog。

VB

      Public Class ColorPickerForm
          Public ReadOnly Property SelectedColor() As Drawing.Color
              Get
                  Return ColorPanel.BackColor
              End Get
          End Property
          Private Sub PickColorButton_Click(ByVal sender As System.Object, _
                                  ByVal e As System.EventArgs) Handles_
                                  PickColorButton.Click
              ColorDialog1.Color = ColorPanel.BackColor
              If ColorDialog1.ShowDialog() = Windows.Forms.DialogResult.OK Then
                  ColorPanel.BackColor =ColorDialog1.Color
              End If
          End Sub
          Private Sub AcceptColorButton_Click(ByVal sender As System.Object, _
                                ByVal e As System.EventArgs) Handles _
                                AcceptColorButton.Click
              Me.DialogResult = Windows.Forms.DialogResult.OK
              Me.Close()
          End Sub
      End Class

C#

      using System;
      using System.Drawing;
      using System.Windows.Forms;
      namespace WizardClassLibrary
      {
          public partial class ColorPickerForm : Form
          {
              public ColorPickerForm()
              {
                  InitializeComponent();
                  PickColorButton.Click += PickColorButton_Click;
                  AcceptColorButton.Click += AcceptColorButton_Click;
              }
              public Color SelectedColor
              {
                  get {return ColorPanel.BackColor; }
              }
              private void PickColorButton_Click(object sender, EventArgs e)
              {
                  ColorDialog1.Color =ColorPanel.BackColor;
                  if (ColorDialog1.ShowDialog() == DialogResult.OK)
                  {
                      ColorPanel.BackColor = ColorDialog1.Color;
                  }
              }
              private void AcceptColorButton_Click(object sender, EventArgs e)
              {
                  this.DialogResult =DialogResult.OK;
                  this.Close();
              }
          }
      }

MyWizard类实现了IWizard接口,用户可以通过该接口对模板进行大量的交互操作。现在,为RunStarted方法添加代码,项目创建进程在启动后会调用这个方法。然后用户就可以为主窗体选择并应用新的背景色:

VB

      Imports Microsoft.VisualStudio.TemplateWizard
      Imports System.Windows.Forms
      Public Class MyWizard
          Implements IWizard
          Public Sub BeforeOpeningFile(ByVal projectItem As EnvDTE.ProjectItem) _
                                                     Implements IWizard.BeforeOpeningFile
          End Sub
          Public Sub ProjectFinishedGenerating(ByVal project As EnvDTE.Project) _
                                             Implements IWizard.ProjectFinishedGenerating
          End Sub
          Public Sub ProjectItemFinishedGenerating _
                                         (ByVal projectItem As EnvDTE.ProjectItem) _
                                         Implements IWizard.ProjectItemFinishedGenerating
          End Sub
          Public Sub RunFinished() Implements IWizard.RunFinished
          End Sub
          Public Sub RunStarted(ByVal automationObject As Object, _
                                ByVal replacementsDictionary As  _
          Dictionary(Of String, String), _
                                ByVal runKind As WizardRunKind, _
                                ByVal customParams() As Object) _
          Implements IWizard.RunStarted
              Dim selector As New ColorPickerForm
              If selector.ShowDialog = DialogResult.OK Then
                  Dim c As Drawing.Color = selector.SelectedColor
                  Dim colorString As String = "System.Drawing.Color.FromArgb(" & _
          c.R.ToString & "," & _
          c.G.ToString & "," & _
          c.B.ToString & ")"
                  replacementsDictionary.Add _
                                        ("Background=""Silver""", _
                                         "Background=""" & colorString & """")
              End If
          End Sub
          Public Function ShouldAddProjectItem(ByVal filePath As String) As Boolean _
                                                  Implements IWizard.ShouldAddProjectItem
              Return True
          End Function
      End Class

C#

      using System;
      using System.Drawing;
      using System.Windows.Forms;
      using Microsoft.VisualStudio.TemplateWizard;
      namespace WizardClassLibrary
      {
          public class MyWizard : IWizard
          {
              public void BeforeOpeningFile(EnvDTE.ProjectItem projectItem)
              {
              }
              public void ProjectFinishedGenerating(EnvDTE.Project project)
              {
              }
              public void ProjectItemFinishedGenerating(EnvDTE.ProjectItem projectItem)
              {
              }
              public void RunFinished()
              {
              }
              public void RunStarted(object automationObject, Dictionary<string, string>
                  replacementsDictionary, WizardRunKind runKind, object[] customParams)
              {
                  ColorPickerForm selector = new ColorPickerForm();
                  if (selector.ShowDialog() == DialogResult.OK)
                  {
                      Color c = selector.SelectedColor;
                      string colorString = "Color.FromArgb(" +
                          c.R.ToString() + "," +
                          c.G.ToString() + "," +
                          c.B.ToString() + ")";
                      replacementsDictionary.Add
                                        ("Background=""Silver""",
                                         "Background=""" + colorString + """");
                  }
              }
              public bool ShouldAddProjectItem(string filePath)
              {
                  return true;
              }
          }
      }

在RunStarted方法中,提示用户可以选择一个新的颜色,然后使用产生的响应在替换词典中插入一个新的条目。在本例中,将使用由用户指定颜色的RGB值组成的连接字符串替换'Background= "silver"'。如果创建的文件需要应用于新的项目,则需要使用替换词典,因为系统可以在替换词典中搜索替换键。找到这些替换键的实例后,就用对应的替换值替代它们。在本例中搜索的是将BackColor指定为Silver值的行,然后把它替换为用户指定的新颜色。

包含IWizard接口的实现代码的类库必须包含一个能放在GAC中的强名称程序集。为此,应使用Project Properties对话框的Signing选项卡生成一个新的签名密钥,如图15-10所示。

图15-10

选择了Sign the assembly复选框后,密钥文件将没有默认值。为创建新密钥,从下拉列表中选择<New...>项。或者通过下拉列表中的<Browse...>项使用已有的密钥文件。

15.2.3 生成扩展项目模板

这个例子的模板基于ExtendedProjectTemplateExample项目,只需要进行很少的修改,刚才构建的向导就可以正确工作。前面在替换词典中添加了一项,并搜索将Background设置为Silver的实例。如果希望在使用向导时给MainWindow指定Background,就需要确保找到了替换值。为此,只需要把MainWindow的Background属性设置为Silver。这会在MainWindow.xaml文件中给Grid元素添加特性"Background = Silver",这样在替换阶段就可以找到它。

现在需要给向导关联项目模板,以便从这个模板中创建新项目。这是一个手动过程,但在项目的后续重新生成过程中进行了这些手动修改后,就可以自动完成它。首先按照前面的方法把ExtendedProjectTemplateExample导出为一个新项目模板,再在Windows Explorer中找到这个模板的.zip文件,在其中找到.vstemplate文件并编辑它,具体而言,在.vstemplate文件中添加如下代码:

      <VSTemplate Version="2.0.0"
       xmlns="http://schemas.microsoft.com/developer/vstemplate/2005" Type="Project">
        <TemplateData>
        ...
        </TemplateData>
        <TemplateContent>
        ...
        </TemplateContent>
        <WizardExtension>
          <Assembly>WizardClassLibrary, Version=1.0.0.0, Culture=neutral,
             PublicKeyToken=022e960e5582ca43, Custom=null</Assembly>
          <FullClassName>WizardClassLibrary.MyWizard</FullClassName>
        </WizardExtension>
      </VSTemplate>

在示例中添加的<WizardExtension>节点表示向导的类名和它所在的强名称程序集。前面已经给向导程序集签名,所以现在只需要确定PublicKeyToken。最简单的方式是打开Visual Studio 2015命令提示符,定位到包含WizardLibrary.dll的目录。然后执行sn -T <assembly>命令。图15-11显示了该命令的输出结果。需要使用通过命令提示行找到的值替换.vstemplate文件中的PublicKeyToken值。

图15-11

现在,有一个.zip文件,其中包含项目模板,以及一个可用来扩展项目创建过程的程序集。最大的挑战是如何把这些作品交给其他人。本章一开始的模板可以通过将一个.zip文件放在合适的目录中来部署,而这个扩展模板不能。它需要把WizardLibrary.dll文件放在GAC中,而这需要一个安装程序。如何创建安装程序的详细信息参见第49章。