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

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

双方向データ・バインディング

 ここでは、先ほどまでのサンプルを拡張して作成したBMI算出アプリケーションを例に取り、双方向バインディングを解説する。BMI算出アプリケーションの構成を下の図に示す。

双方向バインディングの構成図
PersonオブジェクトのHeightプロパティとWeightプロパティは、値の入力が可能な2つのTextBoxオブジェクトにバインドし、表示のみのBmiプロパティはTextBlockオブジェクトにバインドしている。

 これまでTextBlockコントロールのTextプロパティとデータバインド(以下、バインド)していたHeightプロパティとWeightプロパティは、UI側から身長と体重を入力するために、TextBoxコントロールのTextプロパティとバインドするように変更している。もちろん、データフロー方向を設定するBindingオブジェクトのModeプロパティの値は「TwoWay」だ。

 また、Personクラスには新たにBmiプロパティとCalculateメソッドを追加し、CalculateメソッドはHeightプロパティとWeightプロパティの値から計算したBMI値をBmiプロパティに設定するという実装にしている。

 まず、Personクラスの具体的なC#/VBコードは下記のようになる。

class Person
{
  public double Height { get; set; }
  public double Weight { get; set; }
  public double Bmi { get; private set; }

  public void Calculate()
  {
    Bmi = Weight / Math.Pow(Height, 2);
  }
}

Public Class Person

  Private _height As Double
  Private _weight As Double
  Private _bmi As Double

  Public Property Height() As Double
    Get
      Return _height
    End Get
    Set(ByVal value As Double)
      _height = value
    End Set
  End Property

  Public Property Weight() As Double
    Get
      Return _weight
   End Get
    Set(ByVal value As Double)
      _weight = value
    End Set
  End Property

  Public Property Bmi() As Double
    Get
      Return _bmi
    End Get
    Private Set(ByVal value As Double)
      _bmi = value
    End Set
  End Property

  Public Sub Calculate()
    Bmi = Weight / Math.Pow(Height, 2)
  End Sub

End Class

