第2回 データの表示と入力に必要な知識:連載 WPF/Silverlight UIフレームワーク入門(3/5 ページ)
UIレイアウトの次は、業務アプリには不可欠の「データの表示と入力」の実装だ。それを簡単に実現できるデータ・バインディングについて解説。
●双方向データ・バインディング
ここでは、先ほどまでのサンプルを拡張して作成した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値は「体重÷身長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>
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プロパティ設定のほかに、ボタンのClickイベント・ハンドラでPersonオブジェクトのCalculateメソッドを実行する処理を新たに記述している。
では、実際に実行して動作を確認してみよう。
期待される動作は、
- TextBoxコントロールに身長と体重を入力すると、入力した値が双方向バインディングによってPersonオブジェクトのHeightプロパティとWeightプロパティに反映される
- [計算]ボタンを押すと、PersonオブジェクトのCalculateメソッドが実行され、HeightプロパティとWeightプロパティの値から計算されたBMI値がBmiプロパティの値として設定される
- 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インターフェイスはPropertyChangedイベントを持つだけの非常にシンプルなインターフェイスである。
※System.ComponentModel名前空間のインポートが必要。
このような実装により、ソース側からBindingオブジェクトに対してプロパティ値の変更通知が行われるようになり、値変更時にもターゲット側と同期が取られるようになる。
再度実行してみると、下の画面のように、変更されたBmiプロパティの値がTextBlockコントロールに表示されるのを確認できる。
続いて、データ型の明示的な変換方法と、コレクション・オブジェクトに対するバインドについて解説する。
Copyright© Digital Advantage Corp. All Rights Reserved.