しっぽを追いかけて

ぐるぐるしながら考えています

Unity と猫の話題が中心   掲載内容は個人の私見であり、所属組織の見解ではありません

項目テンプレートで名前空間を設定する

Visual Studio の項目テンプレートは自作できるのですが、どういうわけかトップレベルの名前空間を埋め込む パラメータがありません;

普通にテンプレートを作っても追加先のフォルダで名前空間の決まる $rootnamaspace$ のパラメータを利用するしかないので、~.Views と ~.Presenters の名前空間のクラスを一度に生成するなんてことができないのが不便なところ・・・

標準で用意してほしいところですが、カスタムウィザードを用意することで代用できました

EnvDTE.dll と Microsoft.VisualStudio.TemplateWizardInterface.dll、Microsoft.VisualStudio.WinRT.TemplateWizards.dll を参照したクラスライブラリプロジェクトを作成して下記のような IWizard を実装するクラスを作成します

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using EnvDTE;
using Microsoft.VisualStudio.TemplateWizard;
using System.IO;
using System.Diagnostics;

/// <summary>
/// 項目テンプレート用汎用ウィザード
/// </summary>
public class ItemTemplateWizard : IWizard
{
    private _DTE dte;

    // 概要:
    //     テンプレートの項目を開く前にカスタム ウィザードのロジックを実行します。
    //
    // パラメーター:
    //   projectItem:
    //     開かれるプロジェクト項目。
    public void BeforeOpeningFile(ProjectItem projectItem)
    {
    }

    //
    // 概要:
    //     プロジェクトが生成を完了したときにカスタム ウィザードのロジックを実行します。
    //
    // パラメーター:
    //   project:
    //     生成を完了したプロジェクト。
    public void ProjectFinishedGenerating(Project project)
    {
    }

    //
    // 概要:
    //     プロジェクト項目が生成を完了したときにカスタム ウィザードのロジックを実行します。
    //
    // パラメーター:
    //   projectItem:
    //     生成を完了したプロジェクト項目。
    public void ProjectItemFinishedGenerating(ProjectItem projectItem)
    {
    }

    //
    // 概要:
    //     ウィザードがすべてのタスクを完了したときにカスタム ウィザードのロジックを実行します。
    public void RunFinished()
    {
    }

    //
    // 概要:
    //     テンプレート ウィザードの実行開始時にカスタム ウィザードのロジックを実行します。
    //
    // パラメーター:
    //   automationObject:
    //     テンプレート ウィザードで使用されているオートメーション オブジェクト。
    //
    //   replacementsDictionary:
    //     置き換える標準パラメーターのリスト。
    //
    //   runKind:
    //     ウィザードの実行の種類を示す Microsoft.VisualStudio.TemplateWizard.WizardRunKind。
    //
    //   customParams:
    public void RunStarted(object automationObject, Dictionary<string, string> replacementsDictionary, WizardRunKind runKind, object[] customParams)
    {
        try
        {
            if (!(automationObject is _DTE))
            {
                return;
            }

            this.dte = (_DTE)automationObject;

            var project = (Project)((object[])dte.ActiveSolutionProjects)[0];
            string defaultNamespae = null;

            foreach (Property property in project.Properties)
            {
                if (property.Name.ToLower() == "defaultnamespace")
                {
                    defaultNamespae = (string)property.Value;
                }
            }

            replacementsDictionary.Add("$defaultnamespace$", defaultNamespae);
        }
        catch { }
    }

    //
    // 概要:
    //     指定したプロジェクト項目をプロジェクトに追加するかどうかを示します。
    //
    // パラメーター:
    //   filePath:
    //     プロジェクト項目のパス。
    //
    // 戻り値:
    //     プロジェクト項目をプロジェクトに追加する場合は true。それ以外の場合は false。
    public bool ShouldAddProjectItem(string filePath)
    {
        return true;
    }
}

