メメタァ
というわけで、DependencyObjectについての「その2」です。
前回:http://blogs.wankuma.com/kazuki/archive/2008/01/28/119675.aspx
前回は、単純なプロパティを定義する方法を簡単にやってみた。
例としてPersonクラスを作った。
public class Person : DependencyObject
{
public static readonly DependencyProperty NameProperty =
DependencyProperty.Register("Name", typeof(string), typeof(Person));
public string Name
{
get { return (string)GetValue(NameProperty); }
set { SetValue(NameProperty, value); }
}
}
このNameプロパティにメタ情報をくっつけたりしてみようと思う。
メタ情報といえばAttributeがあるけど、DependencyPropertyでは、PropertyMetadataというやつでメタデータを表す。
PropertyMetadataは、DependencyProperty.Registerメソッドの第4引数として指定する。
長い事文章書くのは好きじゃないのでさくっとコードを書いてみる。
public class Person : DependencyObject
{
// デフォルト値匿名希望のNameプロパティ
public static readonly DependencyProperty NameProperty =
DependencyProperty.Register("Name", typeof(string), typeof(Person),
new PropertyMetadata("匿名 希望"));
public string Name
{
get { return (string)GetValue(NameProperty); }
set { SetValue(NameProperty, value); }
}
}
コメントにある通り、デフォルト値を指定してる。
本当にデフォルト値が効いてるのか試してみると…
class Program
{
static void Main(string[] args)
{
var p = new Person();
Console.WriteLine(p.Name);
}
}
実行結果
匿名 希望
ちゃんと効いてるっぽい。
さらに、PropertyChangedCallbackを指定することで値が変更されたときに色々できる。
public class Person : DependencyObject
{
// デフォルト値匿名希望のNameプロパティ
public static readonly DependencyProperty NameProperty =
DependencyProperty.Register("Name", typeof(string), typeof(Person),
new PropertyMetadata("匿名 希望", NameChanged));
private static void NameChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
Console.WriteLine(e.Property + " Changed");
Console.WriteLine(" NewValue: " + e.NewValue);
Console.WriteLine(" OldValue: " + e.OldValue);
}
public string Name
{
get { return (string)GetValue(NameProperty); }
set { SetValue(NameProperty, value); }
}
}
実行結果
匿名 希望
Name Changed
NewValue: 大田 一希
OldValue: 匿名 希望
ちゃんとコールバックが呼ばれてる。
関連するプロパティの値を書き変えたりするのに使うっぽい。
さらには、CoerceValueCallbackを指定することで値を強制することが出来る。
例えばMin, Max, Valueという値があってMin < Value < Maxの関係に無ければならない。といった関係を崩さないために使われてたりする。
ここでは、人の名前には絶対に「様」を最後につけなきゃだめっていうルールを徹底してみようと思う。
public class Person : DependencyObject
{
// デフォルト値匿名希望のNameプロパティ
public static readonly DependencyProperty NameProperty =
DependencyProperty.Register("Name", typeof(string), typeof(Person),
new PropertyMetadata("匿名 希望", NameChanged, CoerceNameValue));
private static void NameChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
Console.WriteLine(e.Property + " Changed");
Console.WriteLine(" NewValue: " + e.NewValue);
Console.WriteLine(" OldValue: " + e.OldValue);
}
// 名前には様をつけないといけないです
private static object CoerceNameValue(DependencyObject target, object baseValue)
{
string name = baseValue as string;
// 名前入力されてなかったら仕方ない
if (string.IsNullOrEmpty(name))
{
return string.Empty;
}
// 様がついてるか、デフォルト値のときはそのまま
if (name.EndsWith("様") || name == NameProperty.DefaultMetadata.DefaultValue as string)
{
return name;
}
// そうじゃなければ様をつける
return name + "様";
}
public string Name
{
get { return (string)GetValue(NameProperty); }
set { SetValue(NameProperty, value); }
}
}
実験用のMainは下のようにしてみた。
class Program
{
static void Main(string[] args)
{
var p = new Person();
Console.WriteLine(p.Name);
p.Name = "田中 太郎";
Console.WriteLine(p.Name);
}
}
実行結果
匿名 希望
Name Changed
NewValue: 田中 太郎様
OldValue: 匿名 希望
田中 太郎様
PropertyChangedCallbackに来る前に、CoerceValueCallbackを通るっぽい。
メモメモ。
最後は、バリデーション。
これは、説明の必要がないかもしれないけど、プロパティに変な値がセットされないかチェックするための仕組みです。
RegisterメソッドのPropertyMetadataの次の引数にValidateValueCallbackを指定する。
ValidateValueCallbackは、戻り値がboolで、引数がobject型1つのシンプルなものです。
早速実験。
public class Person : DependencyObject
{
// デフォルト値匿名希望のNameプロパティ
public static readonly DependencyProperty NameProperty =
DependencyProperty.Register("Name", typeof(string), typeof(Person),
new PropertyMetadata("匿名 希望", NameChanged, CoerceNameValue),
ValidateName);
private static void NameChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
Console.WriteLine(e.Property + " Changed");
Console.WriteLine(" NewValue: " + e.NewValue);
Console.WriteLine(" OldValue: " + e.OldValue);
}
// 名前には様をつけないといけないです
private static object CoerceNameValue(DependencyObject target, object baseValue)
{
string name = baseValue as string;
// 名前入力されてなかったら仕方ない
if (string.IsNullOrEmpty(name))
{
return string.Empty;
}
// 様がついてるか、デフォルト値のときはそのまま
if (name.EndsWith("様") || name == NameProperty.DefaultMetadata.DefaultValue as string)
{
return name;
}
// そうじゃなければ様をつける
return name + "様";
}
// 名前は、姓と名の間に全角スペースが入るとです
private static bool ValidateName(object value)
{
string name = value as string;
if (string.IsNullOrEmpty(name))
{
return true;
}
return name.IndexOf(' ') != -1;
}
public string Name
{
get { return (string)GetValue(NameProperty); }
set { SetValue(NameProperty, value); }
}
}
Nameプロパティには、"大田 一希"や"田中 太郎"みたいに苗字と名前の区切りとして全角スペースが入ってるものという制約をつけてみました。
制約違反があると、ArgumentExceptionが飛んでくる仕組みになってる。
本当かどうか確認!!
class Program
{
static void Main(string[] args)
{
var p = new Person();
Console.WriteLine(p.Name);
// 苗字と名前の間に全角スペースがあるからOK
p.Name = "田中 太郎";
Console.WriteLine(p.Name);
try
{
// わざと間違えたデータを渡してみる
p.Name = "大田一希";
}
catch (ArgumentException ex)
{
Console.WriteLine(ex.Message);
}
}
}
実行結果
匿名 希望
Name Changed
NewValue: 田中 太郎様
OldValue: 匿名 希望
田中 太郎様
'大田一希' は、プロパティ 'Name' の有効な値ではありません。
ちゃんとバリデーションが走ってる。
ちなみに、コールバック系を全部しかけたときの呼ばれる順番は…
- ValidateValueCallback もとの値の妥当性検証
- CoerceValueCallback 値の強制
- ValidateValueCallback 2での結果に対して妥当性検証
- PropertyChangedCallback プロパティが変更したことへの通知
になってた。