かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

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