本記事は前回の続きとなっているため、初見の方は以下の記事を参照ください。
データバインドしない場合
データバインドの説明をする前に、データバインドをしない場合のコードを一緒に見ていきましょう。
それぞれFormのテキストボックスや、ラベルにViewModelより取得した値を設定します。
using System; using System.Windows.Forms; namespace FormTestSample { public partial class Form1View : Form { private Form1ViewModel _viewModel = new Form1ViewModel(); public Form1View() { InitializeComponent(); } private void CalculationButton_Click(object sender, EventArgs e) { _viewModel.InputTextBox1Text = InputTextBox1.Text; _viewModel.InputTextBox2Text = InputTextBox2.Text; _viewModel.multiplication(); this.AnsLabel.Text = _viewModel.AnsLabelText; } } }
実行すると…
一応期待通りに動作することが分かります。
しかし、この実装方法は非常にめんどくさいし、バグが入り込みやすいです。
ボタンを押すたびに、ViewModelの値を画面のコントロールと同じ値にしてmultiplicationの結果を画面のコントロールと同じ状態にしています。
かなりイケてない作りになってしまっています。
これを解消する方法が「データバインド」です。
データバインド
画面のInputTextBox1とViewModelのInputTextBox1Textを常に同期している状態をつくることが「データバインド」です。
それでは実装をしてみましょう。
using System; using System.Windows.Forms; namespace FormTestSample { public partial class Form1View : Form { private Form1ViewModel _viewModel = new Form1ViewModel(); public Form1View() { InitializeComponent(); InputTextBox1.DataBindings.Add("Text", _viewModel, "InputTextBox1Text"); InputTextBox2.DataBindings.Add("Text", _viewModel, "InputTextBox2Text"); AnsLabel.DataBindings.Add("Text", _viewModel, "AnsLabelText"); } private void CalculationButton_Click(object sender, EventArgs e) { _viewModel.multiplication(); } } }
Form1Viewのコンストラクタにデータバインドのロジックを追加します。
DataBindings.Add([Formの値プロパティ], [ViewModelのインスタンス], [ViewModelのプロパティ名])より紐づけができます。
実行すると…
データバインドするためにはもう2つ仕掛けが必要です。
その1.IPropertyChanged
ViewModelにプロパティ変更時の処理を追加します。
using System; using System.ComponentModel; namespace FormTestSample { public class Form1ViewModel :INotifyPropertyChanged { public string InputTextBox1Text { get; set; } = string.Empty; public string InputTextBox2Text { get; set; } = string.Empty; public string AnsLabelText { get; set; } = string.Empty; public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChanged(string propertyName) { if(PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } public void multiplication() { int left = Convert.ToInt32(InputTextBox1Text); int right = Convert.ToInt32(InputTextBox2Text); AnsLabelText = (left * right).ToString(); } } }
このように書くと、「OnPropertyChanged」が呼ばれたタイミングでPropertyChangedが発火して、テキストボックスやラベルの値と
public string InputTextBox1Text { get; set; } = string.Empty; public string InputTextBox2Text { get; set; } = string.Empty; public string AnsLabelText { get; set; } = string.Empty;
これらViewModelの内容と同期されます。
しかし、これだけ書いても「OnPropertyChanged」が呼ばれていないので何も起きません。
その2.OnPropertyChanged
OnPropertyChangedを呼ぶところですが、メソッドの中など処理の「動き」がある個所には、漏れや読みにくいソースとなるため書かないほうが良いです。
基本的にはプロパティに追加します。
このような場合にgetterとsetterを活用します。
値の取得時(getter)には初期値(string.Empty)を、値の設定時(setter)には画面で入力された値(value)を設定します。
private string _inputTextBox1Text = string.Empty; public string InputTextBox1Text { get { return _inputTextBox1Text; } set { if (_inputTextBox1Text == value) { return; } _inputTextBox1Text = value; OnPropertyChanged("InputTextBox1Text"); } }
if (_inputTextBox1Text == value) としている理由は、値が変更されない場合も想定されるので、効率を考慮し、値が変わらない場合は変更しないようにしました。
このようにすることで、値に変更があれば動的にプロパティ変更されるようになりました。
他のプロパティも同様に修正しましょう。
using System; using System.ComponentModel; namespace FormTestSample { public class Form1ViewModel :INotifyPropertyChanged { private string _inputTextBox1Text = string.Empty; public string InputTextBox1Text { get { return _inputTextBox1Text; } set { if (_inputTextBox1Text == value) { return; } _inputTextBox1Text = value; OnPropertyChanged("InputTextBox1Text"); } } private string _inputTextBox2Text = string.Empty; public string InputTextBox2Text { get { return _inputTextBox2Text; } set { if (_inputTextBox2Text == value) { return; } _inputTextBox2Text = value; OnPropertyChanged("InputTextBox2Text"); } } private string _ansLabelText = string.Empty; public string AnsLabelText { get { return _ansLabelText; } set { if (InputTextBox2Text == value) { return; } _ansLabelText = value; OnPropertyChanged("AnsLabelText"); } } public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChanged(string propertyName) { if(PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } public void multiplication() { int left = Convert.ToInt32(InputTextBox1Text); int right = Convert.ToInt32(InputTextBox2Text); AnsLabelText = (left * right).ToString(); } } }
正しく同期できていることが分かりました。
まとめ
今回は、ViewとViewModelを同期する「データバインド」について説明しました。
これはWindowsフォームアプリケーション開発では当たり前に使われる方法です。
データバインドするためには、IPropertyChangedのインタフェースを忘れないことと、getter, setterを活用することを忘れないでください。