検索
連載

第5回 WPFの「データ・バインディング」を理解する連載:WPF入門(2/3 ページ)

データとなるモデルと、表示を行うビューを結び付ける「データ・バインディング」と、データの表示をカスタマイズできる「データ・テンプレート」について解説。

Share
Tweet
LINE
Hatena

Bindingマークアップ拡張の書き方

 これまでの例では、Bindingマークアップ拡張を単に「{Binding X}」というように記述してきたが、Bindingマークアップ拡張にはさまざまなプロパティがあり、データ・バインディングの挙動を細かく設定することができる。以下、主要なものをいくつか紹介していく。

データ・バインディングの向きとタイミング

 Modeプロパティで、データ・バインディングの向きとタイミングを指定できる。Modeプロパティに対して設定できる値は以下のとおりである。

  • OneTime: UI要素生成時に一度だけ、ソース・プロパティの値を読み出してターゲット・プロパティに与える。
  • OneWay: ソース・プロパティが変更された際に、ターゲット・プロパティに変更を反映させる(逆は行わない)。
  • OneWayToSource: ターゲット・プロパティが変更された際に、ソース・プロパティに変更を反映させる。
  • TwoWay:ソース・プロパティおよびターゲット・プロパティのいずれの変更も、他方に反映させる。

 当然、TwoWayやOneWayToSourceを指定するためには、ソース・プロパティが書き込み可能である(setアクセサを持っている)必要がある。また、TwoWayやOneWayToSourceを指定した際には、変更をソース・プロパティに反映させるタイミングをUpdateSourceTriggerプロパティで指定できる。UpdateSourceTriggerプロパティに設定できる値は以下のとおりである。

  • Default: バインディング・ターゲットの依存関係プロパティのメタデータに基づいてタイミングを決定する(何も指定しない場合、これが設定される)。
  • PropertyChanged: バインディング・ターゲットの値が変化するたびに(例えば、テキストボックスの場合、1文字入力されるたびに)変更を通知する。
  • LostFocus: バインディング・ターゲットの要素がフォーカスを失うたびに(例えば、テキストボックスからフォーカスを外した際に)変更を通知する。
  • Explicit: 明示的にUpdateSourceメソッドを呼び出した場合にのみ変更を通知する。

バインディング・ソースのパスの書き方

 Pathプロパティで、バインディング・ソースのパスを指定する。Pathプロパティは省略形で書くことができ、これまで用いてきた、

{Binding X}

という書き方は、

{Binding Path=X}

と同じ意味である。Pathプロパティでは、以下のような構文でパス指定が可能である。

  • 「{Binding X.Y}」のように、「.」でつなぐことで階層的なパス指定が可能
  • 「{Binding X[0]}」のように、角カッコを用いてインデクサを利用可能
  • データ・ソース(=DataContextに渡されたオブジェクト)そのもののインデクサを利用する場合には、「{Binding [0]}」というように、Pathプロパティに直接角カッコを記述する
  • 引数が複数あるインデクサを利用する場合には、「<Binding Path="[3,4]" />」のように「,」を利用する(ただし、マークアップ拡張ではこの書き方はできない。<Binding>要素を使う必要がある)
  • 階層的なパス指定とインデクサは混在可能(「{Binding X[0].Y}」というような記述も可能)
  • 添付プロパティをバインディング・ソースにする場合には、「{Binding (Canvas.Left)}」のように、カッコでくくって「(型名.プロパティ名)」と記述する

 例えば、List 3に示すように、階層的なデータをDataContextプロパティに渡した場合を考えてみよう。

this.DataContext = new
{
  管理者 = new { 姓 = "岩永", 名 = "信之" },
  コンテンツ = new[]
  {
    new { タイトル = "C# 入門", URL = "csharp" },
    new { タイトル = "信号処理", URL = "dsp" },
    new { タイトル = "力学", URL = "dynamics" },
  }
};

Me.DataContext = New With
  {
    .管理者 = New With {.姓 = "岩永", .名 = "信之"},
    .コンテンツ =
      {
        New With {.タイトル = "C# 入門", .URL = "csharp"},
        New With {.タイトル = "信号処理", .URL = "dsp"},
        New With {.タイトル = "力学", .URL = "dynamics"}
      }
  }