RunStarted メソッドの中で引数の automationObject を _DTE にキャストして Project インスタンスを取得、そこから defaultnamespace のプロパティの値を参照して replacementsDictionary にパラメータとして追加します

ウィザードは GAC に登録しないと項目テンプレートで利用できないので、このクラスを含んだ状態でビルドしてできる dll を下記のようなコマンドを管理者権限で実行して登録します

cd /d %~dp0

"%ProgramFiles(x86)%\Microsoft SDKs\Windows\v8.1A\bin\NETFX 4.5.1 Tools\gacutil.exe" -i PrismMVPVM.Templates.dll

登録が終わったら項目テンプレートの .vstemplate に タグで登録した dll、ウィザードクラスを設定します

<?xml version="1.0" encoding="utf-8"?>
<VSTemplate Version="3.0.0" Type="Item" xmlns="http://schemas.microsoft.com/developer/vstemplate/2005" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://schemas.microsoft.com/developer/vstemplate/2005">
  <TemplateData>
    <Name>MVPVM 基本ページ(Prism)</Name>
    <Description>MVPVM アーキテクチャの Prism フレームワーク 基本ページ</Description>
    <Icon>Icon.png</Icon>
    <TemplateGroupID>WinRT-Managed</TemplateGroupID>
    <ProjectType>CSharp</ProjectType>
    <NumberOfParentCategoriesToRollUp>1</NumberOfParentCategoriesToRollUp>
    <DefaultName>BasicPage.xaml</DefaultName>
    <ShowByDefault>false</ShowByDefault>
    <SortOrder>41</SortOrder>
    <PreviewImage>BasicPage.png</PreviewImage>
    <TargetPlatformName>Windows</TargetPlatformName>
    <RequiredPlatformVersion>6.3.0</RequiredPlatformVersion>
  </TemplateData>
  <TemplateContent>
    <ProjectItem ReplaceParameters="true" OpenInEditor="true" ItemType="Page" SubType="Designer" CustomTool="MSBuild:Compile" TargetFileName="$fileinputname$.xaml">BasicPage.xaml</ProjectItem>
    <ProjectItem ReplaceParameters="true" TargetFileName="$fileinputname$.xaml.cs">BasicPage.xaml.cs</ProjectItem>
    <ProjectItem ReplaceParameters="true" OpenInEditor="true" TargetFileName="/Presenters/$fileinputname$Presenter.cs">BasicPagePresenter.cs</ProjectItem>
  </TemplateContent>
  <WizardExtension>
    <Assembly>PrismMVPVM.Templates, Version=1.0.0.0, Culture=neutral, PublicKeyToken=c8a122ba7c2f04d1</Assembly>
    <FullClassName>PrismMVPVM.Templates.ItemTemplateWizard</FullClassName>
  </WizardExtension>
</VSTemplate>

こうするとテンプレートコードの中でトップレベルの名前空間が下記のように埋め込めるというわけですね

#region License
//-----------------------------------------------------------------------
// <copyright>
//     Copyright $registeredorganization$ $year$.
// </copyright>
//-----------------------------------------------------------------------
#endregion

namespace $defaultnamespace$.Views
{
    using Microsoft.Practices.Prism.StoreApps;
    using $defaultnamespace$.Presenters;

    /// <summary>
    /// $fileinputname$ 画面
    /// </summary>
    [PresenterView(typeof($safeitemname$Presenter))]
    public sealed partial class $safeitemname$ : VisualStateAwarePage, IPresenterView<$safeitemname$Presenter>
    {
        /// <summary>
        /// コンストラクタ
        /// </summary>
        public $safeitemname$()
        {
            this.InitializeComponent();
        }

        #region IPresenterView<TPresenter>

        /// <summary>
        /// この画面の Presenter
        /// </summary>
        public $safeitemname$Presenter Presenter
        {
            get { return this.GetPresenter<$safeitemname$Presenter>(); }
        }

        #endregion //IPresenterView<TPresenter>
    }
}

ちょっとめんどくさい;