View を支える依存関係プロパティ


はじめに

オレオレ解釈の覚書 その11

依存関係プロパティについてです。

本文

依存関係プロパティとは簡単に言えば Xaml 上でバインドできるコントロールのプロパティです。身近なところで言えば TextBox.Text や CheckBox.IsChecked などがこれに当たります。その実体は DependencyProperty という静的なフィールドで、DependencyObject から派生したクラスで定義します。例えば TextBox.Text であれば以下のようになります。(実際の System.Windows.Controls.TextBox とは異なります。あくまでイメージです。)

TextBox
using System.Windows;

namespace TestApp.Views.Controls
{
    public class TextBox : DependencyObject
    {
        // 依存関係プロパティ
        public static readonly DependencyProperty TextProperty =
            DependencyProperty.Register(
                "Text",
                typeof(string),
                typeof(TextBox));

        // CLR ラッパープロパティ
        public string Text
        {
            get => (string) this.GetValue(TextProperty);
            set => this.SetValue(TextProperty, value);
        }
    }
}

依存関係プロパティは DependencyProperty にメタ情報を渡して初期化するだけで、Xaml からバインドするための準備ができてしまいます。ただし、プログラム(C#側)からアクセスする際には、DependencyObject のメソッドを経由する必要があり、通常のプロパティと使用感が合いません。例のようにラッパープロパティを用意してインターフェースに統一感を持たせるのが一般的で、これによりコントロールの利用者は依存関係プロパティの存在を意識せずにアクセスすることができます。

宣伝

上記のようにすれば依存関係プロパティを定義できますが、初期化のくだりは多くの場合で規則的になり、いくつも用意する際は面倒ですし、バグの温床にもなります。拙作ですが、NuGet にて公開中の下記のライブラリでは、初期化の際に要求されるメタ情報を省略することができます。ラッパープロパティを用意することが暗黙のルールであることを利用し、必要な情報を補っています。

TextBox
using Plow.Wpf;
using System.Windows;

namespace TestApp.Views.Controls
{
    public class TextBox : DependencyObject
    {
        // 依存関係プロパティ
        public static readonly DependencyProperty TextProperty =
            DependencyPropertyExtensions.Register();

        // CLR ラッパープロパティ
        public string Text
        {
            get => (string) this.GetValue(TextProperty);
            set => this.SetValue(TextProperty, value);
        }
    }
}

おわりに

ViewModel で登場した変更通知プロパティとは異なり、依存関係プロパティはほとんどのコントロールで必要なものがある程度用意されているため、新たに追加する機会は少ないかもしれません。とはいえ、ユーザーコントロールとしてまとめた際や、既存のコントロールを拡張する場合に登場するため、頭の片隅には置いておきたいですね。

次回はこれを応用した添付プロパティについてまとめます。