WPFのControlTemplateとかで使えるBindingの特殊な書き方にTemplateBindingというものがある。こいつは、プロパティ名を受け取るだけのシンプルなもの。
TemplateBindingを使わなくても、BindingのRelativeSourceに{RelativeSource TemplatedParent}を指定することで、TemplateBindingと同じような動きをさせることが出来る。
ただし、使ってみると微妙に動きが違うことに気づいた。今まではTemplateBindingを使うと楽チンくらいにしか思ってなかったけど、違いを明らかにするために、いくつか実験をしてみようと思う。
とりあえず、実験するためにカスタムコントロールを1つこさえる。Controlを継承して、Textプロパティを定義しただけのシンプルなコントロール。
using System.Windows;
using System.Windows.Controls;
namespace WpfComboBoxStudy
{
// TextBoxもどきコントロール
public class CustomControl : Control
{
static CustomControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl), new FrameworkPropertyMetadata(typeof(CustomControl)));
}
#region Textプロパティ
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
// Using a DependencyProperty as the backing store for Text. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register(
"Text",
typeof(string),
typeof(CustomControl),
new UIPropertyMetadata(null));
#endregion
}
}
Generic.xaml側はこんな感じ。TextBoxを置いて、TemplateBindingを使ってTextBoxのTextプロパティとCustomControlのTextプロパティをバインドしている。
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfComboBoxStudy">
<Style TargetType="{x:Type local:CustomControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomControl}">
<!-- TemplateBinding!!!! -->
<TextBox Text="{TemplateBinding Text}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
このコントロールを、WindowにおいてTextプロパティにHello worldを設定する。
<Window x:Class="WpfComboBoxStudy.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfComboBoxStudy"
Title="Window1" Height="300" Width="300">
<StackPanel>
<local:CustomControl x:Name="customControl" Text="Hello world" />
</StackPanel>
</Window>
これを実行すると、TextBoxにHello worldと表示される素敵な画面が出来上がる。
さて、ここで1つ問題が出てくる。問題を明らかにするためにボタンを1つ置いてClickイベントでCustomControlのTextプロパティをMessageBoxで表示するようにしてみた。
<Window x:Class="WpfComboBoxStudy.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfComboBoxStudy"
Title="Window1" Height="300" Width="300">
<StackPanel>
<local:CustomControl x:Name="customControl" Text="Hello world" />
<!-- ↓こいつね!!!↓ -->
<Button Content="Alert" Click="Alert" />
</StackPanel>
</Window>
using System.Windows;
namespace WpfComboBoxStudy
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void Alert(object sender, RoutedEventArgs e)
{
MessageBox.Show(customControl.Text);
}
}
}
これを実行してみると、ちょっとした問題にぶちあたる。実行直後にボタンを押すと期待通りの値になる。
ただし、TextBoxの中身を書き換えてボタンを押すと…
ばっちり値が反映されてない。つまり、ターゲットからソースへの書き戻しが行われない。
ソースからターゲットへの書き戻しは普通に動く。試してみよう。
もう1つボタンを置く。
<Window x:Class="WpfComboBoxStudy.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfComboBoxStudy"
Title="Window1" Height="300" Width="300">
<StackPanel>
<local:CustomControl x:Name="customControl" Text="Hello world" />
<Button Content="Alert" Click="Alert" />
<!-- ↓足したのはこいつね!!!↓ -->
<Button Content="Update Source" Click="UpdateSource" />
</StackPanel>
</Window>
そして、イベントハンドラでcustomControlのTextプロパティの値を更新する処理を書く。
private void UpdateSource(object sender, RoutedEventArgs e)
{
customControl.Text = "筋肉が落ちていく";
}
これを実行して、Update Sourceボタンを押すと…
ぽちっとな
ソースからターゲットへ、値の更新がばっちり反映されている。
ということで、TemplateBindingは一方通行なのでした。Bindingを使うと…
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfComboBoxStudy">
<Style TargetType="{x:Type local:CustomControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomControl}">
<!-- Binding!!!! -->
<TextBox Text="{Binding Path=Text,
RelativeSource={RelativeSource TemplatedParent}}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
テキストボックスを編集してAlertを押してみると、ちゃんとCustomControlのTextプロパティが書き換わってる。
もちろんUpdateSourceボタンを押したときの動作もOK。
ということで簡単にまとめ。
TemplateBindingはソースからターゲットへの一方通行。
細かな制御をしたければ{Binding RelativeSource={RelativeSource TemplatedParent}, ....}を使おう。