第4章で取り上げたように、データバインディングを用いることで、ビューモデルのプロパティの値を、ビュー上のコントロールのプロパティに簡単に反映させることができます。例として、書籍のタイトルと価格を保持するBookクラスを考えてみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
using System.ComponentModel; using System.Runtime.CompilerServices; namespace TwoWayBindingSample { public class Book : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; private void NotifyPropertyChanged([CallerMemberName] string name = null) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(name)); } private string _Title; public string Title { get { return _Title; } set { if (value == _Title) return; _Title = value; NotifyPropertyChanged(); } } private decimal _Price; public decimal Price { get { return _Price; } set { if (value == _Price) _Price = value; _Price = value; NotifyPropertyChanged(); } } } } |
これを以下のようなビューを用いて表示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
<Page x:Class="TwoWayBindingSample.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:TwoWayBindingSample"> <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center"> <StackPanel Orientation="Horizontal" /> <TextBlock Text="題名" VerticalAlignment="Center" /> <TextBox Width="300" Margin="5" Text="{Binding Title}" /> </StackPanel> <StackPanel Orientation="Horizontal"> <TextBlock Text="価格" VerticalAlignment="Center" /> <TextBox Width="100" Margin="5" Text="{Binding Price}" /> </StackPanel> </StackPanel> </Grid> </Page> |
ビューモデルの初期化は以下のようにMainPageのコンストラクタで行っておきましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
using Windows.UI.Xaml.Controls; namespace TwoWayBindingSample { public sealed partial class MainPage : Page { public MainPage() { this.InitializeComponent(); this.DataContext = new Book() { Title = "Windows 8本格プログラミング入門", Price = 2800 }; } } } |
アプリをデバッグモードで起動すると、書籍情報が以下のように表示されます。
ここまでは第4章でみたとおりです。
さて、このビューをユーザーインターフェイスとして用い、ビューモデルの値を編集することを考えてみます。ビューモデルのそれぞれのプロパティがTextBoxのTexstプロパティにバインドされているのだから、テキストを編集すればTextBoxのプロパティが変更されて、それが自動的にビューモデルのプロパティに反映されるのでは?と思った方がいるかもしれません。もし、データバインディングがそのような挙動を示すのであれば大変うれしいのですが、そのような振る舞いをするのでしょうか?
実験で確かめてみましょう。ビューモデルのTitleプロパティのSetメソッドの先頭の箇所に、デバッガーでブレークポイントを設定します。
デバッグ起動したのち、TextBoxに表示されている書籍の題名を編集し、TABキーを押して変更を確定させてください。ここでもし、TexstBoxのTextプロパティの変更がビューモデルに書き戻されているのであれば、ビューモデルのプロパティのSetメソッドが呼ばれるので、先ほど設定したブレークポイントでアプリケーションの実行が一時中断するはずです。
中断しましたか?しませんでしたね。特別に指定しなければ、データバインディングは一方通行で作用します。このため、期待したいような振る舞いにはならないのです。
ところで、「特別に指定しなければ」とあるように、実はデータバインディングを双方向で作用するように、指定することもできます。
1 |
<TextBox Width="300" Margin="5" Text="{Binding Title, Mode=TwoWay}" /> |
上記のようにMode=TwoWayの指定を追加した上で、もう一度、書籍の題名を変更してみてください。今度はTitleプロパティのSetメソッドに設定したブレークポイントで実行が中断されるはずです。
さて、TwoWayモードを指定した双方向バインディングを利用する上で注意すべきことが一点あります。書籍の価格を表示しているTextBoxについてもTwoWayを指定し、編集した金額がビューモデルのプロパティPriceに反映されるか同様に実験してみてください。
どうもデバッガーで設定したブレークポイントがヒットしていない(=変更が反映されていない)模様です。何が起こっているのでしょうか。ここで、Visual StudioのOutputウィンドウに目を向けてください。 以下のようなエラーメッセージに気がつくでしょう。
1 2 3 4 |
Error: Cannot save value from target back to source. BindingExpression: Path='Price' DataItem='TwoWayBindingSample.Book, TwoWayBindingSample, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'; target element is 'Windows.UI.Xaml.Controls.TextBox' (Name='null'); target property is 'Text' (type 'String'). |
このエラーは、ビューモデルとビューのプロパティの型が異なるために発生しています。ここまで明示的には書いていませんでしたが、バインド元(ソース)のプロパティとバインド先(ターゲット)のプロパティの型が異なる場合、基本的になんらかの形でその変換方法を指定する必要があります。唯一の例外はターゲットの型が文字列(string)型の場合です。この場合、ソースのオブジェクトのToStringメソッドが呼び出され、文字列に変換されます。
双方向バインディングを用いる場合、ターゲットからソースへの書き戻しが発生しますが、この時にも同様の原則があてはまります。上記の例ではPriceプロパティがdecimal型のため、文字列のTextプロパティの値をどのように書き戻せばよいのかを指定しない限り、エラーとなってしまいます。
このような場合、双方向の変換を明示的に行うコンバーターを実装し、それをバインディング時に指定する必要があります。ここでは以下のようなコンバーターを実装してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
using System; using Windows.UI.Xaml.Data; namespace TwoWayBindingSample { public class DecimalToString : IValueConverter { public object Convert( object value, Type targetType, object parameter, string language) { return value.ToString(); } public object ConvertBack( object value, Type targetType, object parameter, string language) { return decimal.Parse(value.ToString()); } } } |
このコンバーターを静的リソースとして定義し、バインディング時に使用します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<Page .... <Page.Resources>; <local:DecimalToString x:Key="d2s" /> </Page.Resources> .... <TextBox Width="100" Margin="5" Text="{Binding Price, Mode=TwoWay, Converter={StaticResource d2s}}" />; .... </Page> |
この状態で再度実験してみてください。今度はPriceプロパティへの書き戻しが行われていることを確認できることかと思います。
サンプルコードはgithubから入手可能です。