[WPF] WPF入門 ~依存関係プロパティ ②~

投稿日 : 2009年3月19日 17:08
依存関係プロパティの2回目です。
前回では依存関係プロパティの説明、実装方法を取り上げました。
今回はそこから掘り下げて「依存関係プロパティでは何が出来るのか?」を取り上げたいと思います。
それでは復習として、依存関係プロパティの定義・登録部分を見てみましょう。

- VB -
Public Class ConcreteDependencyObject
    Inherits DependencyObject
    Public Shared ReadOnly CaptionProperty As DependencyProperty
 
    Shared Sub New()
        CaptionProperty = DependencyProperty.Register( _
          "Caption", _
          GetType(String), _
          GetType(ConcreteDependencyObject), _
          New PropertyMetadata())
    End Sub
 
    Public Property Caption() As String
        Get
            Return CStr(GetValue(ConcreteDependencyObject.CaptionProperty))
        End Get
        Set(ByVal value As String)
            SetValue(ConcreteDependencyObject.CaptionProperty, value)
        End Set
    End Property
End Class
- C# -
public class ConcreteDependencyObject : DependencyObject
{
    public static DependencyProperty CaptionProperty;
 
    static ConcreteDependencyObject()
    {
        CaptionProperty = DependencyProperty.Register(
            "Caption",
            typeof(string),
            typeof(ConcreteDependencyObject),
            new PropertyMetadata());
    }
 
    public string Caption
    {
        get { return GetValue(ConcreteDependencyObject.CaptionProperty) as string; }
        set { SetValue(ConcreteDependencyObject.CaptionProperty, value); }
    }
}

依存関係プロパティは上記例のように定義・実装を行います。

まずは依存関係プロパティを理解する上で、WPFプロパティシステムに登録しているメソッド、Registerメソッドの第3引数に渡している、メタデータ(PropertyMetadataクラス)についてまず理解する必要があります。
依存関係プロパティでのメタデータはMSDNではこのように説明されています。

登録時の条件など、特定の型に適用されるときの依存関係プロパティの動作を定義します。 Windows Presentation Foundation (WPF) プロパティ システムには、リフレクションや一般的な 共通言語ランタイム (CLR) 特性から得られる以上の詳細なプロパティ情報を提供するメタデータ報告システムが含まれています。依存関係プロパティのメタデータは、依存関係プロパティを定義するクラスで個別に割り当てることも、依存関係プロパティを別のクラスに追加する際に変更することもできます。また、依存関係プロパティをその定義元の基本クラスから継承するすべての派生クラスで明確にオーバーライドすることもできます。

上記説明の通りに、依存関係プロパティの動作の定義や、登録条件などを指定する事が出来ます。
それを定義する為にPropertyMetadataクラスが使用されるといった仕組みになっています。
まずは簡単なPropertyMetadataクラスの使用例を見てみたいと思います。

プロパティの既定値の設定
プロパティの既定値とは「プロパティ値が何も設定されていない場合の値」になります。
従来のWindows Formでのコントロールの既定値の設定ではDefaultValueAttributeクラスが使用されていました。
WPFではPropertyMetadataクラスを使用して既定値を設定します。コード例を見てみましょう。

- VB -
Public Class ConcreteDependencyObject
    Inherits DependencyObject
    Public Shared ReadOnly CaptionProperty As DependencyProperty
 
    Shared Sub New()
        Dim metaData As New PropertyMetadata("DefaultValue")
        'または
        'Dim metaData As New PropertyMetadata()
        'metaData.DefaultValue = "DefaultValue"
 
        CaptionProperty = DependencyProperty.Register("Caption", GetType(String), GetType(ConcreteDependencyObject), metaData)
    End Sub
End Class
- C# -
public class ConcreteDependencyObject : DependencyObject
{
    public static DependencyProperty CaptionProperty;
 
    static ConcreteDependencyObject()
    {
        PropertyMetadata metaData = new PropertyMetadata("DefaultValue");
        //または
        //PropertyMetadata metaData = new PropertyMetadata();
        //metaData.DefaultValue = "DefaultValue";
 
        CaptionProperty = DependencyProperty.Register("Caption", typeof(string), typeof(ConcreteDependencyObject), metaData);
    }
}

上記のようにPropertyMetadataクラスのコンストラクタを使用するか、DefaultValueプロパティを使用する事で既定値を設定する事が出来ます。
ここで既定値について注意しなければなりません。それは既定値と初期値は意味が異なる点です。これはWPFに限った事ではありませんが、初期値とは一般的にクラスのコンストラクタなどで初めに設定する値の事を指します。既定値とはクラスのインスタンシングに関わらず、クラスのメタなデータが設定されている値を指します。

依存関係プロパティ値の強制
依存関係プロパティではプロパティ値の強制を行う事が出来ます。
プロパティ値の強制を行うかの判断を実行するコールバック用メソッドを依存関係プロパティのメタデータに設定する事が出来ます。
まずはコード例を見てみましょう。

