かずきのBlog@hatena

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

T4 TemplateでViewModelを生成する その3「Commandにも対応」

前のやつにCommandも定義出来るように追加してみました。

namespace VMDsl
{
    using System.Collections.Generic;

    // 名前空間
    public class NSDef
    {
        public NSDef()
        {
            this.Classes = new List<ClassDef>();
        }

        public string NS { get; set; }
        public List<ClassDef> Classes { get; private set; }
    }

    // クラスの定義
    public class ClassDef
    {

        public ClassDef(NSDef ns)
        {
            this.NS = ns;
            this.Properties = new List<PropertyDef>();
            this.Commands = new List<CommandDef>();
            this.NS.Classes.Add(this);
        }

        public NSDef NS { get; private set; }
        public string Name { get; set; }
        public List<PropertyDef> Properties { get; private set; }
        public List<CommandDef> Commands { get; private set; }
    }

    // プロパティの定義
    public class PropertyDef
    {
        public PropertyDef()
        {
            this.Attributes = new List<string>();
        }

        public string Type { get; set; }
        public string Name { get; set; }
        public List<string> Attributes { get; private set; }
    }

    // コマンド
    public class CommandDef
    {
        public string Name { get; set; }
        public string CommandClass { get; set; }
        public string ExecuteMethodName { get; set; }
        public string CommandParameterType { get; set; }
    }

    // 流れるようなインターフェースの起点
    public static class Namespace
    {
        public static NSDef Make(string name)
        {
            return new NSDef { NS = name };
        }
    }

    // 流れるようなインターフェースのための拡張メソッド
    public static class ObjectExtensions
    {
        // クラスを追加する
        public static ClassDef Class(this NSDef self, string name)
        {
            var clazz = new ClassDef(self)
            {
                Name = name
            };
            return clazz;
        }

        // プロパティをクラスに追加する
        public static ClassDef Property(this ClassDef self, string type, string name, params string[] attrs)
        {
            var prop = new PropertyDef
            {
                Type = type,
                Name = name
            };
            prop.Attributes.AddRange(attrs);
            self.Properties.Add(prop);
            return self;
        }

        // コマンドをクラスに追加する
        public static ClassDef Command(this ClassDef self, string name, string commandParameterType)
        {
            var command = new CommandDef
            {
                Name = name,
                // とりあえずBlendで用意されてるActionCommandにする
                CommandClass = "Microsoft.Expression.Interactivity.Core.ActionCommand",
                ExecuteMethodName = name + "_Execute",
                CommandParameterType = commandParameterType
            };
            self.Commands.Add(command);
            return self;
        }

        public static ClassDef Command(this ClassDef self, string name)
        {
            return self.Command(name, "object");
        }

        // クラスの定義を終わる
        public static NSDef EndClass(this ClassDef self)
        {
            return self.NS;
        }
    }
}

テンプレートは以下のように変更しました。そろそろ何がなんだかわからなくなってきました・・・。

<#
foreach (var n in ns)
{
#>
namespace <#= n.NS #>
{
	using System;
	using System.ComponentModel.DataAnnotations;
<#
	foreach (var clazz in n.Classes)
	{
#>
	public partial class <#= clazz.Name #> : WpfApplication15.ViewModelBase
	{
		// Properties
<#
		foreach (var p in clazz.Properties)
		{
#>
		private <#= p.Type #> _<#= p.Name #>;
<#
			foreach (var attr in p.Attributes)
			{
#>
		[<#= attr #>]
<#
			}
#>
		public <#= p.Type #> <#= p.Name #>
		{
			get
			{
				return _<#= p.Name #>; 
			}
			
			set
			{
				if (Equals(value, _<#= p.Name #>))
				{
					return;
				}
				Validator.ValidateProperty(
					value,
					new ValidationContext(this, null, null)
					{
						MemberName = "<#= p.Name #>"
					});
			
				_<#= p.Name #> = value;
				base.OnPropertyChanged("<#= p.Name #>");
			}
		}
<#
		}
#>
		// Commands
<#
		foreach (var command in clazz.Commands)
		{
#>
		private System.Windows.Input.ICommand _<#= command.Name #>;
		public System.Windows.Input.ICommand <#= command.Name #>
		{
			get 
			{ 
				return _<#= command.Name #> = _<#= command.Name #> ??
					 new <#= command.CommandClass #>(o => <#= command.ExecuteMethodName #>((<#= command.CommandParameterType #>) o)); 
			}
		}
		partial void <#= command.ExecuteMethodName #>(<#= command.CommandParameterType #> arg);
<#
		}
#>
	}
<#
	}
#>
}
<#
}
#>

こんな風にメタデータを定義します。

<#@ assembly name="$(ProjectDir)\T4\VMDsl.dll" #>
<#@ import namespace="VMDsl" #>
<#
var ns = new[]
{
	Namespace.Make("WpfApplication15.ViewModels")
		.Class("PersonViewModel")
			.Property("string", "Name",
				"Required(ErrorMessage=\"だめよ\")")
			.Property("int?", "Age")
			.Command("Hoge")
		.EndClass()
};
#>

こんなViewModelが定義されます。パーシャルメソッドでコマンドの実行メソッドが定義されるので、パーシャルクラスを定義してメソッドを定義するだけでOKです。

// <autogenerated />
namespace WpfApplication15.ViewModels
{
	using System;
	using System.ComponentModel.DataAnnotations;
	public partial class PersonViewModel : WpfApplication15.ViewModelBase
	{
		// Properties
		private string _Name;
		[Required(ErrorMessage="だめよ")]
		public string Name
		{
			get
			{
				return _Name; 
			}
			
			set
			{
				if (Equals(value, _Name))
				{
					return;
				}
				Validator.ValidateProperty(
					value,
					new ValidationContext(this, null, null)
					{
						MemberName = "Name"
					});
			
				_Name = value;
				base.OnPropertyChanged("Name");
			}
		}
		private int? _Age;
		public int? Age
		{
			get
			{
				return _Age; 
			}
			
			set
			{
				if (Equals(value, _Age))
				{
					return;
				}
				Validator.ValidateProperty(
					value,
					new ValidationContext(this, null, null)
					{
						MemberName = "Age"
					});
			
				_Age = value;
				base.OnPropertyChanged("Age");
			}
		}
		// Commands
		private System.Windows.Input.ICommand _Hoge;
		public System.Windows.Input.ICommand Hoge
		{
			get 
			{ 
				return _Hoge = _Hoge ??
					 new Microsoft.Expression.Interactivity.Core.ActionCommand(o => Hoge_Execute((object) o)); 
			}
		}
		partial void Hoge_Execute(object arg);
	}
}

プロジェクトは以下からダウンロードできます。
T4VMDsl2.zip