7.3 バインディング条件
7.3.1 バインディングの流れを制御する
これまでのバインディングは、データソースが変更されると、自動的にターゲットのプロパティも変更するというものでした。そのため、データソースに値を設定するコードは明示的に記述しなければなりません。
しかし、これまでのようにテキストボックスに入力されたテキストが、直接データソースのプロパティに関連付けられている場合、テキストボックスのテキストプロパティが変更されると、自動的にデータソースのプロパティが更新されるというバインディングが要求されます。やはり、この場合もイベントを記述するより、直接バインディングさせてしまったほうが効率的で確実になります。
バインディングの流れは、片方向だけではありません。ターゲットとソースの間のプロパティの変更を双方向で監視する仕組みも存在しています。ラベルのように、ユーザーによって編集されることはないコントロールに対しては、通常はソースからターゲットへの片方向となりますが、テキストボックスのような編集可能なコントロールには双方向でバインディングされます。このような、バインディングの流れは Mode プロパティから設定することができます。
public BindingMode Mode { get; set; }
Mode プロパティには、System.Windows.Data.BindingMode 列挙体のいずれかのメンバを設定することができます。
public enum BindingMode
この列挙体には、表1のようなメンバが宣言されています。Mode プロパティには、既定で Default メンバが設定されています。
メンバ | 意味 |
---|---|
OneWay | ソースプロパティが変更されたとき、ターゲットプロパティを更新する |
OneWayToSource | ターゲットプロパティが変更されたとき、ソースプロパティを更新する。 |
TwoWay | ソースプロパティが変更されたとき、ターゲットプロパティを更新する。またはターゲットプロパティが変更されたとき、ソースプロパティを更新する。 |
OneTime | アプリケーション起動時、または DataContext プロパティが変更されたとき、ターゲットプロパティを更新する。 |
Default | 既定の動作。 |
Default が設定されている場合、ユーザーが GUI 上から変更できない多くのプロパティに対しては OneWay が適用されていますが、テキストボックスやチェックボックスのような、ユーザーがプロパティを編集可能なコントロールに対しては TwoWay が適用されます。
TwoWay または OneWayToSource を採用する場合、何を持ってターゲットプロパティが変更されたと判定するかどうかが問題になります。通常は、ターゲットプロパティの変更時にバインディングを行えばよいと考えるかもしれませんが、一般的なフォームのデザインでは、編集可能なコントロールの変更ではなく、最終的に「OK」ボタンを押したタイミングでバインディングを行うべきだと考えられるでしょう。
例えば、テキストボックスを編集すると Text プロパティが変更されますが、このタイミングでソースプロパティを更新していては効率が悪いと考えられます。特に、ソースがネットワークやデータベースなど、比較的低速な空間に配置されていたり、可能な限りパフォーマンスを最適化させなければならない類のデータである場合は重要になります。
ソースプロパティを更新するタイミングは、UpdateSourceTrigger プロパティで設定することができます。
public UpdateSourceTrigger UpdateSourceTrigger { get; set; }
UpdateSourceTrigger プロパティには、System.Windows.Data.UpdateSourceTrigger 列挙体のいずれかのメンバを設定します。
public enum UpdateSourceTrigger
UpdateSourceTrigger 列挙体のメンバは、表2のようなものがあります。
メンバ | 意味 |
---|---|
Explicit | UpdateSource() メソッドが呼び出されたとき、バインディングソースを更新する。 |
LostFocus | バインディングターゲットの要素がフォーカスを失ったとき、バインディングソースを更新する。 |
PropertyChanged | バインディングターゲットのプロパティが変更されたとき、バインディングソースを更新する。 |
Default | 既定の動作。 |
Default メンバを選択した場合、多くの依存プロパティに対しては PropertyChanged メンバの動作が適用されますが、Text プロパティに対しては LostFocus メンバが採用されます。
using System; using System.ComponentModel; using System.Windows; using System.Windows.Data; using System.Windows.Controls; class User : INotifyPropertyChanged { private string name, address; public event PropertyChangedEventHandler PropertyChanged; public string Name { set { name = value; OnPropertyChanged("Name"); } get { return name; } } public string Address { set { address = value; OnPropertyChanged("Address"); } get { return address; } } protected void OnPropertyChanged(string name) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(name)); } } class Test { [STAThread] public static void Main() { User user = new User(); user.Name = "名前"; user.Address = "住所"; Label nameLabel = new Label(); nameLabel.SetBinding(ContentControl.ContentProperty, "Name"); Label addrLabel = new Label(); addrLabel.SetBinding(ContentControl.ContentProperty, "Address"); TextBox nameTextBox = new TextBox(); nameTextBox.Margin = new Thickness(10); nameTextBox.SetBinding(TextBox.TextProperty, "Name"); TextBox addrTextBox = new TextBox(); addrTextBox.Margin = new Thickness(10); addrTextBox.SetBinding(TextBox.TextProperty, "Address"); StackPanel panel = new StackPanel(); panel.Children.Add(nameLabel); panel.Children.Add(addrLabel); panel.Children.Add(nameTextBox); panel.Children.Add(addrTextBox); panel.DataContext = user; Window wnd = new Window(); wnd.Content = panel; Application app = new Application(); app.Run(wnd); } }
コード1は「7.1 暗黙のデータソース コード1」に非常に良く似た実行結果となるプログラムです。外見や挙動はほぼそのままですが、テキストボックスに入力したテキストがリアルタイムでラベルと同期にするのではなく、テキストボックスがキーボードフォーカスを失った時点でラベルが更新されます。これは、TextBox クラスの Text プロパティに対する Binding オブジェクトの既定の動作です。
このプログラムでは、明示的に Binding オブジェクトを生成していませんが、これまでのプログラムとは異なり TextBox オブジェクトにもバインディングを設定しています。Text プロパティの変更イベントを使っていないことに注目してください。このプログラムは Binding オブジェクトの働きだけでデータの変更を適切に管理しています。Binding オブジェクトを明示的に生成し、UpdateSourceTrigger プロパティを変更すれば、ソースのプロパティを更新するタイミングなどを具体的に設定することも可能です。
前述したように、ユーザーの編集に合わせてリアルタイムでデータソースを更新する必要がない場合、一般的なデザインでは「OK」ボタンを押したときにデータを確定してバインディングするべきです。そのような場合は、Explicit メンバを設定し、SetBinding() メソッドが返した BindingExpressionBase オブジェクトの UpdateSource() メソッドを使って一度に更新するべきです。
public virtual void UpdateSource ()
UpdateSource() メソッドは、ターゲットプロパティの値でソースプロパティの値を明示的に更新します。このメソッドは Mode プロパティが TwoWay または OneWayToSource のときに使用します。
UpdateSource() とは逆に、ソースプロパティの値でターゲットプロパティを更新する UpdateTarget() メソッドも用意されています。
public virtual void UpdateTarget ()
バインドしているデータをリアルタイムで更新させる必要がなく、「OK」ボタンなど、最終的な処理の更新を許可する操作が行われた時点で全てのデータを更新させる処理に必要になります。手動でデータの更新を行う場合に利用してください。
using System; using System.Windows; using System.Windows.Data; using System.Windows.Controls; class User { private string name; public string Name { set { name = value; } get { return name; } } } class Test : Window { [STAThread] public static void Main() { Window wnd = new Test(); Application app = new Application(); app.Run(wnd); } private User user; private Label label; private TextBox textBox; private Button button; private BindingExpressionBase labelBindingExpr, textBoxBindingExpr; public Test() { user = new User(); Binding bind1 = new Binding(); bind1.Mode = BindingMode.OneWay; Binding bind2 = new Binding(); bind2.Mode = BindingMode.OneWayToSource; bind2.UpdateSourceTrigger = UpdateSourceTrigger.Explicit; label = new Label(); labelBindingExpr = label.SetBinding(ContentControl.ContentProperty, "Name"); textBox = new TextBox(); textBox.Margin = new Thickness(10); textBox.Text = "名前を入力してください"; textBoxBindingExpr = textBox.SetBinding(TextBox.TextProperty, "Name"); button = new Button(); button.Content = "OK"; button.Click += buttonClick; StackPanel panel = new StackPanel(); panel.Children.Add(label); panel.Children.Add(textBox); panel.Children.Add(button); panel.DataContext = user; Content = panel; } private void buttonClick(Object sender, RoutedEventArgs e) { textBoxBindingExpr.UpdateSource(); labelBindingExpr.UpdateTarget(); } }
コード2は、ラベルとテキストボックスに Binding オブジェクトを設定していますが、このプログラムではバインディングソースやターゲットのプロパティの変更で自動的に更新されることはありません。バインディングソースの User クラスにはプロパティの変更を通知する仕組みを実装していませんし、テキストボックスに設定している Binding オブジェクトの UpdateSourceTrigger プロパティには、Explicit が設定されているため UpdateSource() メソッドの呼び出し以外で更新されることはありません。