List 3: 階層的なデータをDataContextに渡す(上:C#、下:VB)

 このとき、List 4に示すようなXAMLコードを書くと、Figure 5に示すような表示結果が得られる。

……省略……
<StackPanel x:Name="panel" Canvas.Left="99">

  <!-- 階層的なプロパティ指定 -->
  <TextBlock Text="{Binding 管理者.姓}"/>

  <!-- インデクサー -->
  <TextBlock Text="{Binding コンテンツ[1].URL}"/>

  <!-- 添付プロパティ -->
  <TextBlock
    Text="{Binding ElementName=panel, Path=(Canvas.Left)}"/>

</StackPanel>
……省略……

List 4: パス指定の例(XAML)


Figure 5: List 4の表示結果

コレクション走査パスの指定

 Pathプロパティでは、「{Binding Path=List/X}」というように「/」でつなぐことで、リストボックスなどで選択された行のプロパティを表示することができる(コレクションを走査して、選択中の要素を拾い出してくれる)。

 前節と同様に、List 3に示すデータをDataContextプロパティに渡した場合、List 5に示すようなXAMLコードを書くとFigure 6に示すような表示結果が得られる。

……省略……
<StackPanel>

  <!-- 「/」による選択行の表示を働かせるためには
      IsSynchronizedWithCurrentItemプロパティの指定が必要 -->
  <DataGrid ItemsSource="{Binding Path=コンテンツ}"
            IsSynchronizedWithCurrentItem="True" />
 
  <!-- DataGridコントロールで選択されている行の
      [タイトル]プロパティが表示される -->
  <TextBlock Text="{Binding Path=コンテンツ/タイトル}"  />

</StackPanel>
……省略……

List 5: 階層的なデータを参照するBindingマークアップ拡張の書き方(XAML)


Figure 6: List 5の表示結果
DataGridコントロール内の行を選択すると、その行データの[タイトル]列の値が、下部のテキストボックスに表示される。

バインディング・ソースの明示的指定

 特に何も指定しない場合、バインディング・ソースは、UI要素のDataContextプロパティに与えられたオブジェクトになる。一方で、Bindingマークアップ拡張のSourceプロパティを設定することで、バインディング・ソースを明示的に指定することもできる。

 例えば、List 6に示すように、静的プロパティをバインディング・ソースに指定するなどの用途で利用する。

……省略……
<StackPanel>

  <TextBlock Text="{Binding Source={x:Static Application.Current},
             Path=StartupUri}" />

</StackPanel>
……省略……

List 6: 静的プロパティをバインディング・ソースに指定する例(XAML)
このコードを実行すると、(Applicationクラス(System.Windows名前空間)のCurrent静的プロパティから得られる)ApplicationオブジェクトのStartupUriプロパティ値(=アプリケーションの起動時に自動的に表示されるUIを参照するURI)が、TextBlockコントロールに表示される。

 Sourceプロパティのほかに、後述するElementNameプロパティやRelativeSourceプロパティでもバインディング・ソースを選択できるが、これら3つのプロパティを同時に指定することはできない。

XPath

 バインディング・ソースがXMLデータの場合、PathプロパティではなくXPathプロパティを利用する。

 XPathプロパティには、(XMLに付随するパス指定言語である)XPath言語(XML Path Language)でクエリを記述する。XPathクエリの書き方については本稿の範囲を超えるため、詳細は割愛し、例のみをList 7に示す。

……省略……
<Grid>

  <Grid.Resources>
    <XmlDataProvider x:Key="Pages" XPath="Pages">
      <x:XData>
        <Pages xmlns="">
          <Page Title="C# 入門" Url ="csharp" />
          <Page Title="信号処理" Url ="dsp" />
          <Page Title="力学" Url ="dynamics" />
        </Pages>
      </x:XData>
    </XmlDataProvider>
  </Grid.Resources>

  <ListBox ItemsSource=
      "{Binding Source={StaticResource Pages}, XPath=Page}">
    <ListBox.ItemTemplate>
      <DataTemplate>
        <TextBlock Text="{Binding XPath=@Title}"/>
      </DataTemplate>
    </ListBox.ItemTemplate>
  </ListBox>

</Grid>
……省略……

List 7: XMLデータをバインディング・ソースとして利用する例(XAML)
このコードを実行すると、XMLデータの<Pages>要素内の各<Page>要素のTitle属性値が、リストボックスの各行に表示される。

ほかのUI要素の参照

 場合によっては、モデルなどのデータ・ソースを介さず、UI要素間で直接プロパティ値の同期を取りたいことがある。このような場合に、ElementNameプロパティを指定することで、ほかのUI要素をバインディング・ソースにしてデータ・バインディングを行える。

 例えば、List 8に示すようなXAMLコードを書くことで、テキストボックスに表示されるテキストと、スライダーの値を同期できる(スライダーのつまみを動かすと、即座にテキストが変化する)。

……省略……
<StackPanel>
  <TextBox x:Name="textValue" />
  <Slider Value="{Binding ElementName=textValue, Path=Text}" />
</StackPanel>
……省略……

List 8: UI要素間でのデータ・バインディング(XAML)

自分自身や先祖要素の参照

 RelativeSourceプロパティを指定することで、UI要素自身や、先祖要素(=直接の親だけではなく、階層的にたどれる上位の要素すべて)のプロパティをデータ・ソースに指定することができる。利用例をList 9に示す。

……省略……
<StackPanel>
  <!-- 先祖をたどって<Window>要素を見つけ、
       そのTitleプロパティとバインディング -->
  <TextBlock Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window},
             Path=Title}" />

  <!-- 自分自身のWidthプロパティとHeightプロパティを
       バインディング(要するに、正方形にする) -->
  <Rectangle Fill="Blue" Width="50"
             Height="{Binding RelativeSource={RelativeSource Self}, Path=Width}" />
