創建Visual Studio插件,項目模板,項目創建嚮導等原來也是如此簡單。本文通過開發Prism的Plugin實例講解其中的實現過程與註意事項。 ...
在【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、生成發佈
使用效果如下:
這回再也不用每次都添加好幾次工程了,一次搞定。懶人就是這樣想辦法偷懶。