連載
» 2009年07月13日 00時00分 公開

連載 WPF/Silverlight UIフレームワーク入門:第4回 “見た目”を決めるコントロール・テンプレート (2/3)

[八巻雄哉,グレープシティ株式会社]

TemplateBindingマークアップ拡張機能

 作成したコントロール・テンプレートを1つのコントロールでしか使用しないのであれば、表示文字列を含めたすべての外観が固定的であっても問題はないのだが、それは極めてまれなケースだろう。

 多くの場合、上記のコード例のようにリソースを利用して複数のコントロールでコントロール・テンプレートを共有できれば便利である。そしてその際には、Contentプロパティのようにコントロールごとに設定値が異なるプロパティは、コントロール・テンプレートによって表示される外観にも個別に反映されなければならない。

 このような動作は、マークアップ拡張機能である「TemplateBinding」を使用し、コントロールのプロパティと、コントロール・テンプレート内の要素のプロパティとをバインドしてやることで実現できる。

 以下のコードは、コントロール・テンプレート内のTextBlock要素のTextプロパティとBorder要素のBackgroundプロパティを、それぞれButtonコントロールのContentプロパティとBackgroundプロパティにバインドさせたものである。

<StackPanel>
  <StackPanel.Resources>
    <ControlTemplate x:Key="ButtonTemplate"  TargetType="Button">
      <Border Background="{TemplateBinding Background}"
        CornerRadius="30" Padding="10">
        <TextBlock Text="{TemplateBinding Content}"
          VerticalAlignment="Center" HorizontalAlignment="Center"/>
      </Border>
    </ControlTemplate>
  </StackPanel.Resources>

  <Button Content="Button1" Background="LightBlue"
    Template="{StaticResource ButtonTemplate}" Margin="30"/>
  <Button Content="Button2" Background="LightPink"
    Template="{StaticResource ButtonTemplate}" Margin="30"/>

</StackPanel>

TemplateBindingを使って、コントロールのプロパティとコントロール・テンプレート内の要素のプロパティをバインドさせたコード例(XAML)

 このようなコードにより、それぞれのButtonコントロールのContentプロパティとBackgroundプロパティの設定値が、コントロール・テンプレートによる外観に反映されるようになる。

TemplateBindingの使用によりButtonコントロールのContentプロパティとBackgroundプロパティの設定値が反映されている画面

 TemplateBindingマークアップ拡張機能は、通常のBindingマークアップ拡張機能のバインディング・ソースがテンプレートの適用対象となったものである。そのため、Bindingクラス(System.Windows.Data名前空間)が持つRelativeSourceという、バインディング・ソースに対して相対的な位置を指定するプロパティを用いることにより、同様の動作実現できる(なお、BindingクラスのRelativeSourceプロパティ、およびRelativeSourceマークアップ拡張機能は、Silverlight 2ではサポートされていなかったが、Silverlight 3 Beta 1で追加された)。

 上記のコントロール・テンプレート部分を下記のコードに置き換えた場合でも、動作はまったく同様となる。なお、「TemplatedParent」はテンプレートの適用対象を表す。

<StackPanel>
  <StackPanel.Resources>
    <ControlTemplate x:Key="ButtonTemplate"  TargetType="Button">
      <Border Background="{Binding Background, RelativeSource={RelativeSource TemplatedParent}}"
        CornerRadius="30" Padding="10">
        <TextBlock Text="{Binding Content, RelativeSource={RelativeSource TemplatedParent}}"
          VerticalAlignment="Center" HorizontalAlignment="Center"/>
      </Border>
    </ControlTemplate>
  </StackPanel.Resources>
……省略……

TemplateBindingマークアップ拡張の代わりにRelativeSourceプロパティを使用したコード例
「Background="{TemplateBinding Background}"」という記述が「Background="{Binding Background, RelativeSource={RelativeSource TemplatedParent}}"」に変更され、「Text="{TemplateBinding Content}"」という記述が「Text="{Binding Content, RelativeSource={RelativeSource TemplatedParent}}"」に変更されている。

