WPFのButtonからみるContentControlの特徴について

コンピュータ

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 は、見た目上は

<Button>
<TextBlock Text="OK"/>
</Button>

と書いていますが、内部的には C# のメソッド呼び出しに変換されています。

そのときに使われるのが、

  • AddChild(object value)

  • AddText(string text)

です。


AddChild(object)

void AddChild(object value)

これは 子要素(オブジェクト)を追加するためのメソッド です。

<Button>
<TextBlock Text="OK"/>
</Button>

↑ この XAML は、概念的には次のような処理に変換されます。

var button = new Button();
button.AddChild(new TextBlock { Text = "OK" });

つまり、

  • <TextBlock /> のような 要素ノード

  • StackPanel や Grid などの コントロール・パネル

これらはすべて AddChild(object) 経由で渡されます。


AddText(string)

void AddText(string text)

こちらは XAML内に直接書かれた文字列 を処理するためのメソッドです。

<Button>OK</Button>

この場合、XAML パーサは次のようなイメージで処理します。

var button = new Button();
button.AddText("OK");

結果として、Button の Content には "OK" という文字列が設定されます。


なぜ AddText が必要なのか

XAML では、

<Button>OK</Button>

<Button Content="OK"/>

ほぼ同じ意味 を持ちます。

この「要素の中に直接書いた文字」を受け取るために
AddText(string) が用意されています。

もし AddText が無ければ、

  • XAML の可読性が大きく下がる

  • 文字列専用の特別構文が必要になる

という問題が出てきます。


ContentControl との関係

ContentControl は 「1つだけ Content を持つ」 コントロールです。

そのため、

  • AddChild(object)

  • AddText(string)

どちらが呼ばれても、最終的には

Content = value;

という形で 単一の Content に集約 されます。

※ もし複数回 AddChild / AddText が呼ばれた場合は例外になります
(ContentControl は ItemsControl ではないため)


StackPanel との対比(ここが美味しい)

<StackPanel>
<TextBlock Text="A"/>
<TextBlock Text="B"/>
</StackPanel>

StackPanel は 複数の子要素を持てる ので、

  • AddChild() が何度も呼ばれる

  • 内部の Children コレクションに追加される

一方 Button(ContentControl)は、

  • AddChild は1回だけ

  • 中に複数置きたいなら StackPanel を噛ませる

バインド可能なプロパティとは

WPF において バインド可能なプロパティ とは、
XAML からデータバインディングの対象として利用できるプロパティ を指します。

WPF のコントロールでは、これらのプロパティは原則として
DependencyProperty(依存関係プロパティ) として定義されています。

[System.ComponentModel.Bindable(true)]
public object Content { get; set; }

このように [Bindable(true)] 属性が付与されているプロパティは、
バインディング用途として公開されている ことを意味します。


なぜ DependencyProperty が使われるのか

WPF のデータバインディングは、単なる値の代入ではなく、

  • 値の変更通知

  • スタイルやテンプレートとの共存

  • 優先順位(ローカル値、スタイル、アニメーションなど)

  • ライフサイクル管理

といった仕組みを前提に設計されています。

これらを支えているのが DependencyProperty です。

そのため、WPF のコントロールにおいて
バインディング対象として想定されているプロパティは、
DependencyProperty として実装されています。


Bindable(true) の役割

Bindable(true) 属性は、

このプロパティは、XAML からバインディング対象として使用してよい

という 利用者向けの目印 です。

厳密には、バインディングの仕組み自体は DependencyProperty によって実現されていますが、
使う側の視点では「Bindable(true) が付いているかどうか」
判断基準にして問題ありません。

コメント