バインディングの概念を説明し、StaticResourcesやDynamicResourcesを使ってプロパティをバインドする方法を解説する記事は数多くあります。これらの概念はWPFが提供するデータバインディング式を使用しています。本記事では、WPFが提供するさまざまなタイプのデータバインディング式について詳しく見ていきましょう。
はじめに
データバインディングは、UI要素とビジネスモデル間でデータを流すことを可能にする強力な技術です。ビジネスモデル内のデータが変更されると、その変更が自動的にUI要素に反映されます。
| モデル | 説明 |
|---|---|
| OneWay | ソース → 宛先 |
| TwoWay | ソース ←→ 宛先 |
| OneWayToSource | ソース ← 宛先 |
| OneTime | ソース → 宛先 (一度だけ) |
これはWPFが提供するさまざまなタイプのデータバインディング式によって実現されます。
データバインディング式のタイプは以下の通りです。
- DataContext バインディング
- RelativeSource バインディング
- ItemSource バインディング
1. DataContext バインディング
DataContextは依存関係プロパティであり、バインディングのデフォルトのソースです。DataContextは論理ツリーに沿って継承されます。したがって、コントロールにDataContextを設定すると、論理ツリー内のすべての子要素は、別のソースが明示的に指定されない限り、同じDataContextを参照します。
例を挙げて詳しく理解しましょう。
1.1 以下のようにBookクラスを作成します。
public class Book
{
public string Name
{
get;
set;
}
public string Author
{
get;
set;
}
}
1.2 XAMLファイルDataContextBinding.XAMLを追加し、以下のように4つのTextBlockを配置します。
<Grid VerticalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition Height="40" />
<RowDefinition Height="40" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Text="Book Name:" FontWeight="Bold" />
<TextBlock Grid.Column="1" />
<TextBlock Text="Author:" FontWeight="Bold" Grid.Row="1" />
<TextBlock Grid.Row="1" Grid.Column="1" />
</Grid>
では、このDataContextプロパティを使ってデータを表示する方法を見てみましょう。
使用法は以下の2つです。
-
- 式を使用する
DataContextを直接バインドするために使用します。
Bookクラスのインスタンスを作成し、そのプロパティを初期化し、クラスのNameプロパティをWindowのDataContextプロパティに割り当てます。
public partial class DataContextBinding: Window
{
public DataContextBinding()
{
InitializeComponent();
// インスタンスを作成
Book book = new Book();
// プロパティを初期化
book.Name = "Computer Networking";
// プロパティをDataContextとして割り当て
this.DataContext = book.Name;
}
}
DataContextは論理ツリーとデータbookに沿って継承されるため、NameはControl Windowにバインドされます。Windowのすべての子要素も同じオブジェクト(book.Name)を参照します。
データを表示するには、以下のようにDataContextをTextblockにバインドします。
<TextBlock Text="Book Name:" FontWeight="Bold" />
<TextBlock Text="{Binding}" Grid.Column="1" />
出力

- 式を使用する
DataContextのプロパティをバインドします。
Bookクラスのインスタンスを作成し、そのプロパティを初期化し、クラスのインスタンス(Book)をWindowのDataContextプロパティに割り当てます。
Book book = new Book();
// プロパティを初期化
book.Name = "Computer Networking";
book.Author = "James F. Kurose";
// インスタンスをDataContextとして割り当て
this.DataContext = book;
では、出力を見てみましょう。

バインディング式はBook型のDataContextオブジェクトをバインドするため、ToString()メソッドが呼び出され、データが文字列として表示されます。データを正しい形式で表示するには、以下のようにデータオブジェクトのプロパティをTextBlockにバインドする必要があります。
<TextBlock Text="Book Name:" FontWeight="Bold" />
<TextBlock Text="{Binding Name}" Grid.Column="1" />
<TextBlock Text="Author:" FontWeight="Bold" Grid.Row="1" />
<TextBlock Text="{Binding Author}" Grid.Row="1" Grid.Column="1" />
バインディング式は、DataContextバインディングのNameプロパティをバインドするために使用されます。
出力

2. RelativeSource バインディング
RelativeSourceは、バインドソースをバインドターゲットに対して相対的な関係で設定するプロパティです。この拡張は主に、ある要素のプロパティを同じ要素の別のプロパティにバインドする必要がある場合に使用されます。
RelativeSourceには以下の4つのタイプがあります。
- Self
- FindAncestor
- TemplatedParent
- PreviousData
それぞれ詳しく見ていきましょう。
2.1 Self
Selfは、バインドソースとバインドターゲットが同じ場合に使用されます。オブジェクトのあるプロパティが同じオブジェクトの別のプロパティにバインドされます。
例として、高さと幅が同じ楕円を考えます。
XAMLファイルに以下のコードを追加します。WidthプロパティがHeightプロパティに相対的にバインドされています。
<Grid>
<Ellipse
Fill="Black"
Height="100"
Width="{Binding RelativeSource={RelativeSource Self},Path=Height}"
>
</Ellipse>
</Grid>
出力

