データ バインドの概要 (WPF .NET)
Windows Presentation Foundation (WPF) のデータ バインディングでは、アプリでデータの表示や操作を行うための、シンプルかつ一貫した方法を提供しています。 要素は、.NET オブジェクトおよび XML の形式でさまざまな種類のデータ ソースのデータにバインドできます。 すべての ContentControl (Button など)、およびすべての ItemsControl (ListBox や ListView など) には、1 つのデータ項目またはデータ項目のコレクションを柔軟にスタイル設定できる組み込みの機能があります。 並べ替えビュー、フィルター ビュー、およびグループ ビューは、データの上に生成できます。
WPF のデータ バインディングには、広範なプロパティによるデータ バインディング固有のサポート、データの柔軟な UI 表現、および UI からのビジネス ロジックの明確な分離など、従来のモデルより優れた長所がいくつかあります。
この記事では、WPF データ バインディングの基本となる概念を最初に説明した後、Binding クラスの使用とデータ バインディングのその他の機能について説明します。
データ バインディングとは
データ バインディングとは、アプリの UI と、そこに表示されるデータとの間の接続を確立する処理です。 バインドが適切に設定され、データから適切な通知が提供される場合、データの値が変更されると、そのデータにバインドされている要素に変更が自動的に反映されます。 データ バインディングには、要素内のデータの外部表現が変更された場合、基になるデータを自動的に更新して変更を反映できるという意味もあります。 たとえば、ユーザーが TextBox
要素の値を編集すると、基になるデータ値がその変更を反映するように自動的に更新されます。
データ バインディングの一般的な用途は、サーバーまたはローカルの構成データをフォームやその他の UI コントロールに配置することです。 WPF では、この概念が、幅広いプロパティをさまざまな種類のデータ ソースにバインドすることを含むように拡張されます。 WPF では、要素の依存関係プロパティは .NET オブジェクト (ADO.NET オブジェクトまたは Web サービスおよび Web プロパティに関連付けられているオブジェクトを含む) および XML データにバインドできます。
基本的なデータ バインディングの概念
バインドする要素およびデータ ソースの性質に関係なく、各バインドは常に次の図で示したモデルに従います。
図で示されているように、データ バインディングは、基本的にバインディング ターゲットとバインディング ソース間の橋渡しです。 図には、次の基本的な WPF データ バインディングの概念が示されています。
通常、各バインドには次の 4 つのコンポーネントがあります。
- バインディング ターゲット オブジェクト。
- ターゲット プロパティ。
- バインディング ソース。
- 使用するバインディン グソース内の値へのパス。
たとえば、
TextBox
のコンテンツをEmployee.Name
プロパティにバインドした場合は、次の表のようにバインドを設定します。設定 値 移行先 TextBox 対象になるプロパティ Text Source オブジェクト Employee
ソース オブジェクトの値のパス Name
ターゲット プロパティは、依存関係プロパティである必要があります。
ほとんどの UIElement プロパティは依存関係プロパティで、ほとんどの依存関係プロパティは、読み取り専用プロパティを除き、既定でデータ バインディングをサポートします。 依存関係プロパティを定義できるのは、DependencyObject から派生した型だけです。 すべての UIElement 型は
DependencyObject
から派生します。バインディング ソースは、カスタム .NET オブジェクトに限定されません。
図では示されていませんが、バインディング ソース オブジェクトになりうるのは、カスタム .NET オブジェクトだけではないことに注意してください。 WPF データ バインディングでは、.NET オブジェクト、XML、および XAML 要素オブジェクト形式のデータがサポートされます。 いくつかの例を示すと、バインディング ソースには、UIElement、リスト オブジェクト、ADO.NET または Web サービス オブジェクト、または XML データを含む XmlNode を指定できます。 詳しくは、「バインディング ソースの概要」をご覧ください。
バインディングを確立するということは、バインディング ターゲットをバインディング ソース "に" バインドするということを理解することが重要です。 たとえば、データ バインディングを使用して、基になる何らかの XML データを ListBox で表示する場合は、ListBox
を XML データにバインドします。
バインドを確立するには、Binding オブジェクトを使用します。 この記事の残りの部分では、Binding
オブジェクトに関連付けられている概念の多くと、その一部のプロパティおよびその使用方法について説明します。
データ コンテキスト
データ バインディングが XAML 要素で宣言されている場合、データ バインディングを解決するために直接の DataContext プロパティが参照されます。 データ コンテキストは、通常、バインディング ソース値のパスの評価におけるバインディング ソース オブジェクトです。 バインディングでこの動作をオーバーライドし、特定のバインディング ソース オブジェクトの値を設定できます。 バインディングをホスティングしているオブジェクトの DataContext
プロパティが設定されていない場合、親要素の DataContext
プロパティが、XAML オブジェクト ツリーのルートまでチェックされます。 つまり、オブジェクトに明示的に設定されていない限り、バインディングの解決に使用されるデータ コンテキストは親から継承されます。
バインディングは、バインディングの解決にデータ コンテキストを使用するのではなく、特定のオブジェクトで解決するように構成できます。 ソース オブジェクトを直接指定することは、たとえば、オブジェクトの前景色を別のオブジェクトの背景色にバインドするような場合に使用されます。 この 2 つのオブジェクト間でバインディングが解決されるため、データ コンテキストは必要ありません。 逆に、特定のソース オブジェクトにバインドされていないバインディングでは、データ コンテキストの解決が使用されます。
DataContext
プロパティが変更されると、データ コンテキストの影響を受ける可能性があるすべてのバインディングが再評価されます。
データ フローの方向
前の図の矢印で示されているように、バインドのデータ フローは、バインディング ターゲットからバインディング ソースへ流れます (たとえば、ユーザーが TextBox
の値を編集するとソース値が変更されます)。また、バインディング ソースが適切な通知を提供する場合は、バインディング ソースからバインディング ターゲットへ流れます (たとえば、TextBox
コンテンツはバインディング ソースの変更で更新されます)。
アプリでユーザーがデータを変更してそれをソース オブジェクトに反映できるようにすることができます。 または、ユーザーがソース データを更新できないようにすることもできます。 データのフローを制御するには、Binding.Mode を設定します。
この図は、さまざまなデータ フローの種類を示しています。
OneWay バインディングによってソース プロパティが変更されたことにより、ターゲット プロパティは自動的に更新されますが、ターゲット プロパティへの変更は、ソース プロパティには反映されません。 この型のバインディングは、バインドされているコントロールが暗黙的な読み取り専用の場合に適しています。 たとえば、株価情報などのソースにバインドしたり、またはターゲット プロパティに、データ バインドされたテーブルの背景色などのように、変更用コントロール インターフェイスがない可能性もあります。 ターゲット プロパティの変更を監視する必要がない場合は、OneWay バインディング モードを使うことにより、TwoWay バインディング モードのオーバーヘッドを回避できます。
TwoWay バインディングにより、ソース プロパティまたはターゲット プロパティのいずれかが変更され、もう一方も自動的に更新されます。 この種類のバインドは、編集可能なフォームやその他の完全にインタラクティブな UI シナリオに適しています。 ほとんどのプロパティは既定で OneWay バインディングに設定されていますが、一部の依存関係プロパティ (一般的に、TextBox.Text や CheckBox.IsChecked などのユーザーが編集可能なコントロールのプロパティ) は、規定で TwoWay バインディングに設定されています。
依存関係プロパティが既定で一方向と双方向のどちらでバインドされるかをプログラムで判断する 1 つの方法は、DependencyProperty.GetMetadata を使用してそのプロパティ メタデータを取得してすることです。 このメソッドの戻り値の型は PropertyMetadata であり、バインドに関するメタデータは含まれません。 ただし、この型を派生型 FrameworkPropertyMetadata にキャストできる場合は、FrameworkPropertyMetadata.BindsTwoWayByDefault プロパティのブール値を調べることができます。 次のコード例は、TextBox.Text プロパティのメタデータの取得方法を示したものです。
public static void PrintMetadata() { // Get the metadata for the property PropertyMetadata metadata = TextBox.TextProperty.GetMetadata(typeof(TextBox)); // Check if metadata type is FrameworkPropertyMetadata if (metadata is FrameworkPropertyMetadata frameworkMetadata) { System.Diagnostics.Debug.WriteLine($"TextBox.Text property metadata:"); System.Diagnostics.Debug.WriteLine($" BindsTwoWayByDefault: {frameworkMetadata.BindsTwoWayByDefault}"); System.Diagnostics.Debug.WriteLine($" IsDataBindingAllowed: {frameworkMetadata.IsDataBindingAllowed}"); System.Diagnostics.Debug.WriteLine($" AffectsArrange: {frameworkMetadata.AffectsArrange}"); System.Diagnostics.Debug.WriteLine($" AffectsMeasure: {frameworkMetadata.AffectsMeasure}"); System.Diagnostics.Debug.WriteLine($" AffectsRender: {frameworkMetadata.AffectsRender}"); System.Diagnostics.Debug.WriteLine($" Inherits: {frameworkMetadata.Inherits}"); } /* Displays: * * TextBox.Text property metadata: * BindsTwoWayByDefault: True * IsDataBindingAllowed: True * AffectsArrange: False * AffectsMeasure: False * AffectsRender: False * Inherits: False */ }
Public Shared Sub PrintMetadata() Dim metadata As PropertyMetadata = TextBox.TextProperty.GetMetadata(GetType(TextBox)) Dim frameworkMetadata As FrameworkPropertyMetadata = TryCast(metadata, FrameworkPropertyMetadata) If frameworkMetadata IsNot Nothing Then System.Diagnostics.Debug.WriteLine($"TextBox.Text property metadata:") System.Diagnostics.Debug.WriteLine($" BindsTwoWayByDefault: {frameworkMetadata.BindsTwoWayByDefault}") System.Diagnostics.Debug.WriteLine($" IsDataBindingAllowed: {frameworkMetadata.IsDataBindingAllowed}") System.Diagnostics.Debug.WriteLine($" AffectsArrange: {frameworkMetadata.AffectsArrange}") System.Diagnostics.Debug.WriteLine($" AffectsMeasure: {frameworkMetadata.AffectsMeasure}") System.Diagnostics.Debug.WriteLine($" AffectsRender: {frameworkMetadata.AffectsRender}") System.Diagnostics.Debug.WriteLine($" Inherits: {frameworkMetadata.Inherits}") ' Displays: ' ' TextBox.Text property metadata: ' BindsTwoWayByDefault: True ' IsDataBindingAllowed: True ' AffectsArrange: False ' AffectsMeasure: False ' AffectsRender: False ' Inherits: False End If End Sub
OneWayToSource は OneWay バインディングの逆です。ターゲット プロパティが変更されると、ソース プロパティが更新されます。 1 つのシナリオ例は、UI からのソース値のみを再評価する必要があるかどうかです。
この図には示されていませんが、OneTime バインディングにより、ソース プロパティによってターゲット プロパティが初期化されますが、それ以降の変更は反映されません。 データ コンテキストが変更されるか、データ コンテキスト内のオブジェクトが変更された場合に、その変更はターゲット プロパティに反映されないことを意味します。 この種類のバインドは、現在の状態のスナップショットが適切な場合や、データが完全に静的である場合に適しています。 また、ソース プロパティの値を使用してターゲット プロパティを初期化するときにデータ コンテキストが事前にわからない場合にも、この型のバインディングは便利です。 基本的に、このモードは、OneWay バインディングを簡易化したもので、ソース値が変わらない場合にパフォーマンスが向上します。
ソースの変更を検出するには (OneWay および TwoWay バインディングに適用)、ソースに INotifyPropertyChanged などの適切なプロパティ変更通知メカニズムを実装する必要があります。 「方法: プロパティの変更通知を実装する (.NET Framework)」を、INotifyPropertyChanged 実装の例として参照してください。
Binding.Mode プロパティには、バインディング モードに関する詳細とバインドの方向を指定する方法の例が記載されています。
ソースの更新のトリガー
TwoWay または OneWayToSource のバインドは、ターゲット プロパティの変更をリッスンし、ソースに反映します。これは、ソースの更新と呼ばれます。 たとえば、TextBox のテキストを編集して、基になるソース値を変更できます。
しかし、ソース値が更新されるのは、テキストの編集中、またはテキストの編集が完了してコントロールがフォーカスを失った後でしょうか。 Binding.UpdateSourceTrigger プロパティは、ソースの更新をトリガーする対象を決定します。 次の図の右矢印の点は、Binding.UpdateSourceTrigger プロパティの役割を示しています。
UpdateSourceTrigger
値が UpdateSourceTrigger.PropertyChanged の場合、TwoWay または OneWayToSource バインディングの右矢印によって示される値は、ターゲット プロパティが変更されるとすぐに更新されます。 ただし、UpdateSourceTrigger
値が LostFocus の場合、この値は、ターゲット プロパティがフォーカスを失ってから、新しい値で更新されます。
Mode プロパティと同様に、依存関係プロパティごとに異なる既定の UpdateSourceTrigger 値が設定されます。 ほとんどの依存関係プロパティの既定値は PropertyChanged です。これにより、ターゲット プロパティの値が変わると、ソース プロパティの値が即座に変わります。 即座の変更は、CheckBox やその他の単純なコントロールでは問題がありません。 ただし、テキスト フィールドについては、キー入力のたびに更新するとパフォーマンスが低下する可能性があり、また通常のように、新しい値をコミットする前にユーザーがバックスペースで入力ミスを消す機会がなくなります。 たとえば、TextBox.Text
プロパティの LostFocus の既定値は、TextBox.Text
が変わったときではなく、コントロール要素のフォーカスが失われたときにのみソース値を変更する UpdateSourceTrigger
です。 依存関係プロパティの既定値を検索する方法については、UpdateSourceTrigger プロパティ ページを参照してください。
次の表に、例として TextBox を使用した UpdateSourceTrigger 値ごとのシナリオ例を示します。
UpdateSourceTrigger の値 | ソース値が更新されるとき | TextBox のシナリオ例 |
---|---|---|
LostFocus (TextBox.Text の既定値) |
TextBox コントロールがフォーカスを失ったとき。 | 検証ロジックに関連付けられている TextBox (以下の「データの検証」セクションを参照)。 |
PropertyChanged |
TextBox に入力するとき。 | チャットルーム ウィンドウ内の TextBox コントロール。 |
Explicit |
アプリが UpdateSource を呼び出すとき。 | 編集可能なフォーム内の TextBox コントロール (ユーザーが送信ボタンを押したときにのみ、ソース値を更新)。 |
例については、「方法: TextBox テキストでソースを更新するタイミングを制御する (.NET Framework)」を参照してください。
データ バインディングの例
データ バインディングの例については、データ バインディング デモに関するページの次のアプリ UI を参照してください。ここでは、オークション品目の一覧が表示されています。
このアプリは、データ バインディングの次の機能を示しています。
ListBox のコンテンツは、AuctionItem オブジェクトのコレクションにバインドされています。 AuctionItem オブジェクトには、Description、StartPrice、StartDate、Category、SpecialFeatures などのプロパティがあります。
ListBox
に表示されるデータ (AuctionItem オブジェクト) は、項目ごとに説明と現在の価格が表示されるようにテンプレート化されます。 テンプレートは、DataTemplate を使用して作成されます。 さらに、各項目の外観は、表示されている AuctionItem の SpecialFeatures の値に依存します。 AuctionItem の SpecialFeatures の値が Color の場合、その項目には青の枠線が付きます。 値が Highlight の場合、その項目にはオレンジの枠線と星が付きます。 「データ テンプレート」セクションでは、データ テンプレートに関する情報を提供します。ユーザーは、提供されている
CheckBoxes
を使用して、データのグループ化、フィルター処理、または並べ替えを行うことができます。 上の図では、[Group by category]\(分類別にグループ化\) と [Sort by category and date]\(カテゴリと日付で並べ替え\) のCheckBoxes
が選択されています。 データが製品のカテゴリに基づいてグループ化され、カテゴリ名がアルファベット順になっていることが分かります。 図では分かりにくいですが、項目は各カテゴリ内での開始日でも並べ替えられています。 並べ替えは、コレクション ビューを使用して行われます。 コレクション ビューについては、「コレクションにバインドする」セクションで説明します。ユーザーが項目を選択すると、選択した項目の詳細が ContentControl に表示されます。 このエクスペリエンスは、マスター詳細シナリオと呼ばれます。 この種類のバインドに関する情報は、「マスター詳細シナリオ」セクションにあります。
StartDate プロパティの型は DateTime で、ミリ秒までの時刻を含む日付を返します。 このアプリでは、より短い日付文字列を表示するため、カスタム コンバーターが使用されています。 コンバーターに関する情報は、「データ変換」セクションにあります。
ユーザーが [Add Product](製品の追加) ボタンを選択すると、次のフォームが表示されます。
ユーザーは、フォーム内のフィールドを編集し、簡単なプレビューまたは詳細なプレビュー ウィンドウを使用して製品の一覧をプレビューしてから Submit
をクリックして新しい製品の一覧を追加することができます。 既存のグループ化、フィルター処理および並べ替えの設定は、新しいエントリに適用されます。 この特定のケースでは、上のイメージで入力した項目が Computer カテゴリ内の 2 番目の項目として表示されます。
この図には示されていませんが、[Start Date\(開始日\) TextBox には検証ロジックが提供されています。 ユーザーが無効な日付 (無効な書式または過去の日付) を入力すると、ToolTip、および TextBox の横の赤い感嘆符でユーザーに通知されます。 検証ロジックの作成方法については、「データの検証」セクションで説明します。
上記で説明したデータ バインディングのさまざまな機能の説明に入る前に、まず WPF データ バインディングの理解に欠かせない基本概念について説明します。
バインディングを作成する
前のセクションで説明した概念の一部をもう一度説明するため、Binding オブジェクトを使用してバインディングを確立します。各バインドには通常、バインディング ターゲット、ターゲット プロパティ、バインディング ソース、および使用するソース値へのパスの 4 つのコンポーネントがあります。 このセクションでは、バインドの設定方法について説明します。
バインディング ソースは要素のアクティブな DataContext に関連付けられています。 明示的に定義されていない場合、要素は自動的にその DataContext
を継承します。
次の例について考えます。この例では、バインディング ソース オブジェクトは、SDKSample 名前空間で定義されている MyData という名前のクラスです。 デモンストレーション目的のため、MyData には、値が "Red" に設定された ColorName という名前の文字列プロパティがあります。 したがって、この例では、背景が赤のボタンが生成されます。
<DockPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:SDKSample">
<DockPanel.Resources>
<c:MyData x:Key="myDataSource"/>
</DockPanel.Resources>
<DockPanel.DataContext>
<Binding Source="{StaticResource myDataSource}"/>
</DockPanel.DataContext>
<Button Background="{Binding Path=ColorName}"
Width="150" Height="30">
I am bound to be RED!
</Button>
</DockPanel>
バインディング宣言の構文の詳細およびコード内でバインドを設定する方法の例については、「バインディング宣言の概要」を参照してください。
この例を基本的な図に適用すると、結果として得られる図は、次のようになります。 この図の Background プロパティは既定で OneWay バインディングをサポートしているため、これは OneWay バインディングを示しています。
ColorName プロパティの型が文字列で Background プロパティの型が Brushであるにもかかわらず、このバインドが機能することを不思議に思うかもしれません。 このバインドでは、既定の型変換が使用されます。これについては、「データ変換」セクションで説明します。
バインディング ソースの指定
前の例では、DockPanel.DataContext プロパティを設定することでバインディング ソースが指定されていることに注意してください。 Button は、次にその親要素である DockPanel から DataContext 値を継承します。 繰り返しますが、バインディング ソース オブジェクトは、バインディングの 4 つの必須コンポーネントの 1 つです。 したがって、バインディング ソース オブジェクトが指定されていないと、バインディングは機能しません。
バインディング ソース オブジェクトを指定するには複数の方法があります。 親要素で DataContext プロパティを使用すると、複数のプロパティを同じソースにバインドする場合に役立ちます。 ただし、個々のバインディング宣言でバインディング ソースを指定する方が適切な場合もあります。 前の例については、DataContext プロパティを使用する代わりに、次の例のように、Binding.Source プロパティをボタンのバインディング宣言で直接設定することでバインディング ソースを指定できます。
<DockPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:SDKSample">
<DockPanel.Resources>
<c:MyData x:Key="myDataSource"/>
</DockPanel.Resources>
<Button Background="{Binding Source={StaticResource myDataSource}, Path=ColorName}"
Width="150" Height="30">
I am bound to be RED!
</Button>
</DockPanel>
要素の DataContext プロパティを直接設定して先祖から DataContext 値を継承すること (最初の例にあるボタンなど)、およびバインディングの Binding.Source プロパティを設定してバインディング ソースを明示的に指定すること (最後の例にあるボタンなど) 以外に、Binding.ElementName プロパティまたは Binding.RelativeSource プロパティを使用してバインディング ソースを指定することもできます。 ElementName プロパティは、スライダーを使用してボタンの幅を調整する場合など、アプリ内の他の要素にバインドする場合に便利です。 RelativeSource プロパティは、ControlTemplate または Style でバインドが指定されている場合に便利です。 詳しくは、「バインディング ソースの概要」をご覧ください。
値にパスを指定する
バインディング ソースがオブジェクトの場合、Binding.Path プロパティを使用してバインドに使用する値を指定します。 XML データにバインドする場合は、Binding.XPath プロパティを使用して値を指定します。 場合によっては、データが XML の場合でも、Path プロパティが使用できる場合があります。 たとえば、(XPath クエリの結果として) 返された XmlNode の Name プロパティにアクセスする場合、XPath プロパティに加えて、Path プロパティを使用する必要があります。
詳細については、Path プロパティおよび XPath プロパティを参照してください。
使用する値への Path がバインディングの 4 つの必須コンポーネントの 1 つである点を強調してきましたが、オブジェクト全体にバインドするシナリオでは、使用する値はバインディング ソース オブジェクトと同じになります。 このような場合は、Path を指定しないようにすることができます。 以下の例を考慮してください。
<ListBox ItemsSource="{Binding}"
IsSynchronizedWithCurrentItem="true"/>
上記の例では、空のバインド構文 {Binding} を使用しています。 この場合、ListBox は DockPanel 親要素から DataContext を継承します (この例では示されていません)。 パスが指定されていない場合、既定では、オブジェクト全体にバインドします。 つまり、この例では、ItemsSource プロパティをオブジェクト全体にバインドしているため、パスが省略されています。 (詳しい説明については、「コレクションにバインドする」セクションを参照してください。)
コレクションにバインドする以外に、オブジェクトの 1 つのプロパティだけではなくオブジェクト全体にバインドするときにもこのシナリオは役立ちます。 たとえば、ソース オブジェクトが String 型で、文字列そのものにバインドしたい場合などです。 もう 1 つの一般的なシナリオは、要素をいくつかのプロパティを持つオブジェクトにバインドする場合です。
バインディング ターゲット プロパティに対してデータが意味を持つように、カスタム ロジックを適用することが必要になる場合があります。 既定の型変換が存在しない場合、カスタム ロジックはカスタム コンバーターの形式にすることができます。 コンバーターの詳細については、「データ変換」を参照してください。
Binding と BindingExpression
データ バインディングの他の機能や使用方法に入る前に、BindingExpression クラスを紹介します。 前のセクションで説明したように、Binding クラスは、バインディング宣言のための高レベルのクラスです。このクラスには、バインドの特性を指定するための多数のプロパティが用意されています。 関連クラスである BindingExpression は、ソースとターゲットの間の接続を維持する基になるオブジェクトです。 バインドには、複数のバインド式の間で共有できるすべての情報が含まれています。 BindingExpression は、Binding のすべてのインスタンス情報を含む、共有できないインスタンス式です。
次の例を考えてみます。ここで myDataObject
は MyData
クラスのインスタンス、myBinding
はソース Binding オブジェクト、MyData
は ColorName
という名前の文字列プロパティを含む定義済みのクラスです。 この例では、TextBlock のインスタンスである myText
のテキストの内容を ColorName
にバインドします。
// Make a new source
var myDataObject = new MyData();
var myBinding = new Binding("ColorName")
{
Source = myDataObject
};
// Bind the data source to the TextBox control's Text dependency property
myText.SetBinding(TextBlock.TextProperty, myBinding);
' Make a New source
Dim myDataObject As New MyData
Dim myBinding As New Binding("ColorName")
myBinding.Source = myDataObject
' Bind the data source to the TextBox control's Text dependency property
myText.SetBinding(TextBlock.TextProperty, myBinding)
同じ myBinding オブジェクトを使用して、他のバインディングを作成できます。 たとえば、myBinding オブジェクトを使用して、チェック ボックスのテキストの内容を ColorName にバインドすることができます。 このシナリオには、myBinding オブジェクトを共有する 2 つの BindingExpression のインスタンスがあります。
BindingExpression オブジェクトは、データ バインドされたオブジェクトで GetBindingExpression を呼び出すことによって返されます。 次の記事では、BindingExpression クラスの使用方法について説明します。
- バインドされているターゲット プロパティからのバインディング オブジェクトの取得 (.NET Framework)
- TextBox テキストでソースを更新するタイミングを制御する (.NET Framework)
データ変換
[バインドの作成] セクションのボタンは、Background プロパティが "Red" という値の文字列プロパティにバインドされているために赤色です。 この文字列値は、文字列値を Brush に変換する型コンバーターが Brush 型に存在するために機能します。
この情報を「バインドの作成」セクションにある図に追加すると、次のようになります。
しかし、バインディング ソース オブジェクトが文字列型のプロパティを持つ代わりに、Color 型の Color プロパティを持つ場合はどうなるでしょうか。 この場合、バインドが機能するためには、最初に Color プロパティ値を Background プロパティが受け入れるものに変換する必要があります。 次の例のように、IValueConverter インターフェイスを実装することによって、カスタム コンバーターを作成する必要があります。
[ValueConversion(typeof(Color), typeof(SolidColorBrush))]
public class ColorBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Color color = (Color)value;
return new SolidColorBrush(color);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
<ValueConversion(GetType(Color), GetType(SolidColorBrush))>
Public Class ColorBrushConverter
Implements IValueConverter
Public Function Convert(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements IValueConverter.Convert
Dim color As Color = CType(value, Color)
Return New SolidColorBrush(color)
End Function
Public Function ConvertBack(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements IValueConverter.ConvertBack
Return Nothing
End Function
End Class
詳細については、「IValueConverter」を参照してください。
これで既定の変換の代わりにカスタム コンバーターが使用されるようになったので、図はこのようになります。
繰り返しますが、バインドされている型に存在する型コンバーターにより、既定の変換が使用できます。 この動作は、ターゲットで利用可能な型コンバーターによって異なります。 独自のコンバーターを作成して、確認してみてください。
データ コンバーターを実装するのが合理的な典型的なシナリオをいくつか次に示します。
カルチャに応じてデータを異なる方法で表示する必要がある場合。 たとえば、特定のカルチャで使用されている規則に基づいて、通貨コンバーターまたはカレンダー日付/時刻のコンバーターを実装することができます。
使用されているデータが必ずしもプロパティのテキスト値を変更することを意図しているわけではく、イメージのソースや表示テキストの色やスタイルなど、他の何らかの値を変更することを意図している場合。 この場合、コンバーターを使用して、適切と思われないプロパティのバインディング (テキスト フィールドをテーブルのセルの Background プロパティにバインドするなど) を変換することができます。
複数のコントロールまたはコントロールの複数のプロパティが同じデータにバインドされている場合。 この場合、プライマリ バインドがテキストだけを表示する可能性があるのに対し、他のバインドは特定の表示に関する問題を処理しますが、同じバインドをソース情報として使用します。
ターゲット プロパティには、MultiBinding と呼ばれるバインドのコレクションがあります。 MultiBinding では、カスタム IMultiValueConverter を使用して、バインドの値から最終的な値を生成します。 たとえば、色は赤、青、および緑の値から計算できますが、これらは同じまたは異なるバインディング ソース オブジェクトからの値にすることができます。 例と情報については、MultiBinding に関するページを参照してください。
コレクションにバインドする
バインディング ソース オブジェクトは、プロパティにデータが含まれている 1 つのオブジェクト、または、多くの場合グループ化されるポリモーフィック オブジェクト (データベースへのクエリの結果など) のデータ コレクションとして扱うことができます。 ここまでは、1 つのオブジェクトへのバインドについてのみ説明しました。 ただし、データ コレクションへのバインドは一般的なシナリオです。 たとえば、一般的なシナリオでは、ListBox、ListView、TreeView などの ItemsControl を使用して、データ コレクションを表示します。たとえば、「データ バインディングとは」のセクションで示されているアプリなどです。
幸い、基本的な図を引き続き使用できます。 ItemsControl をコレクションにバインドする場合、図は次のようになります。
この図に示すように、ItemsControl をコレクション オブジェクトにバインドするには ItemsControl.ItemsSource プロパティを使用します。 ItemsSource
は ItemsControl のコンテンツと考えることができます。 ItemsSource
プロパティは既定で OneWay
バインディングをサポートしているため、バインドは OneWay になります。
コレクションを実装する方法
IEnumerable インターフェイスを実装する任意のコレクションを列挙できます。 ただし、コレクションの挿入または削除によって UI が自動的に更新されるように動的バインドを設定するには、コレクションは INotifyCollectionChanged インターフェイスを実装する必要があります。 このインターフェイスは、基になるコレクションが変更されたときに発生させるイベントを公開します。
WPF には、INotifyCollectionChanged インターフェイスを公開するデータ コレクションの組み込みの実装である ObservableCollection<T> クラスが用意されています。 ソース オブジェクトからターゲットへのデータ値の転送を完全にサポートするには、バインド可能なプロパティをサポートするコレクション内の各オブジェクトにも INotifyPropertyChanged インターフェイスを実装する必要があります。 詳しくは、「バインディング ソースの概要」をご覧ください。
独自のコレクションを実装する前に、ObservableCollection<T> または既存のコレクション クラス (List<T>、Collection<T>、BindingList<T> など) のいずれかを使用することを検討してください。 高度なシナリオで独自のコレクションを実装する場合は、IList の使用を検討します。これは、インデックスで個別にアクセスできる非ジェネリック オブジェクト コレクションを提供するため、最適なパフォーマンスが得られます。
コレクション ビュー
ItemsControl をデータ コレクションにバインドしたら、データを並べ替え、フィルター、またはグループ化することができます。 これを行うには、ICollectionView インターフェイスを実装するクラスであるコレクション ビューを使用します。
コレクション ビューとは
コレクション ビューは、基になるソース コレクション自体を変更することなく、並べ替え、フィルター、およびグループのクエリに基づくソース コレクションを移動および表示を可能にするバインディング ソース コレクションの上にある層です。 コレクション ビューには、コレクション内の現在の項目へのポインターも保持されます。 ソース コレクションが INotifyCollectionChanged インターフェイスを実装している場合、CollectionChanged イベントによって発生した変更はビューに反映されます。
ビューは基になるソース コレクションを変更しないため、各ソース コレクションは関連付けられた複数のビューを持つことができます。 たとえば、Task オブジェクトのコレクションを持つことができます。 ビューを使用すると、同じデータをさまざまな方法で表示できます。 たとえば、ページの左側に優先度で並べ替えられたタスクを表示し、右側に区分でグループ化されたタスクを表示できます。
ビューの作成方法
ビューを作成して使用する方法の 1 つは、ビュー オブジェクトを直接インスタンス化して、それをバインディング ソースとして使用することです。 たとえば、「データ バインディングとは」セクションで示されているデータ バインディング デモ アプリを考えてみましょう。 このアプリは、ListBox をデータ コレクションに直接バインドせずに、データ コレクション経由でビューにバインドするように実装されています。 次の例は、データ バインディング デモ アプリから抽出されたものです。 CollectionViewSource クラスは、CollectionView から継承するクラスの XAML プロキシです。 この例では、ビューの Source が、現在のアプリ オブジェクトの AuctionItems コレクション (型 ObservableCollection<T>) にバインドされています。
<Window.Resources>
<CollectionViewSource
Source="{Binding Source={x:Static Application.Current}, Path=AuctionItems}"
x:Key="listingDataView" />
</Window.Resources>
これにより、リソース listingDataView は、ListBox などのアプリ内の要素のバインディング ソースとして機能します。
<ListBox Name="Master" Grid.Row="2" Grid.ColumnSpan="3" Margin="8"
ItemsSource="{Binding Source={StaticResource listingDataView}}" />
同じコレクションに対して別のビューを作成するには、別の CollectionViewSource インスタンスを作成して違う x:Key
名を付けます。
次の表に、既定のコレクション ビューとして作成される、またはソース コレクションの型に基づいて CollectionViewSource によって作成されるビューのデータ型を示します。
ソース コレクションの型 | コレクション ビューの型 | メモ |
---|---|---|
IEnumerable | CollectionView に基づく内部型 | 項目のグループ化は不可 |
IList | ListCollectionView | 最も高速 |
IBindingList | BindingListCollectionView |
既定のビューの使用
コレクション ビューを作成して使用する 1 つの方法は、バインディング ソースとしてコレクション ビューを指定することです。 WPF では、バインディング ソースとして使用されるすべてのコレクションに対して既定のコレクション ビューも作成されます。 コレクションに直接バインドすると、WPF はその既定のビューにバインドします。 この既定のビューは、同じコレクションにバインドされているすべてのバインドで共有されるため、1 つのバインド コントロールまたはコードによる既定のビューへの変更 (後述する並べ替えや現在の項目ポインターへの変更など) は、同じコレクションにバインドされている他のすべてのバインドに反映されます。
既定のビューを取得するには、GetDefaultView メソッドを使用します。 例については、「データ コレクションの既定のビューを取得する (.NET Framework)」を参照してください。
コレクション ビューと ADO.NET DataTable
パフォーマンスを向上させるために、ADO.NET DataTable または DataView オブジェクトのコレクション ビューは、並べ替えとフィルター処理を DataView に委任します。これにより、並べ替えとフィルター処理が、データ ソースのすべてのコレクション ビューで共有されます。 各コレクション ビューで独立して並べ替えとフィルター処理をできるようにするには、各コレクション ビューを独自の DataView オブジェクトを使って初期化します。
並べ替え
前述したように、ビューでは並べ替え順序をコレクションに適用できます。 これは基になるコレクション内に存在するため、データに関連性がない場合や、本来の順序ではない場合もあります。 コレクションのビューでは、指定する比較の基準に基づいて、順序を強制したり、既定の順序を変更することができます。 これはデータのクライアント ベースのビューであるため、一般的なシナリオは、ユーザーが列に対応する値ごとに表形式のデータの列を並べ替える場合です。 ビューを使用することで、このようなユーザー主導の並べ替えを適用することができます。この場合も、基になるコレクションを変更したり、コレクションのコンテンツにクエリを再実行する必要はありません。 例については、「ヘッダーがクリックされたときに GridView 列を並べ替える (.NET Framework)」を参照してください。
次の例では、「データ バインディングとは」セクションにあるアプリ UI の [Sort by category and date](カテゴリと日付で並べ替え) CheckBox の並べ替えロジックを示しています。
private void AddSortCheckBox_Checked(object sender, RoutedEventArgs e)
{
// Sort the items first by Category and then by StartDate
listingDataView.SortDescriptions.Add(new SortDescription("Category", ListSortDirection.Ascending));
listingDataView.SortDescriptions.Add(new SortDescription("StartDate", ListSortDirection.Ascending));
}
Private Sub AddSortCheckBox_Checked(sender As Object, e As RoutedEventArgs)
' Sort the items first by Category And then by StartDate
listingDataView.SortDescriptions.Add(New SortDescription("Category", ListSortDirection.Ascending))
listingDataView.SortDescriptions.Add(New SortDescription("StartDate", ListSortDirection.Ascending))
End Sub
フィルター処理
ビューでは、コレクションにフィルターを適用して、完全なコレクションの特定のサブセットのみが表示されるようにすることもできます。 データ内の条件をフィルターすることができます。 たとえば、「データ バインディングとは」セクションのアプリで行ったように、[Show only bargains](バーゲンのみを表示) CheckBox には、25 ドル以上の項目を除外するためのロジックが含まれています。 次のコードを実行すると、CheckBox を選択したときに、ShowOnlyBargainsFilter が Filter イベント ハンドラーとして設定されます。
private void AddFilteringCheckBox_Checked(object sender, RoutedEventArgs e)
{
if (((CheckBox)sender).IsChecked == true)
listingDataView.Filter += ListingDataView_Filter;
else
listingDataView.Filter -= ListingDataView_Filter;
}
Private Sub AddFilteringCheckBox_Checked(sender As Object, e As RoutedEventArgs)
Dim checkBox = DirectCast(sender, CheckBox)
If checkBox.IsChecked = True Then
AddHandler listingDataView.Filter, AddressOf ListingDataView_Filter
Else
RemoveHandler listingDataView.Filter, AddressOf ListingDataView_Filter
End If
End Sub
ShowOnlyBargainsFilter イベント ハンドラーには、次の実装があります。
private void ListingDataView_Filter(object sender, FilterEventArgs e)
{
// Start with everything excluded
e.Accepted = false;
// Only inlcude items with a price less than 25
if (e.Item is AuctionItem product && product.CurrentPrice < 25)
e.Accepted = true;
}
Private Sub ListingDataView_Filter(sender As Object, e As FilterEventArgs)
' Start with everything excluded
e.Accepted = False
Dim product As AuctionItem = TryCast(e.Item, AuctionItem)
If product IsNot Nothing Then
' Only include products with prices lower than 25
If product.CurrentPrice < 25 Then e.Accepted = True
End If
End Sub
CollectionViewSource ではなく CollectionView クラスの 1 つを直接使用している場合は、Filter プロパティを使用してコールバックを指定します。 例については、「ビュー内のデータをフィルター処理する (.NET Framework)」を参照してください。
グループ化
IEnumerable コレクションを表示する内部クラスを除き、すべてのコレクション ビューでグループ化がサポートされています。グループ化により、ユーザーはコレクション ビューでコレクションを論理グループに分割することができます。 グループは、明示的 (ユーザーがグループの一覧を提供する) または暗黙的 (グループがデータに応じて動的に生成される) にすることができます。
次の例では、[Group by category](分類別にグループ化) CheckBox のロジックを示しています。
// This groups the items in the view by the property "Category"
var groupDescription = new PropertyGroupDescription();
groupDescription.PropertyName = "Category";
listingDataView.GroupDescriptions.Add(groupDescription);
' This groups the items in the view by the property "Category"
Dim groupDescription = New PropertyGroupDescription()
groupDescription.PropertyName = "Category"
listingDataView.GroupDescriptions.Add(groupDescription)
別のグループ化の例については、「GridView を実装する ListView の項目をグループ化する (.NET Framework)」を参照してください。
現在の項目ポインター
ビューでは、現在の項目の概念もサポートされています。 コレクション ビュー内のオブジェクト間を移動することができます。 移動するときに、項目のポインターを動かすことで、コレクション内の特定の場所に存在するオブジェクトを取得できます。 例については、「データ CollectionView のオブジェクト間を移動する (.NET Framework)」を参照してください。
WPF はビュー (指定したビュー、またはコレクションの既定のビュー) を使用してコレクションのみにバインドするため、コレクションへのすべてのバインディングには現在の項目のポインターがあります。 ビューにバインドする際に、Path
値内のスラッシュ ("/") 文字は、ビューの現在の項目を指定します。 次の例では、データ コンテキストはコレクション ビューです。 最初の行は、コレクションにバインドします。 2 番目の行は、コレクション内の現在の項目にバインドします。 3 番目の行は、コレクション内の現在の項目の Description
プロパティにバインドします。
<Button Content="{Binding }" />
<Button Content="{Binding Path=/}" />
<Button Content="{Binding Path=/Description}" />
スラッシュとプロパティの構文をスタックして、コレクションの階層をトラバースすることもできます。 次の例では、ソース コレクションの現在の項目のプロパティでである、Offices
という名前のコレクションの現在の項目にバインドします。
<Button Content="{Binding /Offices/}" />
現在の項目のポインターは、コレクションに適用されている任意の並べ替えまたはフィルター処理の影響を受ける場合があります。 並べ替えは、現在の項目のポインターを最後に選択した項目に保持しますが、コレクション ビューはそれを中心に再構築されています (以前はリストの先頭にあった選択した項目が、現在はリストの中ほどにある場合があります)。フィルター処理後に選択した項目がビューに残っている場合、フィルター処理はその項目を保持します。 それ以外の場合は、現在の項目のポインターが、フィルター処理されたコレクション ビューの最初の項目に設定されます。
マスターと詳細のバインディング シナリオ
現在の項目の概念は、コレクション内の項目間の移動に役立つだけでなく、マスターと詳細のバインディング シナリオにも役立ちます。 もう一度、「データ バインディングとは」セクションのアプリの UI を考えてみましょう。 そのアプリでは、ListBox で選択した項目によって、ContentControl に表示されるコンテンツが決定されます。 つまり、ListBox 項目を選択すると、選択した項目の詳細が ContentControl に表示されます。
同じビューにバインドされた 2 つ以上のコントロールがあるだけで、マスターと詳細のシナリオを実装できます。 データ バインディングのデモの次の例では、ListBox のマークアップと、「データ バインディングとは」セクションのアプリの UI に表示されている ContentControl が示されています。
<ListBox Name="Master" Grid.Row="2" Grid.ColumnSpan="3" Margin="8"
ItemsSource="{Binding Source={StaticResource listingDataView}}" />
<ContentControl Name="Detail" Grid.Row="3" Grid.ColumnSpan="3"
Content="{Binding Source={StaticResource listingDataView}}"
ContentTemplate="{StaticResource detailsProductListingTemplate}"
Margin="9,0,0,0"/>
両方のコントロールが同じソースである listingDataView 静的リソース (このリソースの定義は「ビューの作成方法」セクションを参照してください) にバインドされていることに注目してください。 このバインドが機能するのは、オブジェクト (この場合は ContentControl) がコレクション ビューにバインドされる際に、ビューの CurrentItem に自動的にバインドされるためです。 CollectionViewSource オブジェクトは、通貨と選択を自動的に同期します。 この例のように、リスト コントロールが CollectionViewSource オブジェクトにバインドされていない場合、機能させるには、その IsSynchronizedWithCurrentItem プロパティを true
に設定する必要があります。
その他の例については、「コレクションにバインドして選択に基づく情報を表示する (.NET Framework)」と「階層データでマスター詳細パターンを使用する (.NET Framework)」を参照してください。
上記の例では、テンプレートが使用されています。 実際、テンプレート (1 つは ContentControl によって明示的に使用され、1 つは ListBox によって暗黙的に使用されています) を使用しないと、データは希望どおりに表示されません。 次のセクションで、データ テンプレートについて説明します。
データ テンプレート
データ テンプレートを使用しないと、「データ バインディングの例」セクションのアプリの UI は、次のようになります。
前のセクションの例で示したように、コントロールと はどちらも ListBoxAuctionItemContentControl のコレクション オブジェクト全体 (より具体的には、コレクション オブジェクトのビュー) にバインドされています。 データ コレクションの表示方法の具体的な指示がない場合、ListBox は基になるコレクション内の各オブジェクトの文字列形式を表示し、ContentControl はバインド先のオブジェクトの文字列形式を表示します。
この問題を解決するには、アプリで DataTemplates を定義します。 前のセクションの例で示したように、ContentControl では、detailsProductListingTemplate データ テンプレートが明示的に使用されます。 ListBox コントロールは、コレクション内の AuctionItem オブジェクトを表示するときに、次のデータ テンプレートを暗黙的に使用します。
<DataTemplate DataType="{x:Type src:AuctionItem}">
<Border BorderThickness="1" BorderBrush="Gray"
Padding="7" Name="border" Margin="3" Width="500">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="86"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Polygon Grid.Row="0" Grid.Column="0" Grid.RowSpan="4"
Fill="Yellow" Stroke="Black" StrokeThickness="1"
StrokeLineJoin="Round" Width="20" Height="20"
Stretch="Fill"
Points="9,2 11,7 17,7 12,10 14,15 9,12 4,15 6,10 1,7 7,7"
Visibility="Hidden" Name="star"/>
<TextBlock Grid.Row="0" Grid.Column="1" Margin="0,0,8,0"
Name="descriptionTitle"
Style="{StaticResource smallTitleStyle}">Description:</TextBlock>
<TextBlock Name="DescriptionDTDataType" Grid.Row="0" Grid.Column="2"
Text="{Binding Path=Description}"
Style="{StaticResource textStyleTextBlock}"/>
<TextBlock Grid.Row="1" Grid.Column="1" Margin="0,0,8,0"
Name="currentPriceTitle"
Style="{StaticResource smallTitleStyle}">Current Price:</TextBlock>
<StackPanel Grid.Row="1" Grid.Column="2" Orientation="Horizontal">
<TextBlock Text="$" Style="{StaticResource textStyleTextBlock}"/>
<TextBlock Name="CurrentPriceDTDataType"
Text="{Binding Path=CurrentPrice}"
Style="{StaticResource textStyleTextBlock}"/>
</StackPanel>
</Grid>
</Border>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=SpecialFeatures}">
<DataTrigger.Value>
<src:SpecialFeatures>Color</src:SpecialFeatures>
</DataTrigger.Value>
<DataTrigger.Setters>
<Setter Property="BorderBrush" Value="DodgerBlue" TargetName="border" />
<Setter Property="Foreground" Value="Navy" TargetName="descriptionTitle" />
<Setter Property="Foreground" Value="Navy" TargetName="currentPriceTitle" />
<Setter Property="BorderThickness" Value="3" TargetName="border" />
<Setter Property="Padding" Value="5" TargetName="border" />
</DataTrigger.Setters>
</DataTrigger>
<DataTrigger Binding="{Binding Path=SpecialFeatures}">
<DataTrigger.Value>
<src:SpecialFeatures>Highlight</src:SpecialFeatures>
</DataTrigger.Value>
<Setter Property="BorderBrush" Value="Orange" TargetName="border" />
<Setter Property="Foreground" Value="Navy" TargetName="descriptionTitle" />
<Setter Property="Foreground" Value="Navy" TargetName="currentPriceTitle" />
<Setter Property="Visibility" Value="Visible" TargetName="star" />
<Setter Property="BorderThickness" Value="3" TargetName="border" />
<Setter Property="Padding" Value="5" TargetName="border" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
これら 2 つの DataTemplates を使用して、結果として得られた UI が、「データ バインディングとは」セクションに示されているものです。 スクリーン ショットからわかるように、DataTemplates を使用すると、データをコントロールに配置できるだけでなく、データの説得力のあるビジュアルを定義できます。 たとえば、上記の DataTemplate では、SpecialFeatures の値が HighLight になっている AuctionItem にオレンジ色の枠と星印が付いて表示されるように、DataTrigger が使用されています。
データ テンプレートの詳細については、「データ テンプレートの概要 (.NET Framework)」を参照してください。
データの検証
ユーザー入力を受け取るほとんどのアプリには、ユーザーが必要な情報を入力していることを確認する検証ロジックが必要です。 検証チェックは、型、範囲、形式、またはその他のアプリに固有の要件に基づいて作成できます。 このセクションでは、WPF でデータ検証がどのように機能するかについて説明します。
検証ルールをバインドに関連付ける
WPF データ バインディング モデルを使用すると、ValidationRules を Binding オブジェクトに関連付けることができます。 たとえば、次の例では、TextBox を StartPrice
という名前のプロパティにバインドし、Binding.ValidationRules プロパティに ExceptionValidationRule オブジェクトを追加します。
<TextBox Name="StartPriceEntryForm" Grid.Row="2"
Style="{StaticResource textStyleTextBox}" Margin="8,5,0,5" Grid.ColumnSpan="2">
<TextBox.Text>
<Binding Path="StartPrice" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<ExceptionValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
ValidationRule オブジェクトは、プロパティの値が有効かどうかを確認します。 WPF には、次の 2 種類の組み込み ValidationRule オブジェクトがあります。
ExceptionValidationRule は、バインディング ソース プロパティの更新中にスローされた例外を確認します。 前述の例では、
StartPrice
は整数型です。 ユーザーが整数に変換できない値を入力すると、例外がスローされ、バインディングが無効としてマークされます。 ExceptionValidationRule を明示的に設定する別の構文としては、Binding オブジェクトまたは MultiBinding オブジェクトで ValidatesOnExceptions プロパティをtrue
に設定することが挙げられます。DataErrorValidationRule オブジェクトは、IDataErrorInfo インターフェイスを実装するオブジェクトによって発生したエラーを確認します。 この検証規則の使用の詳細については、「DataErrorValidationRule」を参照してください。 DataErrorValidationRule を明示的に設定する別の構文としては、Binding オブジェクトまたは MultiBinding オブジェクトで ValidatesOnDataErrors プロパティを
true
に設定することが挙げられます。
ValidationRule クラスから派生し、Validate メソッドを実装することによって、独自の検証規則を作成することもできます。 次の例は、「データ バインディングとは」セクションの [Add Product Listing](製品一覧の追加) の [Start Date(開始日)] TextBox で使用されている規則を示します。
public class FutureDateRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
// Test if date is valid
if (DateTime.TryParse(value.ToString(), out DateTime date))
{
// Date is not in the future, fail
if (DateTime.Now > date)
return new ValidationResult(false, "Please enter a date in the future.");
}
else
{
// Date is not a valid date, fail
return new ValidationResult(false, "Value is not a valid date.");
}
// Date is valid and in the future, pass
return ValidationResult.ValidResult;
}
}
Public Class FutureDateRule
Inherits ValidationRule
Public Overrides Function Validate(value As Object, cultureInfo As CultureInfo) As ValidationResult
Dim inputDate As Date
' Test if date is valid
If Date.TryParse(value.ToString, inputDate) Then
' Date is not in the future, fail
If Date.Now > inputDate Then
Return New ValidationResult(False, "Please enter a date in the future.")
End If
Else
' // Date Is Not a valid date, fail
Return New ValidationResult(False, "Value is not a valid date.")
End If
' Date is valid and in the future, pass
Return ValidationResult.ValidResult
End Function
End Class
次の例に示すように、StartDateEntryForm TextBox は、この FutureDateRule を使用します。
<TextBox Name="StartDateEntryForm" Grid.Row="3"
Validation.ErrorTemplate="{StaticResource validationTemplate}"
Style="{StaticResource textStyleTextBox}" Margin="8,5,0,5" Grid.ColumnSpan="2">
<TextBox.Text>
<Binding Path="StartDate" UpdateSourceTrigger="PropertyChanged"
Converter="{StaticResource dateConverter}" >
<Binding.ValidationRules>
<src:FutureDateRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
UpdateSourceTrigger 値は PropertyChanged であるため、バインディング エンジンによって、キーが入力される度にソース値が更新されます。これは、キーが入力される度に、ValidationRules コレクション内のすべての規則がチェックされることを意味します。 これについては、「検証プロセス」セクションで詳しく説明します。
視覚的なフィードバックを提供する
ユーザーが無効な値を入力した場合に、エラーに関する何らかのフィードバックをアプリ UI に送信することができます。 このようなフィードバックを送信する方法の 1 つとして、Validation.ErrorTemplate 添付プロパティをカスタム ControlTemplate に設定する方法があります。 前のサブセクションで示したように、StartDateEntryForm TextBox は validationTemplate と呼ばれる ErrorTemplate を使用します。 次の例は、validationTemplate の定義を示します。
<ControlTemplate x:Key="validationTemplate">
<DockPanel>
<TextBlock Foreground="Red" FontSize="20">!</TextBlock>
<AdornedElementPlaceholder/>
</DockPanel>
</ControlTemplate>
AdornedElementPlaceholder 要素は、装飾されるコントロールを配置する場所を指定します。
さらに、ToolTip を使用してエラー メッセージを表示することもできます。 StartDateEntryForm と StartPriceEntryFormTextBox は、どちらも textStyleTextBox スタイルを使用します。これにより、エラー メッセージを表示する ToolTip が作成されます。 次の例は、textStyleTextBox の定義を示します。 バインドされた要素のプロパティの 1 つ以上のバインドにエラーがある場合、添付プロパティ Validation.HasError は true
になります。
<Style x:Key="textStyleTextBox" TargetType="TextBox">
<Setter Property="Foreground" Value="#333333" />
<Setter Property="MaxLength" Value="40" />
<Setter Property="Width" Value="392" />
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding (Validation.Errors).CurrentItem.ErrorContent, RelativeSource={RelativeSource Self}}" />
</Trigger>
</Style.Triggers>
</Style>
カスタム ErrorTemplate と ToolTip では、検証エラーが発生した場合、StartDateEntryForm TextBox は次のようになります。
Binding に検証規則が関連付けられているが、バインドされたコントロールで ErrorTemplate が指定されていない場合、既定の ErrorTemplate が使用され、検証エラーが発生したことがユーザーに通知されます。 既定の ErrorTemplate は、装飾レイヤーの赤い枠線を定義するコントロール テンプレートです。 既定の ErrorTemplate と ToolTip では、検証エラーが発生したときの StartPriceEntryForm TextBox の UI は次のようになります。
ダイアログ ボックス内のすべてのコントロールを検証するためのロジックを提供する方法の例については、「ダイアログ ボックスの概要」の「カスタム ダイアログ ボックス」セクションを参照してください。
検証プロセス
検証は通常、ターゲットの値がバインディング ソースのプロパティに転送されるときに発生します。 この転送は、TwoWay と OneWayToSource のバインドに対して行われます。 繰り返しますが、「UpdateSourceTriggerソースの更新のトリガー」セクションで説明したように、ソースが更新されるトリガーは、 プロパティの値に依存しています。
次の項目では、検証プロセスについて説明します。 このプロセス中の任意の時点で検証エラーまたはその他の種類のエラーが発生すると、プロセスは停止されます。
バインディング エンジンは、その Binding で ValidationStep が RawProposedValue に設定されているカスタム ValidationRule オブジェクトが定義されているかどうかを確認します。この場合は、いずれかでエラーが発生するまで、またはすべてが成功するまで、各 ValidationRule に対して Validate メソッドを呼び出します。
その後、バインディング エンジンがコンバーターを呼び出します (ある場合)。
コンバーターが成功すると、バインディング エンジンは、その Binding で ValidationStep が ConvertedProposedValue に設定されているカスタム ValidationRule オブジェクトが定義されているかどうかを確認します。この場合は、いずれかでエラーが発生するまで、またはすべてが成功するまで、ValidationStep が ConvertedProposedValue に設定されている各 ValidationRule に対して Validate メソッドを呼び出します。
バインディング エンジンは、ソース プロパティを設定します。
バインディング エンジンは、その Binding で ValidationStep が UpdatedValue に設定されているカスタム ValidationRule オブジェクトが定義されているかどうかを確認します。この場合は、いずれかでエラーが発生するまで、またはすべてが成功するまで、ValidationStep が UpdatedValue に設定されている各 ValidationRule に対して Validate メソッドを呼び出します。 DataErrorValidationRule がバインドに関連付けられており、その ValidationStep が既定の UpdatedValue に設定されている場合、この時点で DataErrorValidationRule がチェックされます。 この時点で、ValidatesOnDataErrors が
true
に設定されているバインドがすべてオンになります。バインディング エンジンは、その Binding で ValidationStep が CommittedValue に設定されているカスタム ValidationRule オブジェクトが定義されているかどうかを確認します。この場合は、いずれかでエラーが発生するまで、またはすべてが成功するまで、ValidationStep が CommittedValue に設定されている各 ValidationRule に対して Validate メソッドを呼び出します。
ValidationRule がこのプロセス全体を通じて成功しない場合、バインディング エンジンは ValidationError オブジェクトを作成し、バインドされた要素の Validation.Errors コレクションに追加します。 バインディング エンジンは、特定の手順で ValidationRule オブジェクトを実行する前に、バインドされた要素の Validation.Errors 添付プロパティに追加されたすべての ValidationError を削除します。 たとえば、ValidationStep が UpdatedValue に設定されている ValidationRule が失敗した場合、次回の検証プロセスの実行時に、バインディング エンジンは ValidationStep が UpdatedValue に設定されているすべての ValidationRule を呼び出す前に、その ValidationError を削除します。
Validation.Errors が空でない場合、要素の Validation.HasError 添付プロパティは true
に設定されます。 また、Binding の NotifyOnValidationError プロパティが true
に設定されている場合は、バインディング エンジンによって、要素で Validation.Error 添付イベントが発生します。
また、有効な値をいずれかの方向 (ターゲットからソースまたはソースからターゲット) に転送すると、Validation.Errors 添付プロパティが消去されることにも注意してください。
バインディングに関連付けられている ExceptionValidationRule がある場合、または ValidatesOnExceptions プロパティが true
に設定されていて、バインディング エンジンがソースを設定したときに例外がスローされた場合、バインディング エンジンによって UpdateSourceExceptionFilter があるかどうかが確認されます。 UpdateSourceExceptionFilter コールバックを使用して、例外を処理するためのカスタム ハンドラーを提供できます。 Binding で UpdateSourceExceptionFilter が指定されていない場合、バインディング エンジンは例外を使用して ValidationError を作成し、バインドされた要素の Validation.Errors コレクションに追加します。
デバッグのメカニズム
バインド関連のオブジェクトに添付プロパティ PresentationTraceSources.TraceLevel を設定して、特定のバインドの状態に関する情報を受け取ることができます。
関連項目
.NET Desktop feedback