WPFで値の変更を反映させる方法:INotifyPropertyChangedの実装パターン

.NET

前回は、データバインディングについてサンプルを動かしながら説明しました。データバインディングのメリットを体感できましたでしょうか。

前回は、一度バインディングを行うと値が画面に反映され、画面上で値を変更するとデータソース側に自動で反映させることができました。しかし、データバインディング後にデータソース側で変更した内容を画面に反映させることはできませんでした。今回はこのデータソース側の変更を画面に反映させる方法について説明します。

バインディング方向

なぜINotifyPropertyChangedが必要か

冒頭にも記載しました通り、シンプルなデータバインディングだと、コード側でデータを書き換えた際に、UIが更新されません。コードでの変更をUIに通知するために、INotifyPropertyChangedインターフェイスが必要になります。

XAMLでデータバインディングを記述すると、ViewからViewModel(Model)へ連結され、これだけでも十分に協力な機構です。これをさらに双方向にして、ViewModelからViewにも連結させることでさらに強力な機構へと進化することができます。

今回は、このINotifyPropertyChangedに注目して様々な実装方法についても説明していきたいと思います。

Modeプロパティ

実装方法を説明する前に、バインディングの方向を指定するModeプロパティについて軽く触れたいと思います。Modeプロパティは公式の説明ページにもあります通り、下記の値を指定できます。

  • TwoWay
    双方向モード、TextBoxのようにユーザー入力も受け付けます。
  • OneWay
    一方通行、UI側が読み取り専用のときのように、明らかに入力をつけつけない場合に指定するとTwoWayよりもパフォーマンスが良くなります。
  • OneTime
    これも一方通行で最初の一度きりの更新になります。値が変わらない場合は、こちらを指定しておくとTwoWayよりパフォーマンスが良くなります。
  • OneWayToSource
    OneWayやOneTimeとは逆方向の一方通行になります。ViewからViewModeへの一方通行です。
  • Default
    使用するコントロールに応じて自動でModeが選択されます。TextBoxならTwoWay、TextBlockならOneWayのように判別されます。

ここで注意していただきたいのは、TwoWayを指定しても、データの変更を画面に反映させるにはやはりINotifyPropertyChangedインターフェイスの実装が必須になります。

INotifyPropertyChangedインターフェイス

変更通知

INotifyPropertyChangedをどこに実装するかというと、ViewModelまたは、Modelに実装することになります。バインディングされたプロパティが変更があったことを、伝えられるようにします。

View(UI)側は、バインドされたプロパティのオブジェクトが、INotifyPropertyChangedを実装していると、PropertyChangedイベントを経由して、変更通知を補足することができるようになります。

サンプル

INotifyPropertyChangedを実装したサンプルを作成していきたいと思います。
前回作成した下記データバインディングのサンプルに変更を加えます。

まずは、INotifyPropertyChangedを実装します。今回は、ユーザー情報クラスにインターフェイスを実装し、コード側で変更したときに、UI(XAML)に通知されるようにします。

    /// <summary>
    /// ユーザー情報
    /// </summary>
    public class SampleUser : INotifyPropertyChanged
    {
        /// <summary>
        /// プロパティ変更イベント
        /// </summary>
        public event PropertyChangedEventHandler? PropertyChanged;

        /// <summary>
        /// プロパティ変更イベントを発行する。
        /// </summary>
        /// <param name="propertyName">呼び出し元のプロパティ名</param>
        private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        private string _id = string.Empty;
        /// <summary>
        /// ユーザーID
        /// </summary>
        public string ID
        {
            get { return _id; }
            set
            {
                if (_id != value)
                {
                    _id = value;
                    this.NotifyPropertyChanged();
                }
            }
        }

        private string _name = string.Empty;
        /// <summary>
        /// 名称
        /// </summary>
        public string Name
        {
            get { return _name; }
            set 
            {
                if (_name != value)
                {
                    _name = value;
                    this.NotifyPropertyChanged();
                }
            }
        }

        private string _prefecture = string.Empty;

        /// <summary>
        /// 都道府県
        /// </summary>
        public string Prefecture
        {
            get { return _prefecture; }
            set 
            {
                if (_prefecture != value)
                {
                    _prefecture = value;
                    this.NotifyPropertyChanged();
                }
            }
        }

        /// <summary>
        /// ユーザー情報をクリアする。
        /// </summary>
        public void Clear()
        {
            this.ID = string.Empty;
            this.Name = string.Empty;
            this.Prefecture = string.Empty;
        }

        public override string ToString()
        {
            return $"{_id} {_name} {_prefecture}";
        }
    }

まずINotifyPropertyChangedインターフェイスのイベント「PropertyChanged」を実装します。
これは通常のインターフェイスを実装するのと同じです。このイベントを通じてプロパティが変わったことを伝えることになります。
伝え方は一つ一つのプロパティでこのイベントを発行することになります。通常は、変更があったプロパティ名をイベントの引数に渡しますが、毎回プロパティ名を記載するのも手間なので、NotifyPropertyChangedメソッドを作成しました。
この引数にはCallerMemberName属性が付与されていて、呼び出されたもとのメンバー名(プロパティ名)が自動で渡されるようになります。
例えば、IDプロパティのように値が変更されたらNotifyPropertyChangedメソッドを呼び出すことでメソッドの引数に名前「ID」がわたり、IDプロパティが変わったことをPropertyChangedイベントを使って通知することになります。

この状態でクリアボタンを押下すると

すべてのプロパティ値が空文字に設定されて、変更内容が画面に通知されることを確認できます。

このようにINotifyPropertyChangedインターフェイスを実装することで、バインドされている項目が画面とコードで双方向に連携されるようになります。
バインドには他にも便利な機能がありますので、今後も紹介していきたいと思います。


コメント

タイトルとURLをコピーしました