次の方法で共有


データ バインディングの概要

Windows Presentation Foundation (WPF) のデータ バインディングは、アプリがデータを表示および操作するためのシンプルで一貫性のある方法を提供します。 要素は、.NET オブジェクトと XML の形式でさまざまな種類のデータ ソースのデータにバインドできます。 ContentControlなどのButtonや、ItemsControlListBoxなどのListViewには、単一のデータ項目またはデータ項目のコレクションの柔軟なスタイル設定を可能にする機能が組み込まれています。 並べ替え、フィルター、およびグループ ビューは、データの上に生成できます。

WPF のデータ バインディングには、さまざまなプロパティによるデータ バインディングの固有のサポート、データの柔軟な UI 表現、UI からのビジネス ロジックのクリーンな分離など、従来のモデルよりもいくつかの利点があります。

この記事では、まず WPF データ バインディングの基本概念について説明し、次に、 Binding クラスの使用方法とデータ バインディングのその他の機能について説明します。

データ バインディングとは何ですか。

データ バインディングは、アプリ UI と表示されるデータとの間の接続を確立するプロセスです。 バインディングに正しい設定があり、データが適切な通知を提供する場合、データがその値を変更すると、データにバインドされている要素に自動的に変更が反映されます。 データ バインディングは、要素内のデータの外部表現が変更された場合、基になるデータを自動的に更新して変更を反映できることを意味することもできます。 たとえば、ユーザーが TextBox 要素の値を編集すると、その変更を反映するように基になるデータ値が自動的に更新されます。

データ バインディングの一般的な用途は、サーバーまたはローカルの構成データをフォームまたはその他の UI コントロールに配置することです。 WPF では、この概念が拡張され、さまざまな種類のデータ ソースへのさまざまなプロパティのバインドが含まれます。 WPF では、要素の依存関係プロパティを .NET オブジェクト (web サービスおよび Web プロパティに関連付けられているオブジェクト ADO.NET 含む) と 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 プロパティが設定されていない場合は、XAML オブジェクト ツリーのルートまで、親要素の DataContext プロパティがチェックされます。 つまり、バインディングの解決に使用されるデータ コンテキストは、オブジェクトに明示的に設定されていない限り、親から継承されます。

バインドの解決にはデータ コンテキストを使用するのではなく、特定のオブジェクトで解決するようにバインドを構成できます。 ソース オブジェクトを直接指定することは、たとえば、オブジェクトの前景色を別のオブジェクトの背景色にバインドする場合に使用されます。 これらの 2 つのオブジェクト間でバインドが解決されるため、データ コンテキストは必要ありません。 逆に、特定のソース オブジェクトにバインドされていないバインドでは、データ コンテキスト解決が使用されます。

DataContext プロパティが変更されると、データ コンテキストの影響を受ける可能性があるすべてのバインディングが再評価されます。

データ フローの方向

前の図の矢印で示すように、バインド ソースが適切な通知を提供する場合、バインド のデータ フローはバインド ターゲットからバインディング ソース (たとえば、 TextBoxの値を編集するとソース値が変更されます) や、バインディング ソースからバインド ターゲットへのデータ フロー (たとえば、 TextBox コンテンツがバインディング ソースの変更で更新されます) に移動できます。

ユーザーがデータを変更し、ソース オブジェクトに反映できるようにアプリを設定できます。 または、ユーザーがソース データを更新できないようにすることもできます。 Binding.Modeを設定することで、データのフローを制御できます。

この図は、さまざまな種類のデータ フローを示しています。

