かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

ViewModelのコードの自動生成機能とアイテムテンプレートを作ってみた

非実在コマンドパターンを掘り下げていくと、どう頑張ってもVisual Studioがコマンドをうまく認識してくれない+Expression Blendのデザイナがエラーはいて表示してくれなくなったりと挫折してしまいました。
ということで、ViewModelは今の所自動生成するのが一番なんだろうという考えに至り、とりあえずViewModelの自動生成と、それようのアイテムテンプレートを作りました。

ちなみに、中身がわかれば任意のViewModelの基本クラスとかに差し替えとかも出来たりしますが今回作ったのは@ugaya40さんがこっそり作ってる、まだ未完成&未公開のLivetというMVVMフレームワークを前提にしてます。Livetのバイナリは同梱してるので、使うことは出来ます。(たぶんベータ以前の状態だと思われ)


とりあえず、今回作ったのは以下のものです。

  • LivetSupportTemplate.zip
    • Livetの提供するViewModelを基本クラスとするViewModelの自動生成が出来るアイテムテンプレートとLivetのバイナリと自動生成用の補助ライブラリが入っています。
  • Livet.Support.zip
    • LivetSupportTemplateのソースです。

どんな実装になっているかはソースを見てもらうとして簡単に使い方を書きます。

ワンステップでインストール

まず、ViewModelを自動生成してくれるアイテムテンプレートをインストールします。インストールといってもVisual Studioのユーザテンプレートのフォルダにコピーするだけです。
LivetSupportTemplate.zipを解凍すると、LivetViewModel.zipというファイルが出来ます。これを以下のフォルダにコピーします。

C:\Users\ユーザ名\Documents\Visual Studio 2010\Templates\ItemTemplates\Visual C#

ユーザテンプレートの使い方

プロジェクトの作成

まず、Visual Studioを起動してWPFアプリケーションを作成します。ここではLivetTemplateSampleという名前にしました。

必要なアセンブリのコピー

LivetSupportTemplate.zipを解凍するとLibsというフォルダがあるので、それを先ほど作成したLivetTemplateSampleのソリューションフォルダにコピーします。
以下のような配置になります。

LivetTemplateSample (ソリューションのフォルダ)
  Libs (このフォルダをコピーする)
    Livet.dll
    Livet.Support.dll
  LivetTemplateSample (プロジェクトフォルダ)

参照の追加

プロジェクトの参照設定にLivet.dllを追加します。(Livet.Support.dllは追加しなくていいです!)

ViewModelを自動生成してくれるテンプレートの追加

プロジェクトの右クリックメニューから追加→新しい項目を選択します。

Livet ViewModel Generatorという項目が追加されているので選択します。作成するファイル名を適当に(下図ではSampleViewModel.csにしてます)つけて追加をクリックします。

そうすると以下の2ファイルが作成されます。

  • SampleViewModel.cs
    • カスタムコードを追加するためのファイル
  • SampleViewModel.tt
    • ViewModel生成のためのT4テンプレート
SampleViewModel.ttの内容

SampleViewModel.ttは以下のような内容になっています。

<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="$(SolutionDir)\Libs\Livet.Support.dll" #>
<#@ import namespace="Livet.Support" #>
<#@ output extension=".generated.cs" #>
<#= VM.Generate(new ViewModel
{
	Namespace = "LivetTemplateSample",
	Using = new[]
	{
		"System"
	},
	ClassName = "SampleViewModel",
	Properties = new[]
	{
		new Property("int", "Age")
	},
	Commands = new []
	{
		new Command("Add"),
	}
}) #>

