第2回 データの表示と入力に必要な知識連載 WPF/Silverlight UIフレームワーク入門(4/5 ページ)

» 2009年05月19日 00時00分 公開
[八巻雄哉グレープシティ株式会社]

コンバータによるデータ変換

 これまで紹介してきたサンプルは、いずれもString型のプロパティ値とDouble型のプロパティ値を同期するバインディングであったため、暗黙的な型変換によって、型の違いが自動的に解決されていた。しかしながら、このような暗黙的な型変換では対応できないケースも当然ながら存在する。

 先ほどのサンプルで、計算されたBMI値の大きさに応じて背景色を変化させるという例で考えてみよう。BMI値が「25」より大きい場合には肥満を示す赤色に、「18.5」に満たない場合には低体重示す青色に、背景色を変更させたいという要件だ。

 このような要件をバインディングを使って実現する場合、ソースにBmiLevelといったBrush型のプロパティを新たに1つ追加し、そのプロパティと背景色をバインドすればよい。

 あるいは、バインディングが持つデータ変換機能を使用する方法もある。BindingクラスにはConverterというプロパティがあり、このプロパティにIValueConverterインターフェイスを実装した自作のコンバータを設定してやることで、カスタムの相互型変換を行うことができる。

 今回の要件を満たすカスタム・コンバータは、下記のような記述となる。

class BmiLevelConverter : IValueConverter
{
  #region IValueConverter メンバ

  public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  {
    double target = (double)value;
    SolidColorBrush level;

    if (target < 18.5)
      level = new SolidColorBrush(Colors.Blue);
    else if (target > 25)
      level = new SolidColorBrush(Colors.Red);
    else
      level = new SolidColorBrush(Colors.White);

    return level;
  }

  public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  {
    throw new NotImplementedException();
  }

  #endregion
}

Public Class BmiLevelConverter
  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 IValueConverter.Convert
    Dim target As Double = CType(value, Double)
    Dim level As SolidColorBrush
    Select Case target
      Case Is < 18.5
        level = New SolidColorBrush(Colors.Blue)
      Case Is > 25
        level = New SolidColorBrush(Colors.Red)
      Case Else
        level = New SolidColorBrush(Colors.White)
    End Select

    Return level
  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 IValueConverter.ConvertBack
    Throw New NotImplementedException
  End Function

End Class