楕円の高さを変更すると、幅も相対的に変化します。
2.2 FindAncestor
名前が示すように、バインドソースがバインドターゲットの祖先(親)の一つである場合に使用されます。FindAncestor拡張を使用すると、任意のレベルの祖先を見つけることができます。
例を挙げてより明確に理解しましょう。
手順
以下の要素の論理ツリーを表すXAMLを作成します。

<Grid Name="Parent_3">
<StackPanel Name="Parent_2">
<Border Name="Parent_1">
<StackPanel x:Name="Parent_0" Orientation="Vertical">
<button></button>
</StackPanel>
</Border>
</StackPanel>
</Grid>
では、FindAncestor拡張を使って祖先のNameプロパティを子要素のbuttonのContentプロパティにバインドします。
<Grid Name="Parent_3">
<StackPanel
Name="Parent_2"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Width="100"
>
<Border Name="Parent_1">
<StackPanel x:Name="Parent_0" Orientation="Vertical">
<button
Height="50"
Content="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type StackPanel},
AncestorLevel=2},Path=Name}"
></button>
</StackPanel>
</Border>
</StackPanel>
</Grid>
出力

AncestorTypeが"StackPanel"、AncestorLevelが"2"と組み合わさることで、buttonのContentプロパティがStackPanelのNameプロパティ(Parent_2)にバインドされます。
2.3 TemplatedParent
TemplatedParentは、いくつかの未知の値を含むコントロールテンプレートを作成できるようにするプロパティです。これらの値は、ControlTemplateが適用されるコントロールのプロパティに依存します。
例を挙げて詳しく理解しましょう。
手順
- 次のようにボタン用のControlTemplateを作成します。
<Window.Resources>
<ControlTemplate x:Key="template">
<canvas>
<Ellipse Height="110" Width="155" Fill="Black" />
<Ellipse
Height="100"
Width="150"
Fill="{Binding RelativeSource={RelativeSource TemplatedParent},Path=Background}"
>
</Ellipse>
<ContentPresenter
Margin="35"
Content="{Binding RelativeSource={RelativeSource TemplatedParent},Path=Content}"
/>
</canvas>
</ControlTemplate>
</Window.Resources>
上記のコードでは、楕円のFillプロパティとContentPresenterのContentプロパティは、このテンプレートが適用されるコントロールのプロパティ値に依存します。
- ボタンを追加し、テンプレートを適用します。
<button
Margin="50"
Background="Beige"
Template="{StaticResource template}"
Height="0"
Content="Click me"
FontSize="22"
></button>
テンプレートを適用する際、ボタンのBackground(Beige)は楕円のFillプロパティに相対的にバインドされ、Content(Click me)はContentPresenterのContentプロパティに相対的にバインドされます。依存する値が有効になり、以下の出力が得られます。
出力

2.4 PreviousData
これは最も使用頻度の低い相対的な使用法です。データが分析されるときに使用され、以前のデータに対する値の変化を表現する必要があります。
例を挙げて詳しく理解しましょう。
手順
- 以下のようにDataクラスを作成し、INotifyPropertyChangedインターフェースを実装します。
public class Data: INotifyPropertyChanged
{
public int DataValue
{
get;
set;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string PropertyName)
{
if (null != PropertyChanged)
{
PropertyChanged(this,
new PropertyChangedEventArgs(PropertyName));
}
}
}
- Data型のリストを作成し、それをDataContextとして指定します。
public RelativeSourcePreviousData()
{
InitializeComponent();
List < Data > data = new List < Data > ();
data.Add(new Data()
{
DataValue = 60
});
data.Add(new Data()
{
DataValue = 100
});
data.Add(new Data()
{
DataValue = 120
});
this.DataContext = data;
}
- XAMLファイルにItemsControlを追加します。
<ItemsControl ItemsSource="{Binding}"></ItemsControl>
- 以下のようにItemsPanelテンプレートを作成します。
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
- データを正しく表現するために、以下のようにDataTemplateを作成します。
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Grid Margin="30,20,0,0">
<Rectangle Width="80" Height="{Binding DataValue}" Fill="Blue" />
<TextBlock
Foreground="White"
Margin="35,0,0,0"
Text="{Binding DataValue}"
></TextBlock>
</Grid>
<TextBlock Margin="30,20,0,0" Text="Previous Data:"></TextBlock>
<TextBlock
VerticalAlignment="Center"
Margin="5,20,0,0"
Text="{Binding
RelativeSource={RelativeSource PreviousData}, Path=DataValue}"
/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
出力