ContentPresenter要素

 ところで、Buttonコントロールはどんな種類のコントロールだっただろうか。

 連載第1回で解説したように、Buttonコントロールは文字列に限らず、画像やUI要素といったさまざまなオブジェクトをコンテンツとして表示できるContentControlタイプのコントロールである。従って、ButtonコントロールのContentプロパティには、Image要素やPanel要素といった文字列以外のオブジェクトを設定することができる。

 しかしながら、先ほどまでのコントロール・テンプレートのように、ContentプロパティがTextBlockのTextプロパティとバインディングしていたのでは、そのようなオブジェクトを表示させることはできない。文字列以外のコンテンツが設定された場合でも、そのコンテンツをきちんと表示させるためには、そのための専用の要素である「ContentPresenter要素」を使用する必要がある。

 下記のコードは、コントロール・テンプレート内でTextBlock要素の代わりにContentPresenter要素を配置し、TemplateBindingマークアップ拡張を使ってContentプロパティをバインディングさせたものだ。

<StackPanel>
  <StackPanel.Resources>
    <ControlTemplate x:Key="ButtonTemplate"  TargetType="Button">
      <Border Background="{TemplateBinding Background}"
        CornerRadius="30" Padding="10">
        <ContentPresenter Content="{TemplateBinding Content}"
          VerticalAlignment="Center" HorizontalAlignment="Center"/>
      </Border>
    </ControlTemplate>
  </StackPanel.Resources>

  <Button Content="Button1" Background="LightBlue"
    Template="{StaticResource ButtonTemplate}" Margin="30"/>

  <Button Background="LightPink"
    Template="{StaticResource ButtonTemplate}" Margin="30">
    <Button.Content>
      <StackPanel Orientation="Horizontal">
        <Image Source="uac.png"/>
        <TextBlock Text="管理者実行"/>
      </StackPanel>
    </Button.Content>
  </Button>

</StackPanel>

コントロール・テンプレート内にContentPresenter要素を配置し、ContentControlのコンテンツの表示に対応したコード例(XAML)

 上記のコードの表示結果は下記のような画面となる。単純な文字列のコンテンツだけでなく、StackPanel要素を使って画像と文字列をコンテンツとした場合でも、コントロール・テンプレート内のContentpresenter要素によって、きちんと表示されていることを確認できる。

コントロール・テンプレート内のContentPresenter要素に文字列以外のオブジェクトが表示されている画面

 なお、ContentControlタイプのコントロールで、コントロール・テンプレート内にContentPresenter要素を配置すると、Contentプロパティは暗黙的にバインドされるようになっている。そのため、通常は上記のコードのようにTemplateBindingマークアップ拡張を使って明示的にContentプロパティをバインドする必要はない。

 ちなみに、ContentPresenter要素と同タイプのものとしては、ItemsControlタイプのコンテンツ(Itemsプロパティ)部分をコントロール・テンプレート内で表示するためのItemsPresenter要素が存在する。

動的な外観

 ここまで紹介した内容が理解できれば、どんな外観でもコントロール・テンプレートを使って自由に定義できるはずだ。ただし、作成できる外観は、静的な外観に限られる。

 コントロールの中には、その役割から複数の外観が必要なものが存在する。例えばCheckBoxコントロールは、IsCheckedプロパティの値によって、チェックマークがある場合と無い場合の2つの外観の状態が少なくとも必要だ。またコントロールの役割として絶対に必要なわけではないが、マウス・カーソルがボタンの上に重なったときにボタンの色が変化するといったように、視覚的なユーザー補助を目的とした外観の状態もあるだろう。

 WPF UIフレームワークでは、このような動的な外観についても、コントロール・テンプレート内で定義する方法が用意されている。

 実は、2009年6月現在の最新のWPFと、Silverlightの正式版において、コントロール・テンプレート内の動的な外観を定義する方法は、

  • WPF 3.5 SP1(.NET Framework 3.5 SP1)では「Trigger(トリガー)」
  • Silverlight 2では「VisualStateManager(ビジュアル・ステート・マネージャ)」(以下、VSM)

と異なっている。

 TriggerとVSMの優劣についてはさまざまな議論があったようだが、WPFの次のバージョン(.NET Framework 4)からは、WPFでもVSMがサポートされる予定となっており、WPF 4 Beta 1でその動作を確認することができる。また、現在のWPF 3.5 SP1でもWPF Toolkitを利用することで、プレビュー版としてVSMの機能を試すことができる。

 今後、WPFでもVSMを使用した方法が主流になることが予想されるため、以降ではVSMを使用してコントロール・テンプレート内で動的な外観を定義する方法に的を絞って解説する。

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

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

メールマガジン登録

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