- VB -
Public Class ConcreteDependencyObject
    Inherits DependencyObject
    Public Shared ReadOnly CaptionProperty As DependencyProperty
 
    Shared Sub New()
        Dim metaData As New PropertyMetadata(New CoerceValueCallback(AddressOf OnCoerceValueCallBack))
        'または
        'Dim metaData As New PropertyMetadata()
        'metaData.CoerceValueCallback = New CoerceValueCallback(AddressOf OnCoerceValueCallBack)
 
        CaptionProperty = DependencyProperty.Register("Caption", GetType(String), GetType(ConcreteDependencyObject), metaData)
    End Sub
 
    Private Shared Function OnCoerceValueCallBack(ByVal d As DependencyObject, ByVal value As Object) As Object
        If value.ToString() = "ChangeValue" Then Return value
        Return "DefaultValue"
    End Function
End Class
- C# -

public class ConcreteDependencyObject : DependencyObject
{
    public static DependencyProperty CaptionProperty;
 
    static ConcreteDependencyObject()
    {
        PropertyMetadata metaData = new PropertyMetadata(new CoerceValueCallback(OnCoerceValueCallBack));
        //または
        //PropertyMetadata metaData = new PropertyMetadata();
        //metaData.CoerceValueCallback = new CoerceValueCallback(OnCoerceValueCallBack);
 
        CaptionProperty = DependencyProperty.Register("Caption", typeof(string), typeof(ConcreteDependencyObject), metaData);
    }
 
    private static object OnCoerceValueCallBack(DependencyObject d, object value)
    {
        if (value.ToString() == "ChangeValue") return value;
        return "DefaultValue";
    }
}

上記例ではCaptionプロパティ値が再評価されたり強制が明示的に要求されたりした場合に、Captionプロパティ値の値の強制判断を行う為に使用されるコールバック設定例です。
今回の強制可否の判断ロジックに特に意味はありませんが、Captionプロパティ値に「"ChangeValue"」が設定されない限りCaptionプロパティ値は「"DefaultValue"」を強制するような物になってます。
このように依存関係プロパティ値の強制を行う場合はCoerceValueCallBackを使用します。

依存関係プロパティ値変更時コールバック
依存関係プロパティではプロパティ値が変更された場合に通知を受ける事が出来ます。
プロパティ値の変更時にコールバック用メソッドを依存関係プロパティのメタデータに設定する事が出来ます。
コード例を見てみましょう。

- VB -
Public Class ConcreteDependencyObject
    Inherits DependencyObject
    Public Shared ReadOnly CaptionProperty As DependencyProperty
 
    Shared Sub New()
        Dim metaData As New PropertyMetadata(New PropertyChangedCallback(AddressOf OnPropertyChangedCallback))
        'または
        'Dim metaData As New PropertyMetadata()
        'metaData.CoerceValueCallback = New PropertyChangedCallback(AddressOf OnPropertyChangedCallback)
 
        CaptionProperty = DependencyProperty.Register("Caption", GetType(String), GetType(ConcreteDependencyObject), metaData)
    End Sub
 
    Private Shared Sub OnPropertyChangedCallback(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
        Dim oldValue As String = CStr(e.OldValue)
        Dim newValue As String = CStr(e.NewValue)
        Dim dp As DependencyProperty = e.Property
    End Sub
End Class
- C# -
public class ConcreteDependencyObject : DependencyObject
{
    public static DependencyProperty CaptionProperty;
 
    static ConcreteDependencyObject()
    {
        PropertyMetadata metaData = new PropertyMetadata(new PropertyChangedCallback(OnPropertyChangedCallback));
        //または
        //PropertyMetadata metaData = new PropertyMetadata();
        //metaData.CoerceValueCallback = new PropertyChangedCallback(OnCoerceValueCallback);
 
        CaptionProperty = DependencyProperty.Register("Caption", typeof(string), typeof(ConcreteDependencyObject), metaData);
    }
 
    private static void OnPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        string oldValue = (string)e.OldValue;
        string newValue = (string)e.NewValue;
        DependencyProperty dp = e.Property;
    }
}

プロパティ値変更時コールバックは、ルーティングイベントの発行を行ったりする事が良くあります。
ルーティングイベントはまだ取り上げてないので、今回はプロパティ値変更時コールバックでメソッドの第2引数のDependencyPropertyChangedEventArgsより「値の変更前の値」「新たに設定される値」「変更された依存関係プロパティ」の取得が行える事のみを取り上げました。

メタデータの種類
依存関係プロパティではプロパティ特性を格納するメタデータを指定する事が出来ると今までで説明しました。
このメタデータには幾つか種類があります。今まではPropertyMetadataクラスを使用していました。このPropertyMetadataクラスはメタデータの基本クラスとなってます。
PropertyMetadataクラスは上記で挙げた「プロパティの既定値の設定」「依存関係プロパティ値の強制」「依存関係プロパティ値変更時のコールバック」のみが行えますが、その他の細かい依存関係プロパティの設定が行えません。
その他の設定を行えるメタデータとしてFrameworkPropertyMetadataクラスが提供されています。
独自の依存関係プロパティを実装する場合は、通常FrameworkPropertyMetadataクラスを使用するとされています。それはWPFで提供されている既存の依存関係プロパティではFrameworkPropertyMetadataクラスが使用されている為です。
また、FrameworkPropertyMetadataクラスを使用するとシステムの動作(プロパティの継承、データバインディング、レイアウトなど)を簡易的に制御する事が出来ます。
今回は詳しく取り上げませんが、簡単な例を見てみましょう。

