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

投稿日 : 2009年3月18日 20:51
前回までWPFにおけるレイアウトシステムとレイアウト用コントロールについて取り上げました。
次にWPFで提供されている基本コントロールについて取り上げようと思いましたが、そのまえにコントロールを使用する上で知っておく必要がある物を先に取り上げようと思います。
  • 依存関係プロパティ
  • 添付プロパティ
  • ルーティングイベント
上記にあげた物を数回に分けて見ていきたいと思います。
今回は依存関係プロパティについてです。
添付プロパティについては以前に「どのように使用するのか」を理解する為に使用方法のみにフォーカスを当てて取り上げましたが、依存関係プロパティととても関係が深い物なので改めて取り上げます。

依存関係プロパティを具体的に理解するのは、WPFで提供されているコントロール群だけを使用する上では特に意識される事がありませんが、業務アプリケーションを構築する上でカスタムコントロールの作成などは多々あり、たとえカスタムコントロールの作成をしない場合でも、依存関係プロパティの詳細を知るのはとても重要だと思った為、あえて取り上げようと思いました。
まずはMSDNの説明を見てみましょう。

WPF では通常、プロパティは共通言語ランタイム (CLR) プロパティとして公開されます。基本的なレベルでは、これらのプロパティと直接対話でき、これらのプロパティが依存関係として実装されることを認識することはありません。ただし、WPF プロパティ システムの一部またはすべての機能を利用できるように、これらの機能に精通しておく必要があります。
依存関係プロパティの目的は、他の入力の値に基づいてプロパティの値を計算する方法を提供することです。他の入力には、テーマやユーザー設定などのシステム プロパティ、データ バインディングやアニメーション/ストーリーボードなどのジャスト イン タイム プロパティ判定機構、リソースやスタイルなどの多目的のテンプレート、要素ツリー内の他の要素との親子のリレーションシップから判断される値などがあります。また、依存関係プロパティを実装して、自己完結型の検証、既定値、他のプロパティに対する変更を監視するコールバック、およびランタイム情報の可能性がある情報に基づいてプロパティ値を強制するシステムを提供できます。既存のプロパティの実際の実装をオーバーライドしたり新しいプロパティを作成したりするのではなく、依存関係プロパティ メタデータをオーバーライドすることによって、派生クラスで既存のプロパティの特定の特性を変更することもできます。

とても難しい説明でありますが、解りやすく掘り下げていきたいと思います。

まず、WPFではWPFプロパティシステムと呼ばれるサービスがあります。WPFプロパティシステムとは以下のサービスをまとめた物になります。
  • 共通言語ランタイム(CLR)プロパティ
  • 依存関係プロパティ
共通言語ランタイム(CLR)プロパティとは、従来からあるメンバ変数をプロパティのGet・Setを通じて使用するプロパティを指しています。
これ以降、共通言語ランタイム(CLR)プロパティを「CLRプロパティ」と呼びます。
また、依存関係プロパティとはCLRプロパティの機能を拡張する為に使用されるサービスとなります。
「CLRプロパティの機能を拡張するサービス」とはどのような事でしょうか?

まず、WPFにおいてCLRプロパティのみの使用ではいくつかの機能が使用出来ません。
  • リソース
  • データバインディング
  • スタイル
  • アニメーション
  • メタデータのオーバーライド
  • プロパティ値の継承
  • WPFデザイナの統合
上記の機能を使用可能にする為、言い方を変えればCLRプロパティの機能を拡張する為に「依存関係プロパティ」がWPFでは導入されました。
どうしてCLRプロパティでは上記機能が使用出来ないかの簡単な例を、従来のWindows Formを使用したデータバインディングの例を見てみたいと思います。

■VB■
Dim textBox1 As New TextBox()
Dim label1 As New Label()
 
Dim binding As New Binding("Text", TextBox1, "Text")
 
label1.DataBindings.Add(binding)

■C#■
Label label1 = new Label();
TextBox textBox1 = new TextBox();
 
Binding binding = new Binding("Text", textBox1, "Text");
 
this.label1.DataBindings.Add(binding);

