在【Xamarin+Prism开发详解系列】里面经常使用到【Prism unity app】的模板创建Prism.Forms项目:
备注:由于Unity社区已经不怎么活跃,下一个版本将会有Autofac,DryIOC,Ninject的项目模板。
自动弹出选择框:
对于我这类还没有动手写过模板的人来说,确实挺新奇的。于是就决定自己也写个类似的试试,目的就是通过向导创建跨平台Plugin项目,类似Plugin for xamarin,不过是针对Prism,对应平台可以自由选择创建。试了之后才发现也有不少注意的地方,特写下此文做备忘。
项目地址:https://github.com/NewBLife/Prism.Forms.Plugin
插件下载地址:TemplatesForPrism.vsix
1、安装插件开发用的Extensibility模板与工具
2、新建VSIX Project插件项目
source.extension.vsixmanifest 这个文件相当重要,里面可以指定安装目标,模板,向导等。
最后我实现的例子:
安装目标:VS2013以上(2017估计不行)
资产:Project模板,Item模板,Wizard向导
3、从【C# Item Template】与【C# Project Template】模板创建项目。
4、从【类库】模板创建Wizard项目。(Wizard向导只能是类库)
以上步骤之后的项目结构:
介绍:
- Prism.Forms.Plugin.Nuspec:Plugin打包文件模板
- Prism.Forms.Plugin:Plugin项目模板
- Prism.Forms.Plugin.Wizard:Plugin创建向导
- TemplatesForPrism:VSIX插件项目
5、添加引用
- Prism.Forms.Plugin.Nuspec:Microsoft.VisaulStudio.CoreUtility
- Prism.Forms.Plugin:Microsoft.VisaulStudio.CoreUtility
- Prism.Forms.Plugin.Wizard:Microsoft.VisaulStudio.TemplateWizardinterface,envdte
- TemplatesForPrism:Prism.Forms.Plugin.Nuspec,Prism.Forms.Plugin,Prism.Forms.Plugin.Wizard
6、添加生成向导
6.1、NewProjectWizard项目选择向导创建新建一个WinForm选择框,返回选择的结果。
继承IWiazrd向导接口实现:
using EnvDTE; using Microsoft.VisualStudio.TemplateWizard; using System; using System.Collections.Generic; using System.IO; namespace Prism.Forms.Plugin.Wizard { public class NewProjectWizard : IWizard { private DTE _dte = null; private string _solutionDir = null; private string _templateDir = null; private string _projectName = null; PluginNewProjectDialogResult _dialogResult; public void BeforeOpeningFile(ProjectItem projectItem) { } public void ProjectFinishedGenerating(Project project) { if(_dialogResult.CreateAndroid) CreateProject(Target.Droid.ToString()); if (_dialogResult.CreateiOS) CreateProject(Target.iOS.ToString()); if
(_dialogResult.CreateUwp)
CreateProject(Target.UWP.ToString());
}
void CreateProject(string platform) { string name = $"{_projectName}.{platform}"; string projectPath = System.IO.Path.Combine(_solutionDir, Path.Combine(_projectName, name)); string templateName = $"Prism.Forms.Plugin.{platform}"; string templatePath = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(_templateDir), $"{templateName}.zip\\{templateName}.vstemplate"
); _dte.Solution.AddFromTemplate(templatePath, projectPath, name); } public void ProjectItemFinishedGenerating(ProjectItem projectItem) { } public void RunFinished() { } public void RunStarted(object automationObject, Dictionary<string, string> replacementsDictionary, WizardRunKind runKind, object[] customParams) { try { _dte = automationObject as DTE; _projectName = replacementsDictionary["$safeprojectname$"]; _solutionDir = System.IO.Path.GetDirectoryName(replacementsDictionary["$destinationdirectory$"]); _templateDir = System.IO.Path.GetDirectoryName(customParams[0] as string); PluginNewProjectDialog dialog= new PluginNewProjectDialog(); dialog.ShowDialog(); _dialogResult =
dialog.Result; if (_dialogResult.Cancelled) throw new WizardBackoutException(); } catch (Exception) { if (Directory.Exists(_solutionDir)) Directory.Delete(_solutionDir, true); throw; } } public bool ShouldAddProjectItem(string filePath) { return true; } } }
模板选择后执行RunStarted==》弹出Winform选择框==》执行ProjectFinishedGenerating创建子项目
6.2、项目名称safeprojectgroupname向导创建
在NewProjectWizard向导的CreateProject方法里面我们设置了每个子项目的名称为"{_projectName}.{platform}",所以到达子项目的时候$safeprojectname$里面已经带了iOS,Droid,UWP等名称,所以有必要进行处理。
using EnvDTE; using Microsoft.VisualStudio.TemplateWizard; using System.Collections.Generic; using System.Threading; namespace Prism.Forms.Plugin.Wizard { public class SafeProjectGroupName : IWizard { public const string ParameterName = "$safeprojectgroupname$"; static ThreadLocal<string> projectGroupName = new ThreadLocal<string>(); bool isRootWizard; public void BeforeOpeningFile(ProjectItem projectItem) { } public void ProjectFinishedGenerating(Project project) { } public void ProjectItemFinishedGenerating(ProjectItem projectItem) { } public void RunFinished() { // If we were the ones to set the value, we must clear it. if (isRootWizard) projectGroupName.Value = null; } public void RunStarted(object automationObject, Dictionary<string, string> replacementsDictionary, WizardRunKind runKind, object[] customParams) { if (projectGroupName.Value == null) { var name = replacementsDictionary["$safeprojectname$"]; if (name.EndsWith(Target.Abstractions.ToString())) { projectGroupName.Value = name.Replace($".{Target.Abstractions.ToString()}", string.Empty); } else if (name.EndsWith(Target.Droid.ToString())) { projectGroupName.Value = name.Replace($".{Target.Droid.ToString()}", string.Empty); } else if (name.EndsWith(Target.iOS.ToString())) { projectGroupName.Value = name.Replace($".{Target.iOS.ToString()}", string.Empty); } else if (name.EndsWith(Target.UWP.ToString())) { projectGroupName.Value = name.Replace($".{Target.UWP.ToString()}", string.Empty); } // If there wasn't a value already, it means we're the root wizard itself. isRootWizard = true; } replacementsDictionary[ParameterName] = projectGroupName.Value; } public bool ShouldAddProjectItem(string filePath) { return true; } } }
Debug测试方法:
- 给类库添加署名,否则无法注册
- 管理员权限启动VS2015 开发人员命令提示工具
- 导航到wizard生成目录
- 执行gacutil -if Prism.Forms.Plugin.Wizard.dll 命令注册类库
- 执行gacutil –l 列出类库的Token信息(Prism.Forms.Plugin.Wizard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=b2335488e79a16da, processorArchitecture=MSIL)
- 模板文件的WizarExtension节点添加以上Token信息与入口类名称
- 安装模板,设置断点
- 启动另一个Visual Studio程序
- 调试-》附加到进程-》附加新启动的Visual Studio进程
- 新Visual Studio窗口选择建立的模板,将进入断点
7、添加模板文件并设置
Prism.Forms.Plugin.Nuspec模板设置
<?xml version="1.0" encoding="utf-8"?> <VSTemplate Version="3.0.0" Type="Item" xmlns="http://schemas.microsoft.com/developer/vstemplate/2005" xmlns:sdk="http://schemas.microsoft.com/developer/vstemplate-sdkextension/2010"> <TemplateData> <Name>Plugin nuspec for Prism.Forms</Name> <Description>Create a nuspec file for Prism.Forms.Plugin solution</Description> <Icon>Prism.Forms.Plugin.Nuspec.ico</Icon> <TemplateID>325a0391-d11c-4432-8658-b70405881e87</TemplateID><ProjectType>General</ProjectType>
<RequiredFrameworkVersion>2.0</RequiredFrameworkVersion> <NumberOfParentCategoriesToRollUp>1</NumberOfParentCategoriesToRollUp> <DefaultName>PrismFormsPlugin.nuspec</DefaultName> </TemplateData> <TemplateContent> <ProjectItem TargetFileName="$fileinputname$.nuspec" ReplaceParameters="true">Prism.Forms.Plugin.nuspec</ProjectItem> </TemplateContent> </VSTemplate>
注意点:
- Item模板的ProjectType默认为CSharp,有时候没法找到,最好设置为General。
- 模板参照文件的生成操作都设置为无。
- 记得设置分类Category,这样好找。
效果如下:(在添加新项里面)
Prism.Forms.Plugin模板设置
每个文件夹下面为相应的模板文件
- Prism.Forms.Plugin.Abstractions:接口定义
- Prism.Forms.Plugin.Droid:Android平台特性的接口实现
- Prism.Forms.Plugin.iOS:iOS平台特性的接口实现
- Prism.Forms.Plugin.UWP:UWP平台特性的接口实现
最外面的入口模板文件Prism.Forms.Plugin.vstemplate设置:
<?xml version="1.0" encoding="utf-8"?> <VSTemplate Version="3.0.0"Type="ProjectGroup"
xmlns="http://schemas.microsoft.com/developer/vstemplate/2005" xmlns:sdk="http://schemas.microsoft.com/developer/vstemplate-sdkextension/2010"> <TemplateData> <Name>Plugin for Prism.Forms</Name> <Description>Creates all files necessary to create a plugin for Prism.Forms</Description> <Icon>Prism.Forms.Plugin.ico</Icon> <ProjectType>CSharp</ProjectType> <RequiredFrameworkVersion>2.0</RequiredFrameworkVersion> <SortOrder>30</SortOrder> <TemplateID>16bac5e1-199d-4e08-9ed3-2ef287221be1</TemplateID> <DefaultName>PrismFormsPlugin</DefaultName> <ProvideDefaultName>true</ProvideDefaultName> <PromptForSaveOnCreation>true</PromptForSaveOnCreation> <NumberOfParentCategoriesToRollUp>1</NumberOfParentCategoriesToRollUp> </TemplateData> <TemplateContent><ProjectCollection> <ProjectTemplateLink ProjectName="$safeprojectname$.Abstractions">
Prism.Forms.Plugin.Abstractions\Prism.Forms.Plugin.Abstractions.vstemplate
</ProjectTemplateLink> </ProjectCollection>
</TemplateContent><WizardExtension> <Assembly>Prism.Forms.Plugin.Wizard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=b2335488e79a16da</Assembly> <FullClassName>Prism.Forms.Plugin.Wizard.NewProjectWizard</FullClassName> </WizardExtension>
</VSTemplate>
注意点:
- 入口模板文件的Type必须为ProjectGroup,否则只创建一个工程项目
- 模板内容必须使用ProjectTemplateLink添加每个子项目的模板文件设置
- 由于使用向导可选择方式创建子项目,所有这里只添加了接口的模板文件
- WizarExtension节点为向导程序设置,调用Prism.Forms.Plugin.Wizard.NewProjectWizard
子项目模板Prism.Forms.Plugin.Abstractions.vstemplate设置:
<VSTemplate Version="3.0.0" xmlns="http://schemas.microsoft.com/developer/vstemplate/2005"Type="Project">
<TemplateData>
<Name>Prism.Forms.Plugin.Abstractions</Name>
<Description>Implementation Interface for Prism.Forms.Plugin </Description>
<ProjectType>
CSharp
</ProjectType>
<ProjectSubType>
</ProjectSubType>
<SortOrder>1000</SortOrder>
<CreateNewFolder>true</CreateNewFolder>
<DefaultName>Prism.Forms.Plugin.Abstractions</DefaultName> <ProvideDefaultName>true</ProvideDefaultName> <LocationField>Enabled</LocationField> <EnableLocationBrowseButton>true</EnableLocationBrowseButton> <Icon>__TemplateIcon.ico</Icon> <PromptForSaveOnCreation>true</PromptForSaveOnCreation> </TemplateData> <TemplateContent> <Project TargetFileName="$safeprojectname$.Abstractions.csproj" File="Prism.Forms.Plugin.Abstractions.csproj" ReplaceParameters="true"> <ProjectItem ReplaceParameters="true" TargetFileName="I$safeprojectgroupname$.cs">IPrismFormsPlugin.cs</ProjectItem> <Folder Name="Properties" TargetFolderName="Properties"> <ProjectItem ReplaceParameters="true" TargetFileName="AssemblyInfo.cs">AssemblyInfo.cs</ProjectItem> </Folder> </Project> </TemplateContent><WizardExtension> <Assembly>Prism.Forms.Plugin.Wizard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=b2335488e79a16da</Assembly> <FullClassName>Prism.Forms.Plugin.Wizard.SafeProjectGroupName</FullClassName> </WizardExtension>
</VSTemplate>
注意点:
- Type为Project。
- safeprojectgroupname是Prism.Forms.Plugin.Wizard.SafeProjectGroupName向导提供的参数,不是VS内部参数。值为用户选择模板时输入的工程名称。
- 模板参照文件(IPrismFormsPlugin.cs,AssemblyInfo.cs等)的生成操作设置为无,否则编译出错。
IPrismFormsPlugin.cs设置:使用safeprojectgroupname
using System; namespace $safeprojectgroupname$ { public interface I$safeprojectgroupname$ { } }
Prism.Forms.Plugin.Abstractions.csproj设置:使用safeprojectgroupname
<?xml version="1.0" encoding="utf-8"?> <Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> <PropertyGroup> <MinimumVisualStudioVersion>10.0</MinimumVisualStudioVersion> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <ProjectGuid>{$guid1$}</ProjectGuid> <OutputType>Library</OutputType> <AppDesignerFolder>Properties</AppDesignerFolder><RootNamespace>$safeprojectgroupname$</RootNamespace> <AssemblyName>$safeprojectname$</AssemblyName>
<DefaultLanguage>en-US</DefaultLanguage> <FileAlignment>512</FileAlignment> <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> <TargetFrameworkProfile>Profile7</TargetFrameworkProfile> <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <DebugSymbols>true</DebugSymbols> <DebugType>full</DebugType> <Optimize>false</Optimize> <OutputPath>bin\Debug\</OutputPath> <DefineConstants>DEBUG;TRACE</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <DebugType>pdbonly</DebugType> <Optimize>true</Optimize> <OutputPath>bin\Release\</OutputPath> <DefineConstants>TRACE</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel><DocumentationFile>bin\Release\$safeprojectname$.XML</DocumentationFile>
</PropertyGroup> <ItemGroup> <!-- A reference to the entire .NET Framework is automatically included --> </ItemGroup> <ItemGroup> <Compile Include="I$safeprojectgroupname$.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> </ItemGroup> <Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" /> <!-- To modify your build process, add your task inside one of the targets below and uncomment it. Other similar extension points exist, see Microsoft.Common.targets. <Target Name="BeforeBuild"> </Target> <Target Name="AfterBuild"> </Target> --> </Project>
总体文件结构如下:
备注:
IPrismFormsPlugin.cs,AssemblyInfo.cs,Prism.Forms.Plugin.Abstractions.csproj等都是通过VS自动生成的,不是手工创建的。具体方法就是事先建立后想要的项目结构与文件,然后使用VS的导出模板功能就可以自动生成了。添加进模板项目的时候一定记得将生成操作设置为无。
8、插件source.extension.vsixmanifest文件设置
添加安装目标,资产。
9、生成发布
使用效果如下:
这回再也不用每次都添加好几次工程了,一次搞定。懒人就是这样想办法偷懒。