データ バインディング データ フロー

  • OneWay バインドにより、ソース プロパティに対する変更によってターゲット プロパティが自動的に更新されますが、ターゲット プロパティへの変更はソース プロパティに反映されません。 バインドされるコントロールが暗黙的に読み取り専用である場合は、この種類のバインドが適しています。 たとえば、ストック ティッカーなどのソースにバインドしたり、テーブルのデータ バインド背景色などの変更を行うためにターゲット プロパティにコントロール インターフェイスが用意されていない場合があります。 ターゲット プロパティの変更を監視する必要がない場合は、 OneWay バインド モードを使用すると、 TwoWay バインド モードのオーバーヘッドが回避されます。

  • TwoWay バインディングにより、ソース プロパティまたはターゲット プロパティに対する変更によって、もう一方が自動的に更新されます。 この種類のバインドは、編集可能なフォームやその他の完全に対話型の UI シナリオに適しています。 ほとんどのプロパティは、既定のバインディングが OneWay ですが、一部の依存関係プロパティ(通常、TextBox.TextCheckBox.IsChecked などのユーザーが編集可能なコントロールのプロパティ)は、既定で TwoWay バインディングをします。

    依存関係プロパティが既定で一方向と双方向のどちらにバインドされるかをプログラムで判断する方法は、 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など) を実装する必要があります。 実装の例については、「INotifyPropertyChanged を実装する」を参照してください。

Binding.Mode プロパティは、バインド モードの詳細と、バインドの方向を指定する方法の例を提供します。

ソースの更新をトリガーする内容

TwoWayまたはOneWayToSourceバインディングは、ターゲット プロパティの変更をリッスンし、ソースに反映します (ソースの更新と呼ばれます)。 たとえば、TextBox のテキストを編集して、基になるソース値を変更できます。

ただし、テキストの編集中、またはテキストの編集が完了してコントロールのフォーカスが失われると、ソース値は更新されますか? Binding.UpdateSourceTrigger プロパティは、ソースの更新をトリガーする対象を決定します。 次の図の右矢印のドットは、 Binding.UpdateSourceTrigger プロパティの役割を示しています。

UpdateSourceTrigger プロパティの役割を示す図。

UpdateSourceTrigger値がUpdateSourceTrigger.PropertyChangedされている場合、ターゲット プロパティが変更されるとすぐに、TwoWayまたはOneWayToSourceバインドの右矢印によって指される値が更新されます。 ただし、 UpdateSourceTrigger 値が LostFocusされている場合、ターゲット プロパティがフォーカスを失った場合にのみ、その値は新しい値で更新されます。

Mode プロパティと同様に、依存関係プロパティによって既定のUpdateSourceTrigger値が異なります。 ほとんどの依存関係プロパティの既定値は PropertyChangedです。これにより、ターゲット プロパティの値が変更されると、ソース プロパティの値がすぐに変更されます。 CheckBoxやその他の単純なコントロールでは、すぐに変更できます。 ただし、テキスト フィールドの場合、すべてのキーストローク後に更新するとパフォーマンスが低下し、新しい値にコミットする前に、通常のバックスペースと入力エラーの修正の機会がユーザーに拒否される可能性があります。 たとえば、TextBox.Text プロパティの既定値は UpdateSourceTriggerLostFocus値です。これにより、TextBox.Text プロパティが変更されたときではなく、コントロール要素がフォーカスを失ったときにのみソース値が変更されます。 依存関係プロパティの既定値を検索する方法については、「 UpdateSourceTrigger プロパティ」ページを参照してください。

次の表に、UpdateSourceTriggerを例として使用する各TextBox値のシナリオ例を示します。

UpdateSourceTrigger 値 ソース値が更新されたとき TextBox のシナリオ例
LostFocus ( TextBox.Textの既定値) TextBox コントロールがフォーカスを失った場合。 検証ロジックに関連付けられている TextBox (以下の 「データ検証 」を参照)。
PropertyChanged TextBoxを入力します。 チャット ルーム ウィンドウの TextBox コントロール。
Explicit アプリが UpdateSourceを呼び出すとき。 編集可能なフォームの TextBox コントロール (ユーザーが送信ボタンを押した場合にのみソース値を更新します)。

例については、「 方法: TextBox テキストがソース (.NET Framework) を更新するタイミングを制御する」を参照してください。

データ バインディングの例

データ バインディングの例については、オークションアイテムの一覧を表示するの次のアプリ UI を参照してください。