IValueConverterインターフェイスを実装して作成したBmiLevelConverterクラス(上:C#、下:VB)
※Silverlightプロジェクトでは、System.Windows.Data名前空間のインポートが必要。

 IValueConverterインターフェイスは、ソースからターゲット方向に値が反映される際に使用されるConvertメソッドと、ターゲットからソース方向に値が反映されるConvertBackメソッドを持っている。後者はTwoWayバインディングでしか使用されないため、今回は前者のConvertメソッドのみを実装している。

 ご覧の通り、Convertメソッドの実装はとても単純だ。メソッドの引数として受け取ったソース側のプロパティの値を、任意の型の値に変換して戻り値として返すだけである。

 コンバータを使用したバインディングのXAMLコードは下記のようになる。

<StackPanel xmlns:local="clr-namespace:DataBindingSample"
  x:Name="MainPanel" Loaded="MainPanel_Loaded">
  <TextBox Margin="10" Text="{Binding Height, Mode=TwoWay}"/>
  <TextBox Margin="10" Text="{Binding Weight, Mode=TwoWay}"/>
  <Button Margin="10" Content="計算" Click="Button_Click"/>
  <Border Margin="10">
    <Border.Background>
      <Binding Path="Bmi" Mode="OneWay">
        <Binding.Converter>
          <local:BmiLevelConverter/>
        </Binding.Converter>
      </Binding>
    </Border.Background>
    <TextBlock Text="{Binding Bmi, Mode=OneWay}"/>
  </Border>
</StackPanel>

プロパティ要素構文を使ってBindingオブジェクトのConverterプロパティにBmiLevelConverterを設定(XAML)
「local」というプリフィックスに自分自身の名前空間(DataBindingSample)をマッピングし、BmiLevelConverterを要素として記述できるようにしている。BorderコントロールはFrameworkElement型の1つだが、Childというコンテンツ・プロパティ(=コンテンツを格納できるプロパティ)を持っており、コンテンツとして単一のUIElementオブジェクトを持つことができる。この例では、TextBlockコントロールをBorderコントロールのコンテンツとして設定している。このようにわざわざBorderコントロールを利用したのは、TextBlockコントロールが背景色を設定する機能を持っていないため。

 先ほどまでのサンプルに上記のコードを適用することで、下の画面のようにBMI値に応じて自動的にBorderコントロールの背景色が変更されるという動作を確認できるだろう。

Converterを使った双方向バインディングの実行結果
BMI値が25以上のため、BmiLevelConverterからは赤色のSolidColorBrushオブジェクトが返され、赤い背景色(Border要素)となっている。

コレクション・オブジェクトへのバインド

 これまで、Personオブジェクトをソースとすることで単一データにおける表示と入力の例を紹介してきた。しかしながら、実際のアプリケーションでデータが単一のみであるケースはまれであり、多くの場合、何十、何百、あるいは何千というデータを取り扱う必要があるだろう。WPF UIフレームワークでは、そのようなコレクション・オブジェクトに対してバインドする方法も用意されている。

 コレクション・オブジェクトとのバインドには、ItemsControlタイプのコントロール(=ItemsControlクラスから派生しているコントロール。以下、ItemsControl)を使用する。ItemsControlは、前回の「コンテンツ・モデル」で紹介した固定的な複数のコンテンツを表示するという役割のほかに、コレクション・オブジェクトへのバインディングによって得られるデータを表示するという役割も持っているのだ。

 固定的なコンテンツを表示する際には、コンテンツ・プロパティであるItemsプロパティを使用するが、バインディングを利用する際にはItemsSourceプロパティに対してコレクション・オブジェクトを設定する方法を取る。既定値であるnull参照(VBではNothing)以外にItemsSourceプロパティが設定された場合、Itemsプロパティの設定値は無効となる。なお、実行時にはItemsSourceプロパティに設定されたコレクションの中身が、Itemsプロパティ内のItemCollectionオブジェクトにコピーされることになる。

 ここではItemsControlの1つであるListBoxコントロールを例に解説する。以下にサンプルとなるUI部分(XAML)とコードビハインド部分(C#/VB)のコードを示す。

<Grid x:Name="MainPanel" Loaded="MainPanel_Loaded">
  <ListBox ItemsSource=" {Binding}"
    DisplayMemberPath="Bmi"/>
</Grid>

ターゲットとなるListBoxコントロールを含むUI部分(XAML)

private void MainPanel_Loaded(object sender, RoutedEventArgs e)
{
  MainPanel.DataContext =
    new ObservableCollection<Person> {
      new Person(1.7, 60),
      new Person(1.6, 70),
      new Person(1.5, 40)
    };
}

Private Sub MainPanel_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
  Dim bindingSource As New ObservableCollection(Of Person)
  bindingSource.Add(New Person(1.7, 60))
  bindingSource.Add(New Person(1.6, 70))
  bindingSource.Add(New Person(1.5, 40))
  MainPanel.DataContext = bindingSource
End Sub

ObservableCollectionオブジェクトを生成し、DataContextへの設定を行うコードビハインド部分(上:C#、下:VB)
Collections.ObjectModel名前空間のインポートが必要。

 これまでPersonオブジェクトを設定してきたDataContextプロパティには、ObservableCollection<Person>(VBの場合、ObservableCollection(Of Person))オブジェクトを設定している。

 ObservableCollection<T>クラスはList<T>クラスとよく似たクラスであるが、INotifyPropertyChangedインターフェイスとINotifyCollectionChangedインターフェイスがあらかじめ実装されているという点で、バインディングのソースとして使用するのに適したクラスとなっている。

 INotifyCollectionChangedインターフェイスにはCollectionChangedイベントが定義されており、ObservableCollection<T>クラスでは、コレクションに変更が生じた際にCollectionChangedイベントが発生するように実装されている。これにより、Bindingオブジェクトに対してコレクションの変更通知が行われ、ターゲットであるItemsControl側の表示に反映されるようになっている。

 ところで、ItemsSourceプロパティのBindingの記述ではPathプロパティを指定していないが、これはバインドの対象をDataContextから取得するソース・オブジェクト全体、つまりObservableCollectionオブジェクトにするためだ。Pathプロパティの値が既定値であるnull参照(VBではNothing)か、「.(ピリオド)」の場合、バインド対象はソース・オブジェクト全体となる。

 この場合には、コレクション・オブジェクトの各項目(今回の例ではPersonオブジェクト)が持つプロパティから、表示したいプロパティ(今回の例ではBmi)をItemsControl.DisplayMemberPathプロパティに設定することで、そのプロパティの値が表示される。

 なお今回は、以前のサンプルのように[計算]ボタンを押してBMI値を算出するわけではないため、Personクラスに下記のようなコンストラクタを追加し、初期化時にBmiプロパティの値が算出されるように変更した。

public Person(double height, double weihgt)
{
  this.Height = height;
  this.Weight = weihgt;
  this.Calculate();
}

Public Sub New(ByVal height As Double, ByVal weight As Double)
  Me.Height = height
  Me.Weight = weight
  Me.Calculate()
End Sub

追加するPersonクラスのコンストラクタ部分(上:C#、下:VB)

 実行すると、下の画面のようにコレクション・オブジェクトの各項目におけるBmiプロパティの値が、縦方向にリストとして表示される。

DisplayMemberPathプロパティを用いたコレクションへのバインドの実行結果

 DisplayMemberPathプロパティを使う場合、表示できる各項目のプロパティは1つだけであり、なおかつ表示は文字列に限られる。複数のプロパティや文字列以外の表現が必要な場合には、データ・テンプレート機能を利用する。次にこれを説明する。

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。