本記事は前回の続きとなっているため、初見の方は以下の記事を参照ください。
データバインドしない場合
データバインドの説明をする前に、データバインドをしない場合のコードを一緒に見ていきましょう。
それぞれ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を活用することを忘れないでください。

