プログラムを中心とした個人的なメモ用のブログです。 タイトルは迷走中。
内容の保証はできませんのであしからずご了承ください。

2016/09/12

WPF + MVVM の勉強2:プロパティの変更通知を実装する

update2017/11/17 event_note2016/09/11 15:22

C# も WindowsForms もちょっとしか触ったことがない人が、WPF + MVVM でアプリケーションを作成するために勉強したことをまとめてみる記事2回目です。
間違っているところがあれば指摘していただけると嬉しいです。

前回の記事で、データバインディングにより XAML のコントロールとプロパティの関連付けを行いました。
しかし、これだけではプロパティが変更された場合にコントロールの値は変更されませんでした。
そのためにはプロパティが変更されたことをコントロールに通知する仕組みが必要です。
そしてこれは ViewModel に INotifyPropertyChanged を実装することで実現するそうです。

INotifyPropertyChanged の実装

通常、INotifyPropertyChanged を継承する基底クラスを作成し、このクラスを各ウィンドウの ViewModel クラスに継承させるようです。
まずはその基底クラスとなる ViewModelBase の実装です。

/// <summary>
/// ViewModelの親クラス
/// プロパティが変更されたことを通知するため、INotifyPropertyChangedインターフェースを実装する
/// プロパティ変更時OnPropertyChangedを呼び出す
/// </summary>
class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    /// プロパティの変更をViewに通知
    /// </summary>
    /// <param name="propertyName">プロパティ名</param>
    protected void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

細かく見ていきます。

public event PropertyChangedEventHandler PropertyChanged;

イベントの宣言です。
PropertyChangedEventHandler はプロパティが変更されたときに発生する PropertyChanged イベントを処理するメソッドを表します。
PropertyChanged イベントを実行するとコントロールに対して変更通知を発行できます。
尚、PropertyChanged イベントに対するハンドラの登録はデータバインディングを行ったときにフレームワークの内部ですべて自動的に行ってくれるらしいです(これ地味に悩みました・・・)。
つまり前回の記事のコードビハインドの部分

this.DataContext = new MainWindowViewModel();

を書くだけで OK らしいです。

そして、次のコードは変更通知を発行する OnPropertyChanged メソッドの実装です。

/// <summary>
/// プロパティの変更をViewに通知
/// </summary>
/// <param name="propertyName">プロパティ名</param>
protected void OnPropertyChanged(string propertyName)
{
    if (PropertyChanged != null)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

PropertyChangednull の場合もあるらしいので、チェックします。
ちなみに上記のコードは C# 6.0 では null 条件演算子を使用することで if 文を省略できます。

/// <summary>
/// プロパティの変更をViewに通知
/// </summary>
/// <param name="propertyName">プロパティ名</param>
protected void OnPropertyChanged(string propertyName)
{
    PropertyChanged?.InvokeChanged(this, new PropertyChangedEventArgs(propertyName));
}

ViewModelの実装

ViewModel に ViewModelBase を継承させ、プロパティで OnPropertyChanged をコールします。

/// <summary>
/// MainWindowに対するViewModel
/// </summary>
class MainWindowViewModel : ViewModelBase
{
    // バインディングされる値を保持するフィールド
    private string sampleText_;

    // バインディング対象のプロパティ
    public string SampleText
    {
        get
        {
            return sampleText_;
        }
        set
        {
            sampleText_ = value;

            // 変更をViewに通知する
            OnPropertyChanged("SampleText");

            // ラベルの値も連動させる
            SampleLabel = value;
        }
    }

    // バインディングされる値を保持するフィールド
    private string sampleLabel_ = "";

    // バインディング対象のプロパティ
    public string SampleLabel
    {
        get
        {
            return sampleLabel_;
        }
        set
        {
            sampleLabel_ = value;
 
            // 変更をViewに通知する
            OnPropertyChanged("SampleLabel");
        }
    }

    /// <summary>
    /// コンストラクタ
    /// </summary>
    public MainWindowViewModel()
    {
        SampleText = "Sample";
        SampleLabel = "Sample";
    }
}

これにより、プロパティの値が変更されたときにコントロールに変更通知が発行され、コントロールの値が変わります。
いわゆる Observer パターンです。

ちなみに C# 6.0 では nameof 演算子を使用することで、メソッドの引数を文字列ではなく変数名で指定できるようになります。
これにより、Visual Studio のリファクタリング機能の対象となるため、変数名を変更した際に文字列の変更を忘れて動かなくなる、といった事態を回避できます。
なので、C# 6.0 が使用できる環境であれば nameof 演算子を使うべきです。

参考 URL