データ バインディングサンプルのスクリーンショット

このアプリは、データ バインディングの次の機能を示しています。

  • ListBox のコンテンツは、 AuctionItem オブジェクトのコレクションにバインドされます。 AuctionItem オブジェクトには、DescriptionStartPriceStartDateCategorySpecialFeatures などのプロパティがあります。

  • に表示されるデータ (ListBox オブジェクト) はテンプレート化され、各項目の説明と現在の価格が表示されます。 テンプレートは、 DataTemplateを使用して作成されます。 また、各項目の外観は、表示される AuctionItemSpecialFeatures 値によって異なります。 AuctionItemSpecialFeatures 値が Color の場合、項目の境界線は青になります。 値が [強調表示] の場合、項目にはオレンジ色の境界線と星が付きます。 [ データ テンプレート ] セクションでは、データ テンプレートに関する情報を提供します。

  • ユーザーは、指定された CheckBoxes を使用して、データをグループ化、フィルター処理、または並べ替えることができます。 上の図では、[カテゴリ別にグループCheckBoxesが選択されています。 データが製品のカテゴリに基づいてグループ化され、カテゴリ名がアルファベット順になっていることに気付いたかもしれません。 画像から気づくのは難しいですが、項目も各カテゴリ内の開始日で並べ替えられます。 並べ替えはコレクション ビューを使用して行われます。 [ コレクションへのバインド] セクションでは、コレクション ビューについて説明します。

  • ユーザーが項目を選択すると、選択した項目の詳細が ContentControl に表示されます。 このエクスペリエンスは、 マスター詳細シナリオと呼ばれます。 マスター詳細シナリオのセクションでは、この種類のバインドに関する情報を提供します。

  • StartDate プロパティの型はDateTimeで、ミリ秒までの時間を含む日付を返します。 このアプリでは、短い日付文字列が表示されるようにカスタム コンバーターが使用されています。 データ 変換 セクションでは、コンバーターに関する情報を提供します。

ユーザーが [ 製品の追加 ] ボタンを選択すると、次のフォームが表示されます。

[製品登録情報の追加] ページ

ユーザーは、フォームのフィールドを編集し、短いプレビュー ウィンドウまたは詳細なプレビュー ウィンドウを使用して製品登録情報をプレビューし、 Submit 選択して新しい製品登録情報を追加できます。 既存のグループ化、フィルター処理、並べ替えの設定は、新しいエントリに適用されます。 この場合、上記の画像に入力された項目は、[ コンピューター ] カテゴリ内の 2 番目の項目として表示されます。

この画像には表示されませんが、 開始日TextBoxで提供される検証ロジックです。 ユーザーが無効な日付 (無効な書式設定または過去の日付) を入力した場合、 ToolTipTextBoxの横に赤い感嘆符が表示されます。 データ検証セクションでは 、検証 ロジックを作成する方法について説明します。

上記で説明したデータ バインディングのさまざまな機能に進む前に、まず WPF データ バインディングを理解するために重要な基本的な概念について説明します。

バインディングを作成する

前のセクションで説明した概念の一部を説明するために、 Binding オブジェクトを使用してバインディングを確立します。各バインドには、通常、バインディング ターゲット、ターゲット プロパティ、バインディング ソース、使用するソース値へのパスという 4 つのコンポーネントがあります。 このセクションでは、バインドを設定する方法について説明します。

バインディング ソースは、要素のアクティブな DataContext に関連付けられます。 要素が明示的に定義されていない場合、要素は自動的に DataContext を継承します。

次の例では、バインディング ソース オブジェクトが、SDKSample 名前空間で定義されている MyData という名前のクラスです。 デモンストレーションの目的で、 MyData には ColorName という名前の文字列プロパティがあり、その値は "Red" に設定されています。 したがって、この例では、赤い背景を持つボタンが生成されます。

<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 バインドについて説明します。

データ バインディングの Background プロパティを示す図。

ColorName プロパティが文字列型であるのに対し、Background プロパティがBrush型であっても、なぜこのバインドが機能するのか疑問に思うかもしれません。 このバインディングでは、既定の型変換が使用されます。これについては、「 データ変換 」セクションで説明します。

バインディング ソースの指定

前の例では、 DockPanel.DataContext プロパティを設定することでバインディング ソースが指定されていることに注意してください。 Buttonは、その親要素であるDataContextからDockPanel値を継承します。 繰り返しになりますが、バインディング ソース オブジェクトは、バインディングに必要な 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 プロパティにアクセスする場合は、Path プロパティに加えて XPath プロパティを使用する必要があります。

詳細については、 Path および XPath プロパティを参照してください。

使用する値への Path はバインディングに必要な 4 つのコンポーネントの 1 つであることを強調しましたが、オブジェクト全体にバインドするシナリオでは、使用する値はバインディング ソース オブジェクトと同じになります。 このような場合は、 Pathを指定しないことが該当します。 下記の例を検討してください。

<ListBox ItemsSource="{Binding}"
         IsSynchronizedWithCurrentItem="true"/>

上記の例では、空のバインド構文 {Binding} を使用しています。 この場合、 ListBox は親 DockPanel 要素 (この例では示されていません) から DataContext を継承します。 パスが指定されていない場合、既定値はオブジェクト全体にバインドされます。 つまり、この例では、 ItemsSource プロパティをオブジェクト全体にバインドしているため、パスは除外されています。 (詳細については、「 コレクションへのバインド 」セクションを参照してください)。

コレクションへのバインド以外に、このシナリオは、オブジェクトの単一のプロパティではなく、オブジェクト全体にバインドする場合にも役立ちます。 たとえば、ソース オブジェクトが String型の場合は、文字列自体にバインドすることができます。 もう 1 つの一般的なシナリオは、複数のプロパティを持つオブジェクトに要素をバインドする場合です。

バインドされたターゲット プロパティにデータが意味を持つよう、カスタム ロジックを適用することが必要になる場合があります。 既定の型変換が存在しない場合、カスタム ロジックはカスタム コンバーターの形式になることがあります。 コンバーターの詳細については、「 データ変換 」を参照してください。

Binding と BindingExpression

他の機能やデータ バインディングの使用方法に入る前に、 BindingExpression クラスを導入すると便利です。 前のセクションで説明したように、 Binding クラスはバインディングの宣言の高レベル クラスです。バインドの特性を指定できる多くのプロパティが用意されています。 関連するクラス BindingExpressionは、ソースとターゲットの間の接続を維持する基になるオブジェクトです。 バインドには、複数のバインド式で共有できるすべての情報が含まれます。 BindingExpressionは、共有できず、Bindingのすべてのインスタンス情報を含むインスタンス式です。

次の例では、 myDataObjectMyData クラスのインスタンスであり、 myBinding がソース Binding オブジェクトであり、 MyDataColorName という名前の文字列プロパティを含む定義済みのクラスです。 この例では、myTextのインスタンスであるTextBlockのテキスト コンテンツを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 にバインドできます。 そのシナリオでは、BindingExpression オブジェクトを共有のインスタンスが 2 つ存在します。

BindingExpression オブジェクトは、データ バインド オブジェクトに対してGetBindingExpressionを呼び出すことによって返されます。 次の記事では、 BindingExpression クラスの使用方法の一部を示します。

データ変換

[ バインドの作成 ] セクションでは、ボタンの Background プロパティが値 "Red" の文字列プロパティにバインドされているため、ボタンは赤になります。 この文字列値は、文字列値をBrushに変換するために、Brush型に型コンバーターが存在するため機能します。

[ バインドの作成 ] セクションの図にこの情報を追加すると、次のようになります。

データ バインディングの Default プロパティを示す図。

ただし、文字列型のプロパティを持つ代わりに、バインディング ソース オブジェクトに型の 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 つのオブジェクトへのバインドについてのみ説明しました。 ただし、データ コレクションへのバインドは一般的なシナリオです。 たとえば、一般的なシナリオは、ItemsControlListBoxListViewなどのTreeViewを使用して、データ コレクションを表示することです (データ バインディングの概要に関するセクションに示されているアプリなど)。

幸いなことに、基本的な図は引き続き適用されます。 ItemsControlをコレクションにバインドする場合、図は次のようになります。

データ バインディング ItemsControl オブジェクトを示す図。

この図に示すように、 ItemsControl をコレクション オブジェクトにバインドするには、使用するプロパティ ItemsControl.ItemsSource プロパティです。 ItemsSourceItemsControlのコンテンツと考えることができます。 OneWay プロパティは既定でItemsSource バインドをサポートしているため、バインディングはOneWayされます。

コレクションを実装する方法

IEnumerable インターフェイスを実装する任意のコレクションを列挙できます。 ただし、コレクション内の挿入または削除によって UI が自動的に更新されるように動的バインディングを設定するには、コレクションで INotifyCollectionChanged インターフェイスを実装する必要があります。 このインターフェイスは、基になるコレクションが変更されるたびに発生する必要があるイベントを公開します。

WPF には、ObservableCollection<T> インターフェイスを公開するデータ コレクションの組み込み実装であるINotifyCollectionChanged クラスが用意されています。 ソース オブジェクトからターゲットへのデータ値の転送を完全にサポートするには、バインド可能なプロパティをサポートするコレクション内の各オブジェクトも、 INotifyPropertyChanged インターフェイスを実装する必要があります。 詳細については、「 バインディング ソースの概要」を参照してください。

独自のコレクションを実装する前に、ObservableCollection<T>List<T>Collection<T>など、BindingList<T>または既存のコレクション クラスの 1 つを使用することを検討してください。 高度なシナリオがあり、独自のコレクションを実装する場合は、インデックスによって個別にアクセスできるオブジェクトの非ジェネリック コレクションを提供し、最適なパフォーマンスを提供する 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)」を参照してください。