- VB -
Dim metaData As New FrameworkPropertyMetadata()
'データバインドの既定値が双方向バインドであるようにする
metaData.BindsTwoWayByDefault = True
'依存関係プロパティ値が継承可能であるようにする
metaData.Inherits = True
'ここで登録する依存関係プロパティはアニメーション無効
metaData.IsAnimationProhibited = True
 
CaptionProperty = DependencyProperty.Register("Caption", GetType(String), GetType(ConcreteDependencyObject), metaData)
- C# -
FrameworkPropertyMetadata metaData = new FrameworkPropertyMetadata();
//データバインドの既定値が双方向バインドであるようにする
metaData.BindsTwoWayByDefault = true;
//依存関係プロパティ値が継承可能であるようにする
metaData.Inherits = true;
//ここで登録する依存関係プロパティはアニメーション無効
metaData.IsAnimationProhibited = true;
 
CaptionProperty = DependencyProperty.Register("Caption", typeof(string), typeof(ConcreteDependencyObject), metaData);

このようにPropertyMetadataクラスでは行えなかった設定が行える為、FrameworkPropertyMetadataクラスを使用するようにしましょう。

依存関係プロパティの有効値検証コールバック
依存関係プロパティでは、値の有効地検証用のコールバックも行えます。
これは今まで見てきたメタデータを使用するのではなく、依存関係プロパティ登録時のRegisterメソッドの引数としてコールバックメソッドを指定します。
では簡単な例を見てみましょう。

- VB -
Public Class ConcreteDependencyObject
    Inherits DependencyObject
    Public Shared ReadOnly CaptionProperty As DependencyProperty
 
    Shared Sub New()
        Dim metaData As New FrameworkPropertyMetadata()
 
        CaptionProperty = DependencyProperty.Register( _
        "Caption", GetType(String), GetType(ConcreteDependencyObject), metaData, New ValidateValueCallback(AddressOf OnValidateValueCallback))
    End Sub
 
    Private Shared Function OnValidateValueCallback(ByVal value As Object) As Boolean
        If value.ToString() = "DengerousValue" Then Return False
        Return True
    End Function
End Class
- C# -
public class ConcreteDependencyObject : DependencyObject
{
    public static DependencyProperty CaptionProperty;
 
    static ConcreteDependencyObject()
    {
        FrameworkPropertyMetadata metaData = new FrameworkPropertyMetadata();
 
        CaptionProperty = DependencyProperty.Register(
            "Caption", typeof(string), typeof(ConcreteDependencyObject), metaData,new ValidateValueCallback(OnValidateValueCallback));
    }
 
    private static bool OnValidateValueCallback(object value)
    {
        if (value.ToString() == "DengerousValue") return false;
        return true;
    }
}

上記例ではRegisterメソッドの第5引数としてValidateValueCallbackデリゲートを指定しています。
この検証コールバックを使用すると、設定された値が有効でない場合に例外が発生します。このコールバックを使用し値の検証を行い、不正なプロパティの設定を防ぐ事が出来ます。

依存関係プロパティの優先順位
依存関係プロパティには値の優先順位があります。依存関係プロパティはリソースやアニメーション、テンプレートやスタイル等の様々な要素から設定される事があります。
その上で依存関係プロパティには優先順位が定められています。
今回はこの優先順位については取り上げません。それはその様々な要素についてまだ取り上げていない為です。
その様々な要素を取り上げた後に改めて依存関係プロパティの優先順位について取り上げたいと思います。



今回は依存関係プロパティで何が行えるか?について取り上げました。
次回は「読み取り専用の依存関係プロパティの実装」と「コレクション型依存関係プロパティ」を取り上げたいと思います。

to be continue・・・

フィードバック

# [WPF] WPF入門 ~依存関係プロパティ ③~

2009/03/20 13:17 by .NETな日々
[WPF] WPF入門 ~依存関係プロパティ ③~

# re: [WPF] WPF入門 ~依存関係プロパティ ②~

2010/10/03 8:43 by 三輪の牛
 依存性プロパティの丁寧な解説ありがとうございます。
 TYPOを見つけましたのでお知らせします。

 有効地検証用→有効値検証用

# re: [WPF] WPF入門 ~依存関係プロパティ ②~

2012/07/19 9:09 by hoge
まずは依存関係プロパティを理解する上で、WPFプロパティシステムに登録しているメソッド、Registerメソッドの第3引数に渡している、メタデータ(PropertyMetadataクラス)についてまず理解する必要があります。
→第4引数では?
コメントの入力
タイトル
名前
Url
コメント