WPF は XAML とデータバインディングを中核に設計されたフレームワークです。
そのため、特別な理由がない限り、フレームワークの流儀に逆らわないのが大人の対応と言えるでしょう。
XAML は、View オブジェクトを 静的な宣言として表現するための仕組みです。
このとき生成される View オブジェクトには、明確で一貫したライフサイクルが存在します。
バインディングされるデータソースも、この View のライフサイクルと整合していることが、
最も自然で理解しやすい構成になります。
WPF では、その前提のもとで、
-
XAML による View の宣言
-
DependencyProperty を通じたバインディング
という設計が徹底されています。
CLR プロパティを用いた例外的な構成も理論上は可能ですが、
それは WPF が用意した仕組みの外側で振る舞うことになり、
保守性や可読性を損ないやすくなります。
その点を踏まえたうえで、
次に WPF の Button がなぜ ContentControl なのか を見ていきます。
Buttonサンプル
WPFで Button を XAML で定義すると、次のようになります。
<Button Content="OK"
Width="120"
Height="60" />

文字を表示するだけであれば、WinForms の Button でも同様のことができます。
しかし、WPF の Button では 文字以外の要素も簡単に表示できる という大きな特徴があります。
例えば、次のように Button の中に画像を配置することができます。
<Button Height="128" Width="128">
<StackPanel>
<TextBlock Text="OK" />
<Image Source="J:\csharp\WpfCB\ButtonSample\img.png" Height="64" Width="64"/>
</StackPanel>
</Button>

この例では、Button の中に StackPanel を配置し、その内部に
TextBlock と Image の2つのコントロールを配置しています。
Content プロパティの役割
Button には Content というプロパティがあります。
最初の例では、この Content に文字列 “OK” を設定していました。
<Button Content="OK" />
一方、2つ目の例では Content を明示的に指定せず、
Button の内部に直接コントロールを記述しています。
これは、
Button の内部に記述した要素が、そのまま Content として扱われる
という WPF の仕様によるものです。
つまり、Button の Content には文字列だけでなく、
任意のコントロールやレイアウト要素を配置できる ということになります。
ContentControl という考え方
このように、
「内部に1つの Content を持ち、その中身として任意の要素を配置できる」
という性質を持つコントロールを、WPF では ContentControl と呼びます。
Button はその代表例のひとつです。
-
文字列を表示してもよい
-
画像を表示してもよい
-
レイアウトパネルを配置してもよい
Button 自体は 「中身が何であるか」を意識しない という設計になっています。
WPFらしさが現れるポイント
WinForms の Button は、基本的に
「文字を表示してクリックを受け取る部品」でした。
一方、WPF の Button は、
-
表示内容は Content に委ねる
-
見た目はテンプレートで決まる
-
中身は UI 要素そのもの
という、UI を部品として組み立てる発想 で設計されています。
Button を通して ContentControl を理解すると、
WPF が「見た目と構造を分離するフレームワーク」であることが
実感しやすくなると思います。
ContentControl.AddChild / AddText の正体
WPF の XAML は、見た目上は
と書いていますが、内部的には C# のメソッド呼び出しに変換されています。
そのときに使われるのが、
-
AddChild(object value) -
AddText(string text)
です。
AddChild(object)
これは 子要素(オブジェクト)を追加するためのメソッド です。
↑ この XAML は、概念的には次のような処理に変換されます。
つまり、
-
<TextBlock />のような 要素ノード -
StackPanel や Grid などの コントロール・パネル
これらはすべて AddChild(object) 経由で渡されます。
AddText(string)
こちらは XAML内に直接書かれた文字列 を処理するためのメソッドです。
この場合、XAML パーサは次のようなイメージで処理します。
結果として、Button の Content には "OK" という文字列が設定されます。
なぜ AddText が必要なのか
XAML では、
と
が ほぼ同じ意味 を持ちます。
この「要素の中に直接書いた文字」を受け取るためにAddText(string) が用意されています。
もし AddText が無ければ、
-
XAML の可読性が大きく下がる
-
文字列専用の特別構文が必要になる
という問題が出てきます。
ContentControl との関係
ContentControl は 「1つだけ Content を持つ」 コントロールです。
そのため、
-
AddChild(object) -
AddText(string)
どちらが呼ばれても、最終的には
という形で 単一の Content に集約 されます。
※ もし複数回 AddChild / AddText が呼ばれた場合は例外になります
(ContentControl は ItemsControl ではないため)
StackPanel との対比(ここが美味しい)
StackPanel は 複数の子要素を持てる ので、
-
AddChild()が何度も呼ばれる -
内部の Children コレクションに追加される
一方 Button(ContentControl)は、
-
AddChild は1回だけ
-
中に複数置きたいなら StackPanel を噛ませる
バインド可能なプロパティとは
WPF において バインド可能なプロパティ とは、
XAML からデータバインディングの対象として利用できるプロパティ を指します。
WPF のコントロールでは、これらのプロパティは原則として
DependencyProperty(依存関係プロパティ) として定義されています。
このように [Bindable(true)] 属性が付与されているプロパティは、
バインディング用途として公開されている ことを意味します。
なぜ DependencyProperty が使われるのか
WPF のデータバインディングは、単なる値の代入ではなく、
-
値の変更通知
-
スタイルやテンプレートとの共存
-
優先順位(ローカル値、スタイル、アニメーションなど)
-
ライフサイクル管理
といった仕組みを前提に設計されています。
これらを支えているのが DependencyProperty です。
そのため、WPF のコントロールにおいて
バインディング対象として想定されているプロパティは、
DependencyProperty として実装されています。
Bindable(true) の役割
Bindable(true) 属性は、
このプロパティは、XAML からバインディング対象として使用してよい
という 利用者向けの目印 です。
厳密には、バインディングの仕組み自体は DependencyProperty によって実現されていますが、
使う側の視点では「Bindable(true) が付いているかどうか」 を
判断基準にして問題ありません。

コメント