次の例CheckBoxの内容] セクションのアプリ UI の [カテゴリと日付で並べ替え] の並べ替えロジックを示しています。

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

フィルタリング

ビューはコレクションにフィルターを適用して、ビューに完全なコレクションの特定のサブセットのみを表示することもできます。 データ内の条件をフィルター処理できます。 たとえば、[ データ バインディング とは] セクションでアプリによって行われるように、[バーゲンのみ表示] CheckBox には、25 ドル以上のアイテムを除外するロジックが含まれています。 次のコードは、そのが選択されたときにFilterCheckBoxイベント ハンドラーとして設定するために実行されます。

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

CollectionViewではなく、CollectionViewSource クラスのいずれかを直接使用している場合は、Filter プロパティを使用してコールバックを指定します。 例については、「 ビューでのデータのフィルター処理 (.NET Framework)」を参照してください。

グルーピング

IEnumerable コレクションを表示する内部クラスを除き、すべてのコレクション ビューでグループ化がサポートされます。これにより、ユーザーはコレクション ビュー内のコレクションを論理グループにパーティション分割できます。 グループは明示的に指定できます。ユーザーがグループの一覧を提供する場合は暗黙的で、グループはデータに応じて動的に生成されます。

次の例は、"カテゴリ別にグループ化" 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 (.NET Framework) を実装する ListView の項目をグループ化する」を参照してください。

