連載
» 2010年08月03日 00時00分 公開

連載:WPF入門:第3回 XAMLコードから生成されるプログラム・コードを理解する ―― XAML(2): WPF固有機能の基礎 ―― (3/4)

[岩永信之(http://ufcpp.net),著]

■依存関係プロパティ

 ここまでにも名前だけはたびたび出てきているが、WPFでは「依存関係プロパティ(dependency property)」という、通常のプロパティ(区別のために、WPFの文脈においては「CLRプロパティ」と呼ぶことがある)とは異なる「値の保持機構」を持っている。

 依存関係プロパティは以下のような用途を想定して作られている(Figure 3)。

  • 包含継承: 親要素で設定した値をそのまま継承して使うための機構。
  • リソース: 1カ所で定義したオブジェクトを複数カ所から参照するための、リソースの定義/参照機構。
  • スタイル: HTMLでいうところのCSSのようなスタイル設定機構。
  • データ・バインディング: モデルとビューの間など、異なるオブジェクト間で値を結び付ける(一方での値の変更を他方に通知し、即座に反映させる)機構。
Figure 3: 依存関係プロパティの用途

 これらは、いずれも「ほかの要素の値に依存してプロパティの値を決定する機構」といえる。依存関係プロパティという名前はここから来ている。

 リソース、スタイル、および、データ・バインディングに関する詳細は次回以降で説明する。今回は、依存関係プロパティの定義および利用方法について説明していく(包含継承の説明を含む)。

依存関係プロパティの定義

 まずList 5に、依存関係プロパティを定義するクラスの最低限の実装例を示す。

public class Point : DependencyObject
{
  public int X
  {
    get { return (int)GetValue(XProperty); }
    set { SetValue(XProperty, value); }
  }
 
  public static readonly DependencyProperty XProperty =
    DependencyProperty.Register(
      "X", typeof(int), typeof(Point),
      new UIPropertyMetadata(0));
}

Public Class Point
  Inherits DependencyObject

  Public Property X() As Integer
    Get
      Return CType(GetValue(XProperty), Integer)
    End Get
    Set(ByVal value As Integer)
      SetValue(XProperty, value)
    End Set
  End Property

  Public Shared ReadOnly XProperty As DependencyProperty = _
    DependencyProperty.Register( _
      "X", GetType(Integer), GetType(Point),
      New UIPropertyMetadata(0))
End Class

List 5: 依存関係プロパティを定義する最低限のクラス定義(上:C#、下:VB)

 依存関係プロパティの定義には、DependencyPropertyクラス(System.Windows名前空間)を用いる。ただし、DependencyPropertyクラスのインスタンス自身は、プロパティそのものというよりは辞書のキーのようなもので、実際に値を保持するのはDependencyObjectクラス(System.Windows名前空間)(の子クラス)になる。

 依存関係プロパティは以下のようにして定義する(Figure 4)。

  • (辞書のキーとして使われる)依存関係プロパティの静的フィールド名は「プロパティ名+Property」とする
  • DependencyProperty.Registerメソッドを用いて、WPFのフレームワークに登録する
  • DependencyProperty.Registerメソッドの引数には、プロパティ名、プロパティの型、プロパティを定義する型、および、メタデータ(後述)を与える
Figure 4: 依存関係プロパティの定義(C#)

 実際の値の読み書きはDependencyObjectクラスのSetValue/GetValueメソッドを通して行う。また、内部的にSetValue/GetValueメソッドを呼び出すだけのCLRプロパティ(この例の場合、Xプロパティ)も定義しておく。ただし、このCLRプロパティ内ではSetValue/GetValueメソッド呼び出し以外の処理を行ってはならない。依存関係プロパティは、必ずしもこのCLRプロパティを通して呼ばれるわけではなく、データ・バインディングなどを行うとSetValue/GetValueメソッドが直接呼び出される場合があるため、CLRプロパティ内に記述した処理は行われないことがある。

添付プロパティの場合

 依存関係プロパティの仕組みは、添付プロパティを実現するためにも利用される。依存関係プロパティを添付プロパティとして利用したい場合には、List 6に示すように、Registerメソッドの代わりにRegisterAttachedメソッドを用いた登録を行う。

public class MyCanvas
{
  public static int GetX(DependencyObject obj)
  {
    return (int)obj.GetValue(XProperty);
  }
  public static void SetX(DependencyObject obj, int value)
  {
    obj.SetValue(XProperty, value);
  }
 
  public static readonly DependencyProperty XProperty =
    DependencyProperty.RegisterAttached(
      "X", typeof(int), typeof(MyCanvas),
      new UIPropertyMetadata(0));
}

Public Class MyCanvas

  Public Shared Function GetX(ByVal obj As DependencyObject) As Integer
    Return CType(obj.GetValue(XProperty), Integer)
  End Function

  Public Shared Sub SetX(ByVal obj As DependencyObject, ByVal value As Integer)
    obj.SetValue(XProperty, value)
  End Function

  Public Shared ReadOnly XProperty As DependencyProperty = _
    DependencyProperty.RegisterAttached( _
      "X", GetType(Integer), GetType(MyCanvas),
      New UIPropertyMetadata(0))

End Class

List 6: 添付プロパティとして利用する場合の依存関係プロパティの登録(上:C#、下:VB)

 また、添付プロパティの場合には、CLRプロパティの代わりに「Setプロパティ名」「Getプロパティ名」(上記のコードの例では「GetX」「SetX」)という名前の静的メソッドを定義しておく。通常の依存関係プロパティの場合と同様、これらのSet/Getメソッドの内部では、DependencyObject.SetValue/DependencyObject.GetValueメソッド呼び出し以外の処理を行ってはならない。

オーナー・クラスの追加

 ほかのクラスで定義された既存の依存関係プロパティを自作のクラスでも利用したい場合、DependencyProperty.AddOwnerメソッドを用いることで、WPFに登録情報を追加できる。例えば、標準のTextBoxクラスで定義されいてるText依存関係プロパティを、自作のMyControlクラスでも利用したい場合、List 7に示すような記述を行う。

using System.Windows;
using System.Windows.Controls;
 
public partial class MyControl : UserControl
{
  public static DependencyProperty TextProperty =
    TextBox.TextProperty.AddOwner(typeof(MyControl));
}

Public Class MyControl
  Public Shared TextProperty As DependencyProperty =
    TextBox.TextProperty.AddOwner(GetType(MyControl))
End Class

List 7: 依存関係プロパティのオーナー・クラスの追加(上:C#、下:VB)

 この仕組みは例えば、ComboBoxクラス(System.Windows.Controls名前空間)などのIsSelected依存関係プロパティなどで利用されている。ComboBoxクラスの子要素となるComboBoxItemクラスに対してIsSelected依存関係プロパティを設定する場合、本来なら、「<ComboBoxItem ComboBox.IsSelected="true"/>」というように、添付プロパティとして設定する必要があるが、IsSelected依存関係プロパティはオーナー・クラスの追加の仕組みを使ってComboBoxItemクラス側にも定義されていて、「<ComboBoxItem IsSelected="true"/>」と書くことができる。

依存関係プロパティの意義

 WPFが、CLRプロパティではなく、わざわざ依存関係プロパティのような仕組みを利用する意義は、以下のような点にある。

(1)パフォーマンス

 依存関係プロパティは一種の辞書構造になっているが、これはデータ・バインディングのパフォーマンスの向上や、添付プロパティの実現のために利用されている。

 CLRプロパティを用いてデータ・バインディングのような仕組みを実現するためには、リフレクションに頼ることになる。一般に、リフレクションの利用は著しくパフォーマンスを低下させることがある。プロパティの値を辞書的に持てば、リフレクションの利用を避けることができ、パフォーマンスの向上が見込める。

 また、辞書的に値を持つことで、添付プロパティ(=要素自身ではなく、親要素で利用する値を保持する機構)も実現可能である。

(2)メタデータの保持

 依存関係プロパティは、次節で説明するようなメタデータを持つことができる。

メタデータ

 メタデータはPropertyMetadataクラス(System.Windows名前空間)、もしくは、その子クラスのインスタンスとして定義する。利用場面に応じて以下の3つのうちのいずれかを用いる(いずれもSystem.Windows名前空間に所属するクラス)。

  • PropertyMetadataクラス
  • UIPropertyMetadataクラス
  • FrameworkPropertyMetadataクラス

 PropertyMetadataクラスは、WPFに限らず幅広い用途で利用することを想定したもので、メタデータとして最低限の情報を持っている。PropertyMetadataクラスの持っているプロパティのうち、主要なものを以下に示す。

  • DefaultValue: 依存関係プロパティのデフォルト値。
  • CoerceValueCallback: (このプロパティは)依存関係プロパティの値を変更する前に呼び出され、値が適切かどうかを判定し、適切でなければ適切な値に修正するためのコールバック・メソッド。このような挙動を「値の強制(coercion)」と呼ぶ。
  • PropertyChangedCallback: (このプロパティは)依存関係プロパティの値が変更された際に呼び出されるコールバック・メソッド。

 UIPropertyMetadataクラスは、UI要素向けのメタデータで、PropertyMetadataクラスの持つ情報に加えて、アニメーションの可否を表すIsAnimationProhibitedプロパティを持つ。

 FrameworkPropertyMetadataクラスは、WPF用のメタデータで、UIPropertyMetadataクラスの持つ情報に加えて、WPF固有の豊富な機能向けの情報を持っている。FrameworkPropertyMetadataクラスのプロパティのうち、主要なものを以下に示す。

  • Inherits: 値の包含継承が行われるかどうか。
  • IsDataBindingAllowed: データ・バインディングに対応しているかどうか。
  • BindsTwoWayByDefault: データ・バインディングの既定の挙動が双方向であることを示す。

値の優先順位

 依存関係プロパティを利用する際、包含継承やスタイルなどを併用すると、複数の個所から値が設定されることになる。この場合、値の設定元には優先順位があり、順位の高いものが依存関係プロパティの値に反映される。優先順位は以下のとおりである(上に行くほど優先度が高い)。

  1. PropertyMetadataクラスのCoerceValueCallbackプロパティに設定したコールバック・メソッドによる値の強制
  2. アニメーションによる動的な値の更新
  3. ローカル値(=要素に対して直接設定した値)
  4. TemplatedParent(=テンプレート親。要素がXAMLのコントロール・テンプレート機能を用いて作られた場合に設定される、要素の作成元)からの包含継承
  5. スタイル内のトリガー(=マウス・オーバーなどのイベントをきっかけとして起きる値の変化)
  6. (XAMLの)コントロール・テンプレート内のトリガー
  7. スタイル中で設定される値
  8. テーマ・スタイル(=アプリケーション全体に適用されるスタイル)中で設定される値
  9. 親要素からの包含継承
  10. メタデータで設定された規定値

 最後に、ルーティング・イベントについて解説する。

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。