データ・バインディングを理解する

データ・バインディングとは

バインディングには「結合」という意味があり、データ・バインディングとは、MVVMパターンでいうViewとViewModelを結び付けるために提供されている仕組みのことです。
データ・バインディングの特徴は、一度だけ値を代入して終わりというわけではなく、値が変化するたびに結び付いている先の値もその都度即座に変化することです。
MVVMパターンにおけるXamlはView部分に相当しますが、Xaml内に{Binding プロパティ名}を記述し、ViewModelが保持するプロパティ値と結び付けることで、ViewにViewModelの値を反映することができます。

f:id:marikooota:20170530001607p:plain

では実際のところどのようにして、ViewとViewModelの値をやりとりしているのでしょうか。

DataContextを介して値をやりとりする

ViewとViewModel間のやりとりはDataContextというプロパティを介してやりとりします。
Viewで表示したいViewModelのデータ・ソースをDataContextプロパティに渡すだけで、結び付けることができるのです。

WPFのアプリケーションにはUI層とデータ層の2つの層があり、UI層はMVVMパターンでいうViewで構成されます。
データ層はMVVMパターンでいうViewModelとModelで構成され、最初はnullとして始まり、DataContextプロパティを使用することで値を設定できます。
Button,Label,DataGrid,WindowなどのUIオブジェクトは、DataContextを介すことで簡単にやりとりすることができます。

DataContextはこのように設定します。

Xamlで設定する場合
(名前空間xmlns:vm="clr-namespace:WpfApp.ViewModelを指定しておく。)

<Window.DataContext>
  <vm:MainViewModel />
</Window.DataContext>

コードビハインドで設定する場合
(usingにWpfApp.ViewModel;を追加しておく。)

this.DataContext = new MainViewModel();

XamlでDataContextの設定を行う時は、初期値の設定など細かい設定は行えませんが、コードビハインドで行う場合は以下のように初期値の設定を行うことができます。
InitializeComponent();の下に記述します。

var viewmodel = new MainViewModel()
{
  hoge = "hogehoge"
};
this.DataContext = viewmodel;

では実際のアプリケーションではどのように使うのか、次に説明していきます。

コードを見ながらデータ・バインディングを理解する

ここからは、ViewとViewModelを使ったデータ・バインディングのサンプルコードを見ながら理解を深めていきます。
ボタンをクリックすると、カウントアップする簡単なアプリケーションです。

1.まずはXaml(View)からです。
XamlのTextBlockには{Binding プロパティ名}と書いておき、ここにViewModelプロパティ値を表示する!というように宣言しておきます。

MainWindow.xaml
<StackPanel>
    <TextBlock Text="{Binding Count}"/>
    <Button x:Name="CountButton" Click="CountUp_Click">Count Up!</Button>
</StackPanel>

2.次にViewModelです。
ViewModelにはViewに表示するプロパティ名を定義しておきます。
Bindingを使うためのDataContextとなるクラスのメンバ変数は常にgetter/setterを持ったプロパティでなければいけません。
なので、このように定義します。

private int countVal;
public int Count
{
    get { return countVal; }
    set { countVal = value; }
}

でも、これだけでは値に変化があったときにViewの値も更新するようなコードを書いていないので、バインディングで値が反映されることはありません。
なので、ViewModelのプロパティ値に変化があった時に通知するようなイベントを用意します。

INotifyPropertyChanged(System.ComponentModel名前空間)というインターフェイスを継承して「プロパティ値が変更されたことをクライアントに通知する」イベントを実装しましょう。

MainViewModel.cs
public class MainViewModel : INotifyPropertyChanged
{
    private int countVal;
    public int Count
    {
        get { return countVal; }
        set
        {
            countVal = value;
            // 通知するプロパティを引数に指定する。
            // これを書かないと通知されない。
            NotifyPropertyChanged("Count");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void NotifyPropertyChanged(String info)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
    }
}

3.最後にコードビハインドです。 DataContextに初期値として0を設定し、ボタンクリックのイベントにインクリメントの処理を書いておきます。
ViewModelのプロパティ値をインクリメントさせると、バインディングで結び付いている先のViewの値もインクリメントします。

MainWindow.xaml.cs
public partial class MainWindow : Window
{
    private MainViewModel viewmodel;
    public MainWindow()
    {
        InitializeComponent();
        var viewmodel = new MainViewModel()
        {
            Count = 0
        };
        this.DataContext = viewmodel;
    }
    
    private void CountUp_Click(object sender, RoutedEventArgs e)
    {
        viewmodel.Count++;
    }
}

以上でサンプルは完成です。
ボタンをクリックするとテキストブロックの数値がカウントアップされたらOKです。

まとめ
  • データ・バインディングとはViewとViewModelを結び付けるために提供されている仕組み
  • Viewに{Binding プロパティ名}と書き、ViewModelで定義しているプロパティ名と結び付ける
  • ViewとViewModelの値はDataContextを介してやりとりする
  • ViewModelのプロパティ値に変化があったことを通知するINotifyPropertyChangedインターフェースを継承し、「プロパティ値が変更されたことをクライアントに通知する」イベントを実装する

とても参考になったサイト
サンプルコードを見ながら理解するMVVMの基礎的な実装 - Neutral Scent
【WPF基礎】脱WPF初心者のための基礎知識 その1〜DataContextってなんぞ?〜: おっさんどりーむ 〜日本語で理解するプログラミング技術〜
連載:WPF入門:第5回 WPFの「データ・バインディング」を理解する (1/3) - @IT