この1ファイルで1つのViewModelが自動生成されます。以下に各プロパティの説明をします。

  • Namespace(string)
    • 自動生成されるViewModelの名前空間を指定します(ふつうは自動で指定されるのでいじる必要はありません)
  • Using(string[])
    • using句を指定します。多分、これもいじらなくていいと思います。
  • ClassName
    • 自動生成されるViewModelのクラス名を指定します。これも自動で指定されるのでいじる必要はありません。
  • Properties(Property[])
    • プロパティを指定します
  • Commands(Command[]
    • Commandを指定します。

プロパティとコマンドの情報は専用のクラスを使って指定します。Propertyクラスには以下のコンストラクタが定義されています。

/// <summary>
/// プロパティのコンストラクタ
/// </summary>
/// <param name="typeName">プロパティの型名を指定します</param>
/// <param name="name">プロパティ名を指定します</param>
/// <param name="attributes">プロパティにつける属性を指定します(例:Required(ErrorMessage=\"エラーメッセージ\"))</param>
public Property(string typeName, string name, params string[] attributes)

Commandクラスには以下のコンストラクタが定義されています。

/// <summary>
/// 引数なしでCanExecuteを使用しないコマンドを作成します。
/// </summary>
/// <param name="name">コマンド名</param>
public Command(string name)

/// <summary>
/// 引数なしでsupportCanExecuteにtrueを設定するとCanExecuteを使用するコマンドを作成します。
/// </summary>
/// <param name="name">コマンド名</param>
/// <param name="supportCanExecute">CanExecuteを使用する場合はtrue</param>
public Command(string name, bool supportCanExecute)

/// <summary>
/// 引数を受け取るコマンドを作成します。
/// </summary>
/// <param name="argumentType">コマンド引数の型</param>
/// <param name="name">コマンド名</param>
public Command(string argumentType, string name)

/// <summary>
/// 引数を受け取るコマンドを作成します。
/// supportCanExecuteにtrueを設定するとCanExecuteを使用するコマンドを作成します。
/// </summary>
/// <param name="argumentType">コマンド引数の型</param>
/// <param name="name">コマンド名</param>
/// <param name="supportCanExecute">CanExecuteを使用する場合はtrue</param>
public Command(string argumentType, string name, bool supportCanExecute)

新規作成の状態で、以下のようなViewModelが自動生成されています。

namespace LivetTemplateSample
{
	using System;

	public partial class SampleViewModel : Livet.ViewModel
	{
		private int _Age;
		
		public int Age
		{
			get
			{
				return this._Age;
			}
			
			set
			{
				if (Equals(this._Age, value))
				{
					return;
				}
				
				this._Age = value;
				base.RaisePropertyChanged("Age");
			}
		}
		
		
		private Livet.Command.DelegateCommand<object> _AddCommand;
		
		public Livet.Command.DelegateCommand<object> AddCommand
		{
			get
			{
				return this._AddCommand = this._AddCommand ?? new Livet.Command.DelegateCommand<object>(
					arg => Add(),
					arg => true);
			}
		}
		
	}
}

T4テンプレートにCommandの定義を1つ追加してみます。

<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="$(SolutionDir)\Libs\Livet.Support.dll" #>
<#@ import namespace="Livet.Support" #>
<#@ output extension=".generated.cs" #>
<#= VM.Generate(new ViewModel
{
	Namespace = "LivetTemplateSample",
	Using = new[]
	{
		"System"
	},
	ClassName = "SampleViewModel",
	Properties = new[]
	{
		new Property("int", "Age")
	},
	Commands = new []
	{
		new Command("Add"),
		// これを追加!!
		new Command("string", "AbortShibayan", true)
	}
}) #>

T4テンプレートを保存するとViewModelが以下のように再生成されます。

namespace LivetTemplateSample
{
	using System;

	public partial class SampleViewModel : Livet.ViewModel
	{
		private int _Age;
		
		public int Age
		{
			get
			{
				return this._Age;
			}
			
			set
			{
				if (Equals(this._Age, value))
				{
					return;
				}
				
				this._Age = value;
				base.RaisePropertyChanged("Age");
			}
		}
		
		
		private Livet.Command.DelegateCommand<object> _AddCommand;
		
		public Livet.Command.DelegateCommand<object> AddCommand
		{
			get
			{
				return this._AddCommand = this._AddCommand ?? new Livet.Command.DelegateCommand<object>(
					arg => Add(),
					arg => true);
			}
		}
		
		// ここから下が追加されたよ!!!
		private Livet.Command.DelegateCommand<string> _AbortShibayanCommand;
		
		public Livet.Command.DelegateCommand<string> AbortShibayanCommand
		{
			get
			{
				return this._AbortShibayanCommand = this._AbortShibayanCommand ?? new Livet.Command.DelegateCommand<string>(
					arg => AbortShibayan(arg),
					arg => CanAbortShibayan(arg));
			}
		}
		
	}
}

コマンドで使用するAbortShibayanメソッドとCanAbortShibayanメソッドは自動生成されないのでSampleViewModel.csのほうに手動で追加します。(コンパイルエラーになるから実装し忘れはないと思う)

namespace LivetTemplateSample
{
    public partial class SampleViewModel
    {
        // コマンドのメソッドを記述してください
        public void Add()
        {
        }

        public void AbortShibayan(string s)
        {
        }

        public bool CanAbortShibayan(string s)
        {
            return s == "@shibayan";
        }
    }
}

こんな感じでT4テンプレートにクラスの定義を追加しつつ、コードファイルでカスタムの処理とかを作りこんでいきます。もう1つViewModelを作りたい場合はLivet ViewModel Generatorで新規に追加すればOKです。
と言う風に、ViewModelの自動生成処理を割と本気?で作ってみました。以上です。