現在の項目ポインター

ビューでは、現在の項目の概念もサポートされています。 コレクション ビュー内のオブジェクト間を移動できます。 移動すると、コレクション内の特定の場所に存在するオブジェクトを取得できる項目ポインターを移動します。 例については、「 データ 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)」を参照してください。

上記の例ではテンプレートが使用されていることに気付いたかもしれません。 実際、テンプレート ( ContentControl によって明示的に使用されたものと、 ListBoxによって暗黙的に使用されたもの) を使用しないと、データは希望する方法で表示されません。 次のセクションでは、データ テンプレートを使用します。

データ テンプレート

データ テンプレートを使用しない場合、「 データ バインディングの例 」セクションのアプリ UI は次のようになります。

データ テンプレートを使用しないデータ バインディングデモ

前のセクションの例に示すように、 ListBox コントロールと ContentControl の両方が 、AuctionItemのコレクション オブジェクト全体 (具体的には、コレクション オブジェクトのビュー) にバインドされます。 データ コレクションを表示する具体的な指示がない場合、 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 つの DataTemplate を使用すると、結果の UI がデータ バインディング の概要セクションに表示されます。 そのスクリーンショットからわかるように、DataTemplates では、コントロールにデータを配置するだけでなく、データの説得力のあるビジュアルを定義できます。 たとえば、上のDataTriggerDataTemplateを使用すると、SpecialFeatures の値がHighLightであるAuctionItemは、オレンジ色の枠と星で表示されます。