</StackPanel>
……省略……

List 9: RelativeSourceの利用例(XAML)
このコードを実行すると、TextBlockコントロールに先祖<Window>要素のTitleプロパティ値が表示され、その下に縦・横50ピクセルの青い正方形が表示される。

値の変換

 値そのままではなく、何らかの変換処理を行ってからデータ・バインディングしたい場合もあるだろう。そのような場合には、Bindingマークアップ拡張のConverterプロパティにIValueConverterインターフェイス(System.Windows.Data名前空間)を実装するクラスを渡す。

 例えば、角度の度数(degree)と弧度数(radian:ラジアン)の(双方向)変換を考えてみよう。まず、List 10に示すように、IValueConverterインターフェイス実装クラスを用意する。

using System;
using System.Windows.Data;
using System.Globalization;
 
namespace atmarkit05
{
  public class DegreeToRadianConverter : IValueConverter
  {
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
      double x = (double)value;
      return x / 180 * Math.PI;
    }
 
    public object ConvertBack(object value, Type targetType, object parameter,  CultureInfo culture)
    {
      double x =  double.Parse((string)value);
      return (x / Math.PI * 180).ToString();
    }
  }
}

Public Class DegreeToRadianConverter
  Implements IValueConverter

  Public Function Convert(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.Convert

    Dim x As Double = CType(value, Double)
    Return x / 180 * Math.PI

  End Function

  Public Function ConvertBack(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.ConvertBack

    Dim x As Double = Double.Parse(CType(value, String))
    Return (x / Math.PI * 180).ToString()

  End Function

End Class

List 10: 度と弧度を変換するためのクラス(上:C#、下:VB)
なおVBの場合、プロジェクト・プロパティで設定した、[ルート名前空間]が「atmarkit05」なっている必要がある。

 このクラスを利用して、List 11に示すようなXAMLコードを記述することで、度と弧度の変換が可能になる。

……省略……
<StackPanel xmlns:local="clr-namespace:atmarkit05">

  <StackPanel.Resources>
    <local:DegreeToRadianConverter x:Key="DegToRad"/>
  </StackPanel.Resources>

  <Slider x:Name="slider" Value="0" Minimum="0" Maximum="360" />
  <TextBox Text="{Binding ElementName=slider, Path=Value}" />
  <TextBox Text="{Binding ElementName=slider, Path=Value,
           Converter={StaticResource DegToRad}}" />

</StackPanel>
……省略……

List 11: Converterプロパティを使った値の変換の例(XAML)
このコードを実行してスライダーを動かすと、上のTextBoxコントロールに0〜360の度数値が表示され、その下のTextBoxコントロールに(度数から変換された)弧度数が表示される。
なおVBの場合、プロジェクト・プロパティで設定した、[ルート名前空間]が「atmarkit05」なっている必要がある。

データ検証

 Bindingマークアップ拡張では、エンド・ユーザーから入力されたデータの検証を行うために、以下の3つのプロパティを利用する。

  • ValidationRules: ValidationRuleクラス(System.Windows.Controls名前空間)を継承するクラスを使って、明示的に検証ルールを追加する。
  • ValidatesOnExceptions: 「True」に設定されている場合、ソース・プロパティの更新中に例外が発生していないかを確認する。ValidationRulesプロパティに「ExceptionValidationRule」を追加した場合と同様の挙動になる。
  • ValidatesOnDataErrors: 「True」に設定されている場合、IDataErrorInfoインターフェイスを介したデータ検証を有効にする。ValidationRulesプロパティに「DataErrorValidationRule」を追加した場合と同様の挙動になる。

 データ検証の結果、何らかのエラーがあった場合には、Validation.HasError添付プロパティに「True」が設定され、Validation.Errors添付プロパティにエラーの一覧が格納される。

 また、WPFのコントロールのいくつかは、標準でデータ検証エラーに対応していて、Figure 7に示すように、データ検証エラーがある場合にテキストボックスの枠線が赤くなるなどの変化が生じる(Validation.HasError添付プロパティをトリガーとしたスタイルが定義されている)。


Figure 7: データ検証エラー時のスタイル
数値をデータ・バインディングしているテキストボックスに対して、不正な文字列を入力することで例外が発生している。ValidatesOnExceptionプロパティをTrueに設定することで、このように、テキストボックスの枠線が赤く変化する(ただし、この例で表示されているツールチップは、標準ではなく、追加でスタイルを定義している)。

 IDataErrorInfoインターフェイスの実装方法については、次回で説明する。

 続いて、データ・バインディングした単一のデータ/コレクション・データの表示方法をカスタマイズできる「データ・テンプレート」について説明する。

Copyright© Digital Advantage Corp. All Rights Reserved.

ページトップに戻る