BmiプロパティとCalculateメソッドが追加されたPersonクラスのコード(上:C#、下:VB)
BMI値は「体重÷身長2」で求められる。

 そして、UI部分のXAMLコードは下記のようになる。

<StackPanel 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"/>
  <TextBlock Margin="10" Text="{Binding Bmi, Mode=OneWay}"/>           
</StackPanel>

身長と体重を入力するTextBoxコントロールを2つ、計算を実行するButtonコントロールを1つ、BMI値を表示するTextBlockコントロールを1つを配置(XAML)

 Bindingオブジェクトは、TextBox要素のText属性においてModeプロパティにTwoWayが指定されているだけで大きな違いはない。Bmiプロパティは計算された値を表示するだけであるため、OneWayとしている。そして、ButtonコントロールのClickイベントにButton_Clickメソッドを登録している。

 ページのロード時に実行されるMainPanel_Loadedメソッドと、ボタンのクリック時に実行されるButton_Clickメソッドは次のようになる。

private void MainPanel_Loaded(object sender, RoutedEventArgs e)
{
  MainPanel.DataContext = new Person();
}

private void Button_Click(object sender, RoutedEventArgs e)
{
  ((Person)MainPanel.DataContext).Calculate();
}

Private Sub MainPanel_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
  MainPanel.DataContext = New Person()
End Sub

Private Sub Button_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
  DirectCast(MainPanel.DataContext, Person).Calculate()
End Sub

DataContextプロパティの設定とCalculateメソッドの実行を行うコードビハインド部分(上:C#、下:VB)

 コードビハインド部分では、先ほどまでと同様のDataContextプロパティ設定のほかに、ボタンのClickイベント・ハンドラでPersonオブジェクトのCalculateメソッドを実行する処理を新たに記述している。

 では、実際に実行して動作を確認してみよう。

 期待される動作は、

  1. TextBoxコントロールに身長と体重を入力すると、入力した値が双方向バインディングによってPersonオブジェクトのHeightプロパティとWeightプロパティに反映される
  2. [計算]ボタンを押すと、PersonオブジェクトのCalculateメソッドが実行され、HeightプロパティとWeightプロパティの値から計算されたBMI値がBmiプロパティの値として設定される
  3. TextBlockコントロールのTextプロパティはPersonオブジェクトのBmiプロパティとバインドされているため、Bmiプロパティの値が反映されTextBlockコントロールにBMI値が表示される

 というものだが、実際には下の画面のように、BMIの値は「0」としか表示されない。

双方向バインディングの実行結果

 実は、上記のような実装の場合、ターゲットからソース方向への値の反映は行われるが、その逆であるソースからターゲット方向への値の反映は行われない。これはBindingオブジェクトに対して、ソースの値の変更が通知されていないことが原因である。従って、ソースが変更されたことを明示的にBindingオブジェクトに伝える必要がある。

ソースの値変更を通知するためのINotifyPropertyChangedインターフェイス

 ソースの値が変更されたことをBindingオブジェクトに通知するためには、PersonクラスにINotifyPropertyChangedインターフェイスを実装し、値の変更時にPropertyChangedイベントを発生させる必要がある。なお、ターゲット側は依存関係プロパティであるため、このような実装が不要となっている。

 以下が、INotifyPropertyChangedインターフェイスを実装したPersonクラスである。

class Person : INotifyPropertyChanged
{
  double _bmi;

  public double Height { get; set; }
  public double Weight { get; set; }
  public double Bmi
  {
    get { return _bmi; }
    private set
    {
      _bmi = value;
      OnPropertyChanged("Bmi");
    }
  }

  public void Calculate()
  {
    Bmi = Weight / Math.Pow(Height, 2);
  }

  #region INotifyPropertyChanged メンバ

  public event PropertyChangedEventHandler PropertyChanged;

  protected virtual void OnPropertyChanged(string propertyName)
  {
    PropertyChangedEventHandler handler = this.PropertyChanged;
    if (handler != null)
      handler(this, new PropertyChangedEventArgs(propertyName));
  }

  #endregion

Public Class Person
  Implements INotifyPropertyChanged

  Private _height As Double
  Private _weight As Double
  Private _bmi As Double

  Public Property Height() As Double
    Get
      Return _height
    End Get
    Set(ByVal value As Double)
      _height = value
    End Set
  End Property

  Public Property Weight() As Double
    Get
      Return _weight
    End Get
    Set(ByVal value As Double)
      _weight = value
    End Set
  End Property

  Public Property Bmi() As Double
    Get
      Return _bmi
    End Get
    Private Set(ByVal value As Double)
      _bmi = value
      OnPropertyChanged("Bmi")
    End Set
  End Property

  Public Sub Calculate()
    Bmi = Weight / Math.Pow(Height, 2)
  End Sub

  Public Event PropertyChanged(ByVal sender As Object, ByVal e As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged

  Protected Overridable Sub OnPropertyChanged(ByVal propertyName As String)
    RaiseEvent PropertyChanged( _
      Me, New PropertyChangedEventArgs(propertyName))
  End Sub

End Class

INotifyPropertyChangedインターフェイスを実装したPersonクラス(上:C#、下:VB)
__部分が追加したコード。INotifyPropertyChangedインターフェイスはPropertyChangedイベントを持つだけの非常にシンプルなインターフェイスである。
System.ComponentModel名前空間のインポートが必要。

 このような実装により、ソース側からBindingオブジェクトに対してプロパティ値の変更通知が行われるようになり、値変更時にもターゲット側と同期が取られるようになる。

 再度実行してみると、下の画面のように、変更されたBmiプロパティの値がTextBlockコントロールに表示されるのを確認できる。

PersonクラスにINotifyPropertyChangedインターフェイスを実装した場合の双方向バインディングの実行結果

 続いて、データ型の明示的な変換方法と、コレクション・オブジェクトに対するバインドについて解説する。

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

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

メールマガジン登録

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