データ テンプレートの詳細については、 データ テンプレートの概要 (.NET Framework) を参照してください。

データの検証

ユーザー入力を受け取るほとんどのアプリには、ユーザーが予想される情報を入力したことを確認するための検証ロジックが必要です。 検証チェックは、種類、範囲、形式、またはその他のアプリ固有の要件に基づいて行うことができます。 このセクションでは、WPF でのデータ検証のしくみについて説明します。

検証規則とバインドの関連付け

WPF データ バインディング モデルを使用すると、 ValidationRulesBinding オブジェクトに関連付けることができます。 たとえば、次の例では、TextBoxStartPrice という名前のプロパティにバインドし、ExceptionValidationRule プロパティにBinding.ValidationRules オブジェクトを追加します。

<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 オブジェクトがあります。

ValidationRule クラスから派生し、Validate メソッドを実装することで、独自の検証規則を作成することもできます。 次の例は、[データ バインディングの内容] セクションの [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

StartDateEntryFormTextBox は、次の例に示すように、この 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に設定する方法です。 前のサブセクションに示すように、StartDateEntryFormTextBox では ErrorTemplate と呼ばれるが使用されます。 次の例は、 validationTemplate の定義を示しています。

<ControlTemplate x:Key="validationTemplate">
    <DockPanel>
        <TextBlock Foreground="Red" FontSize="20">!</TextBlock>
        <AdornedElementPlaceholder/>
    </DockPanel>
</ControlTemplate>

AdornedElementPlaceholder要素は、装飾するコントロールを配置する場所を指定します。

さらに、 ToolTip を使用してエラー メッセージを表示することもできます。 StartDateEntryFormStartPriceEntryFormTextBoxes の両方でスタイル textStyleTextBox が使用され、エラー メッセージを表示するToolTipが作成されます。 次の例は、 textStyleTextBox の定義を示しています。 添付プロパティ Validation.HasError は、バインドされた要素のプロパティの 1 つ以上のバインドがエラーのときに 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>

カスタム ErrorTemplateToolTipでは、検証エラーが発生すると、 StartDateEntryFormTextBox は次のようになります。

日付のデータ バインディング検証エラー

Bindingに検証規則が関連付けられているが、バインドされたコントロールにErrorTemplateを指定しない場合は、検証エラーが発生したときにユーザーに通知するために既定のErrorTemplateが使用されます。 既定の ErrorTemplate は、装飾レイヤーで赤い境界線を定義するコントロール テンプレートです。 既定の ErrorTemplateToolTipでは、検証エラーが発生すると、 StartPriceEntryFormTextBox の UI は次のようになります。

価格のデータ バインディング検証エラー

ダイアログ ボックス内のすべてのコントロールを検証するロジックを提供する方法の例については、「ダイアログ ボックスの概要」の「カスタム ダイアログ ボックス」セクションを参照してください。

検証プロセス

通常、検証は、ターゲットの値がバインディング ソース プロパティに転送されるときに発生します。 この転送は、 TwoWay バインドと OneWayToSource バインディングで発生します。 繰り返しますが、ソースの更新の原因は、「ソースの更新をUpdateSourceTriggerする内容」セクションで説明されているように、 プロパティの値によって異なります。

次の項目では 、検証 プロセスについて説明します。 このプロセス中に検証エラーまたはその他の種類のエラーが発生すると、プロセスは停止します。

  1. バインド エンジンは、そのValidationRuleに対してValidationStepRawProposedValueが設定されているカスタム Binding オブジェクトが定義されているかどうかを確認します。その場合、各Validateに対して ValidationRule メソッドが呼び出され、そのうちの 1 つがエラーになるまで、またはすべてが渡されるまで呼び出されます。

  2. その後、バインディング エンジンはコンバーター (存在する場合) を呼び出します。

  3. コンバーターが成功した場合、バインディング エンジンは、そのValidationRuleに対してValidationStepConvertedProposedValueに設定されているカスタム Binding オブジェクトがあるかどうかを確認します。その場合、ValidateValidationRuleに設定されている各ValidationStepで、いずれかのオブジェクトがエラーに達するまで、またはすべてのオブジェクトが渡されるまで、ConvertedProposedValue メソッドを呼び出します。

  4. バインディング エンジンは、ソース プロパティを設定します。

  5. バインド エンジンは、そのValidationRuleValidationStepUpdatedValueが設定されているカスタム Binding オブジェクトが定義されているかどうかを確認します。その場合、ValidateValidationRuleに設定されている各ValidationStepに対してUpdatedValue メソッドを呼び出し、そのうちの 1 つがエラーになるまで、またはすべてが渡されるまで呼び出します。 DataErrorValidationRuleがバインディングに関連付けられ、そのValidationStepが既定のUpdatedValueに設定されている場合、この時点でDataErrorValidationRuleがチェックされます。 この時点で、 ValidatesOnDataErrorstrue に設定されているバインディングがチェックされます。

  6. バインド エンジンは、そのValidationRuleValidationStepCommittedValueが設定されているカスタム Binding オブジェクトが定義されているかどうかを確認します。その場合、ValidateValidationRuleに設定されている各ValidationStepに対してCommittedValue メソッドを呼び出し、そのうちの 1 つがエラーになるまで、またはすべてが渡されるまで呼び出します。

ValidationRuleがこのプロセス全体を通じていつでも渡されない場合、バインド エンジンはValidationError オブジェクトを作成し、バインドされた要素のValidation.Errors コレクションに追加します。 バインディング エンジンは、特定のステップでValidationRule オブジェクトを実行する前に、そのステップ中にバインドされた要素のValidationError添付プロパティに追加されたValidation.Errorsをすべて削除します。 たとえば、ValidationRuleValidationStep に設定されているUpdatedValueが失敗した場合、次回検証プロセスが発生したとき、バインド エンジンはValidationErrorに設定されているValidationRuleを持つValidationStepを呼び出す直前に、そのUpdatedValueを即座に削除します。

Validation.Errorsが空でない場合、要素のValidation.HasError添付プロパティはtrueに設定されます。 また、NotifyOnValidationErrorBindingプロパティがtrueに設定されている場合、バインディング エンジンは要素に対してValidation.Error添付イベントを発生させます。

また、いずれかの方向 (ターゲットからソースまたはソースからターゲット) への有効な値の転送では、 Validation.Errors 添付プロパティがクリアされることに注意してください。

バインドに ExceptionValidationRule が関連付けられているか、 ValidatesOnExceptions プロパティが true に設定されていて、バインディング エンジンがソースを設定したときに例外がスローされた場合、バインディング エンジンは UpdateSourceExceptionFilterがあるかどうかを確認します。 UpdateSourceExceptionFilter コールバックを使用して、例外を処理するためのカスタム ハンドラーを提供できます。 UpdateSourceExceptionFilterBindingが指定されていない場合、バインド エンジンは例外を含むValidationErrorを作成し、バインドされた要素のValidation.Errors コレクションに追加します。

デバッグ メカニズム

バインド関連オブジェクトの添付プロパティ PresentationTraceSources.TraceLevel を設定して、特定のバインドの状態に関する情報を受け取ることができます。

こちらも参照ください