上記例では、LabelのText プロパティに、TextBoxのTextプロパティをバインドしている、簡単な単一値データバインドの例です。
データバインディングではバインドする対象を「ターゲット(ターゲットコントロール)」、バインドされる側を「データソース(データソースオブジェクト)」と呼びます。
ではBindingクラスのコンストラクタを見てみましょう。

■VB■
Public Class Binding
    Public Sub New(ByVal propertyName As String, ByVal dataSource As Object, ByVal dataMember As String)
    End Sub
End Class

■C#■
public class Binding
{
    public Binding(string propertyName, object dataSource, string dataMember)
    { }
}

Bindingクラスのコンストラクタの引数の詳細は以下のようになっています。
  • ( 第1引数 ) ターゲットのバインド対象のプロパティ名
  • ( 第2引数 ) ターゲットのデータソースとなるオブジェクト
  • ( 第3引数 ) データバインドされるデータソースのメンバ名
ここで注目して頂きたい部分は、Bindingクラスのコンストラクタで渡している第1引数のオブジェクトの型です。
.NET Framework2.0 までのBinding(System.Windows.Forms.Binding)クラスは「文字列でターゲットのバインドプロパティの指定」を行っていました。

次はWPFでのデータバインディングをコードより見てみましょう。
データバインディングについてはまだ取り上げていないので、何故依存関係プロパティを使用しないとデータバインド出来ないのか?の理由だけ解れば現段階ではOKです。

■VB■
Dim label1 As New Label()
Dim textBox1 As New TextBox()
 
Dim binding As New Binding("Text")
binding.Source = textBox1
 
label1.SetBinding(Label.ContentProperty, binding)

■C#■
Label label1 = new Label();
TextBox textBox1 = new TextBox();
 
Binding binding = new Binding("Text");
binding.Source = textBox1;
 
label1.SetBinding(Label.ContentProperty, binding);

LabelクラスはFrameworkElementクラスを継承しています。SetBindingメソッドはFrameworkElementクラスで定義されています。
FremeworkElementクラスのSetBindingメソッドを見てみましょう。

■VB■
Public Class FrameworkElement
    Public Function SetBinding(ByVal dp As DependencyProperty, ByVal binding As BindingBase) As BindingExpressionBase
    End Function
End Class

■C#■
public class FrameworkElement
{
    public BindingExpressionBase SetBinding(DependencyProperty dp, BindingBase binding)
    { }
}

FrameworkElementクラスのSetBindingメソッドの引数の詳細は以下のようになっています。
  • (第1引数) ターゲットのバインドする依存関係プロパティ
  • (第2引数) バインドするバインドソースを設定したBindingクラス
ここでも注目して頂きたい部分は、SetBindingメソッドの第1引数のオブジェクトの型です。
説明から解るように、ターゲットのバインドするプロパティは依存関係プロパティである必要があります。
WPFでCLRプロパティを使用してデータバインディングが出来ない理由は、上記のように依存関係プロパティを背後で使用されている為です。
では、上記SetBindingメソッドの第1引数で使用されている「DependencyProperty」とはなんでしょうか?

SetBindingメソッドの例で解る通りに、依存関係プロパティはDependencyProperty型である事がご理解頂けたかと思います。
DependencyPropertyクラスを提供する(依存関係プロパティを使用可能にする為の)クラスとしてDependencyObjectクラスがあります。
DependencyObjectクラスは依存関係プロパティに関するサービスを提供する重要なクラスです。
独自の依存関係プロパティを作成する為にはDependencyObjectクラスを継承する必要があります。
WPFで提供されている基本コントロールクラス群は全てDependencyObjectクラスの派生クラスであり、その理由から基本コントロール群のみを使用したWPFアプリケーション構築のみしかした事ない場合は依存関係プロパティの背景や必要性が解りづらいのかもしれません。
では基本的な依存関係プロパティの実装例を見てみましょう。

■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
End Class

■C#■
public class ConcreteDependencyObject : DependencyObject
{
    public static DependencyProperty CaptionProperty;
 