青い四角の高さはリスト内の項目の値であり、古いデータは右側に表示されます。最初の項目の値は "60" です。したがって、最初の項目には古い値はありません。
3. ItemSource バインディング
コレクションを扱う際に使用されます。このバインディング式を使用すると、SelectedItemのプロパティを非常に簡単に読み取ることができます。スラッシュは、コレクション内の現在の項目を扱うための特別な演算子です。
以下に3つの式を示します。
- {Binding / }
- {Binding Collection / }
- {Binding Collection / Property}
3.1 {Binding / }
この式は、DataContext内の現在の項目をバインドするために使用されます。
例を取り上げます:
以下の例では、DataContextは文字列型の国(Country)のコレクションであり、ListBoxにバインドされています。
手順
- Countriesクラスを作成し、文字列データ型の国のコレクションを返すGetCountriesName()メソッドを追加します。以下の通りです。
public class Countries
{
public static List <string> GetCountriesName()
{
List <string> countries = new List <string> ();
foreach(CultureInfo culture in CultureInfo.GetCultures(CultureTypes.SpecificCultures))
{
RegionInfo country = new RegionInfo(culture.LCID);
if (!countries.Contains(country.EnglishName))
countries.Add(country.EnglishName);
}
countries.Sort();
return countries;
}
}
- XAMLファイルを追加し、ListBoxとTextBlockを以下のように配置します。
<DockPanel Name="Collection">
<ListBox ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True">
</ListBox>
<TextBlock DockPanel.Dock="Top" />
</DockPanel>
- Countriesクラスのインスタンスを作成し、CountriesコレクションをDataContextとして指定します。
public CurrentItemCollection()
{
InitializeComponent();
Countries countries = new Countries();
this.DataContext = countries.GetCountriesName()
}
- TextBlockのTextプロパティをバインドして、コレクションの現在選択されている項目にバインドします。以下の通りです。
<TextBlock DockPanel.Dock="Top" Text="{Binding /}" />
出力

リスト項目が選択されると、選択された国が右側に表示されます。
3.2 {Binding Collection /}
この式は、DataContext内のコレクションプロパティの現在の項目をバインドするために使用されます。
例:
DataContextはCountriesクラスです。
CollectionプロパティはCountriesListで、ListBoxにバインドされています。
手順
- 上記で作成した国のクラスと似ていますが、若干異なります。戻り値の型がRegionInfoのメソッドを作成します。
public static List <RegionInfo> GetCountries()
{
List <RegionInfo> countries = new List <RegionInfo> ();
foreach(CultureInfo culture in CultureInfo.GetCultures(CultureTypes.SpecificCultures))
{
RegionInfo country = new RegionInfo(culture.LCID);
if (countries.Where(p => p.Name == country.Name).Count() == 0)
countries.Add(country);
}
return countries.OrderBy(p => p.EnglishName).ToList();
}
- RegionInfo型のCountriesListプロパティを追加します。
private List <RegionInfo> countries = null;
public List <RegionInfo> CountriesList
{
get
{
if (countries == null)
countries = GetCountries();
return countries;
}
}
以下はCountriesListコレクション内の値のスクリーンショットです。

- CountriesクラスをDataContextとして指定し、ListboxをDataContextのCountriesListプロパティにバインドします。
<Window.Resources>
<vm:Countries x:Key="Countries"></vm:Countries>
</Window.Resources>
<Grid>
<DockPanel Name="Collection" DataContext="{StaticResource Countries}">
<ListBox
ItemsSource="{Binding CountriesList}"
IsSynchronizedWithCurrentItem="True"
>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding EnglishName}"></TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DockPanel>
</Grid>
- CountriesListプロパティの現在の項目を計算するには、以下のようにTextBlockのTextプロパティをバインドします。
<TextBlock DockPanel.Dock="Top" Text="{Binding CountriesList/}" HorizontalAlignment="Center" FontSize="16" VerticalAlignment="Center" />
出力

右側にはDataContext(CountriesList)内のコレクションの現在の項目(CountriesList)が表示されます。
3.3 {Binding Collection / Property}
この式は、DataContext内のコレクションの現在の項目のプロパティをバインドするために使用されます。
例えば、CountriesListコレクションの現在の項目の特定のプロパティを計算する必要がある場合です。
この例では、プロパティ"EnglishName"の値を表示したいと思います。

そのためには、以下のようにTextBlockのTextプロパティをバインドします。
<TextBlock DockPanel.Dock="Top" Text="{Binding CountriesList/EnglishName}" />
出力

リスト内の項目が選択されると、プロパティ"EnglishName"の値が表示されます。
結論
すべてのデータバインディング式について詳しく説明しました。バインディングの概念とWPFが提供する式を理解するのに役立つことを願っています。
- 著者:Swati Gupta
- 原文タイトル:DataBinding Expressions In WPF
- 原文リンク:https://www.c-sharpcorner.com/article/data-binding-expression-in-wpf/
- 編集:沙漠之尽头的狼