    public static ConcreteDependencyObject()
    {
        CaptionProperty = DependencyProperty.Register(
            "Caption",
            typeof(string),
            typeof(ConcreteDependencyObject),
            new PropertyMetadata());
    }
}

上記例からも解かるとおりに、依存関係プロパティの定義方法はCLRプロパティとは異なります。依存関係プロパティは実装から使用可能にする為には幾つかの決まり事があります。
  • Publicでかつ静的なDependencyProperty型の変数を使用する
  • 依存関係プロパティ名のサフィックス(接頭辞)に "Property" を付ける
  • 変数宣言時に初期化するか、静的コンストラクタにて初期化する(今回は静的コンストラクタを使用)
  • DependencyPropertyクラスの静的メソッドのRegisterメソッドを使用し依存関係プロパティを登録する
依存関係プロパティはCLRプロパティのように定義するのではなく、WPFプロパティシステムに対し依存関係プロパティを登録する必要があります。
このWPFプロパティシステムへ登録する事により、CLRプロパティでは使用できなかった機能が使用可能になります。
また、依存関係プロパティの定義・登録自体はDependencyObjectを継承していなくても定義する事は出来ますが、次に話す依存関係プロパティの値の取得・設定を行うにはDependencyObjectを継承している必要があります。
よって事実上、独自の依存関係プロパティの定義はDependencyObjectを継承している必要があるのです。

それでは次に、依存関係プロパティの値の取得・設定の例を見てみましょう。(上記作成したConcreteDependencyObjectクラスを使用)

■VB■
Dim target As New ConcreteDependencyObject()
Dim captionValue As String = CStr(target.GetValue(ConcreteDependencyObject.CaptionProperty))
target.SetValue(ConcreteDependencyObject.CaptionProperty, "CaptionValue")

■C#■
ConcreteDependencyObject target = new ConcreteDependencyObject();
string captionValue = target.GetValue(ConcreteDependencyObject.CaptionProperty) as string;
target.SetValue(ConcreteDependencyObject.CaptionProperty, "CaptionValue");

上記の例の通りに、依存関係プロパティの値の取得・設定にはDependencyObjectクラスで定義されているGetValueメソッドとSetValueメソッドを使用します。
ここでちょっと気になる事があります。今まで取り上げたサンプル等で上記のようなプロパティ値の取得・設定はなかったはずです。
また、XAMLでは「<ConcreteDependencyObject CaptionProperty="CaptionValue" />」のような依存関係プロパティの記述は今までありませんでした。

その答えとして、多くの依存関係プロパティはCLRプロパティをラッパーとして使用している為です。また依存関係プロパティはCLRプロパティラッパーを通じてアクセスする事が推奨されています。
では、CLRプロパティをラッパーとして依存関係プロパティの値の取得・設定する例を見てみましょう。

■VB■
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

■C#■
public string Caption
{
    get { return GetValue(ConcreteDependencyObject.CaptionProperty) as string; }
    set { SetValue(ConcreteDependencyObject.CaptionProperty, value); }
}

このようにCLRプロパティラッパーを使用すると、依存関係プロパティの値の取得・設定も簡素化され、あたかもCLRプロパティを使用しているようになります。

■VB■
Dim target As New ConcreteDependencyObject()
Dim captionValue As String = target.Caption
target.Caption = "CaptionValue"

■C#■
ConcreteDependencyObject target = new ConcreteDependencyObject();
string captionValue = target.Caption;
target.Caption = "CaptionValue";

依存関係プロパティの実装は上記のようになります。
今回は依存関係プロパティの説明、実装方法までを取り上げてみました。
今回だけでは「結局依存関係プロパティの背景は?」の部分まで取り上げれませんでしたので、次回以降では、依存関係プロパティで出来る事、また名前の如く「どのように他コントロールと依存しあうのか」といった部分を取り上げたいと思います。

to be continue・・・

フィードバック

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

2009/03/19 18:24 by .NETな日々
[WPF] WPF入門 ~依存関係プロパティ ②~

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

2009/08/05 16:58 by namedesu
接尾辞desu
コメントの入力
タイトル
名前
Url
コメント